Si vous programmez en Python (programmation orientée objet) depuis un certain temps, vous avez certainement rencontré des méthodes qui ont self
comme premier paramètre.
Essayons d'abord de comprendre ce qu'est ce paramètre de soi récurrent.
Qu'est-ce que soi en Python?
Dans la programmation orientée objet, chaque fois que nous définissons des méthodes pour une classe, nous les utilisons self
comme premier paramètre dans chaque cas. Regardons la définition d'une classe appelée Cat
.
class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")
Dans ce cas, toutes les méthodes, y compris __init__
, ont le premier paramètre comme self
.
Nous savons que la classe est un modèle pour les objets. Ce plan peut être utilisé pour créer plusieurs nombres d'objets. Créons deux objets différents de la classe ci-dessus.
cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)
Le self
mot-clé est utilisé pour représenter une instance (objet) de la classe donnée. Dans ce cas, les deux Cat
objets cat1
et cat2
ont leurs propres attributs name
et age
. S'il n'y avait pas d'argument self, la même classe ne pouvait pas contenir les informations pour ces deux objets.
Cependant, puisque la classe n'est qu'un modèle, self
permet d'accéder aux attributs et aux méthodes de chaque objet en python. Cela permet à chaque objet d'avoir ses propres attributs et méthodes. Ainsi, même bien avant de créer ces objets, nous référençons les objets comme self
lors de la définition de la classe.
Pourquoi le soi est-il défini explicitement à chaque fois?
Même lorsque nous comprenons l'utilisation de self
, cela peut sembler étrange, en particulier pour les programmeurs venant d'autres langages, qui self
est passé en paramètre explicitement chaque fois que nous définissons une méthode. Comme le dit The Zen of Python , «l' explicite vaut mieux que l'implicite ».
Alors, pourquoi devons-nous faire cela? Prenons un exemple simple pour commencer. Nous avons une Point
classe qui définit une méthode distance
pour calculer la distance par rapport à l'origine.
class Point(object): def __init__(self,x = 0,y = 0): self.x = x self.y = y def distance(self): """Find distance from origin""" return (self.x**2 + self.y**2) ** 0.5
Instancions maintenant cette classe et trouvons la distance.
>>> p1 = Point(6,8) >>> p1.distance() 10.0
Dans l'exemple ci-dessus, __init__()
définit trois paramètres mais nous venons d'en passer deux (6 et 8). De même, distance()
nécessite un mais aucun argument n'a été transmis. Pourquoi Python ne se plaint-il pas de cette incompatibilité de numéro d'argument?
Que se passe-t-il en interne?
Point.distance
et p1.distance
dans l'exemple ci-dessus sont différents et pas exactement les mêmes.
>>> type(Point.distance) >>> type(p1.distance)
On voit que le premier est une fonction et le second est une méthode. Une chose particulière à propos des méthodes (en Python) est que l'objet lui-même est passé comme premier argument à la fonction correspondante.
Dans le cas de l'exemple ci-dessus, l'appel de méthode p1.distance()
est en fait équivalent à Point.distance(p1)
.
Généralement, lorsque nous appelons une méthode avec quelques arguments, la fonction de classe correspondante est appelée en plaçant l'objet de la méthode avant le premier argument. Donc, quelque chose comme obj.meth(args)
devient Class.meth(obj, args)
. Le processus d'appel est automatique tandis que le processus de réception ne l'est pas (son explicite).
C'est la raison pour laquelle le premier paramètre d'une fonction dans la classe doit être l'objet lui-même. Ecrire ce paramètre tel self
quel n'est qu'une convention. Ce n'est pas un mot-clé et n'a pas de signification particulière en Python. Nous pourrions utiliser d'autres noms (comme this
) mais c'est fortement déconseillé. L'utilisation de noms autres que ceux qui self
sont mal vus par la plupart des développeurs et dégrade la lisibilité du code (la lisibilité compte ).
Le soi peut être évité
À présent, vous êtes clair que l'objet (instance) lui-même est transmis automatiquement comme premier argument. Ce comportement implicite peut être évité lors de la création d'une méthode statique . Prenons l'exemple simple suivant:
class A(object): @staticmethod def stat_meth(): print("Look no self was passed")
Voici @staticmethod
un décorateur de fonctions qui rend stat_meth()
statique. Instancions cette classe et appelons la méthode.
>>> a = A() >>> a.stat_meth() Look no self was passed
À partir de l'exemple ci-dessus, nous pouvons voir que le comportement implicite consistant à passer l'objet comme premier argument a été évité lors de l'utilisation d'une méthode statique. Dans l'ensemble, les méthodes statiques se comportent comme les anciennes fonctions simples (puisque tous les objets d'une classe partagent des méthodes statiques).
>>> type(A.stat_meth) >>> type(a.stat_meth)
Le moi est là pour rester
L'explicite self
n'est pas unique à Python. Cette idée a été empruntée à Modula-3 . Voici un cas d'utilisation où cela devient utile.
Il n'y a pas de déclaration de variable explicite en Python. Ils entrent en action dès la première mission. L'utilisation de self
facilite la distinction entre les attributs d'instance (et les méthodes) des variables locales.
Dans le premier exemple, self.x est un attribut d'instance tandis que x est une variable locale. Ils ne sont pas identiques et se trouvent dans des espaces de noms différents.
Beaucoup ont proposé de faire de self un mot-clé en Python, comme this
en C ++ et Java. Cela éliminerait l'utilisation redondante d'explicite self
de la liste de paramètres formels dans les méthodes.
Bien que cette idée semble prometteuse, elle ne se réalisera pas. Du moins pas dans un proche avenir. La raison principale est la rétrocompatibilité. Voici un blog du créateur de Python lui-même expliquant pourquoi le moi explicite doit rester.
__init __ () n'est pas un constructeur
Une conclusion importante qui peut être tirée des informations jusqu'à présent est que la __init__()
méthode n'est pas un constructeur. De nombreux programmeurs Python naïfs se confondent avec lui car il __init__()
est appelé lorsque nous créons un objet.
A closer inspection will reveal that the first parameter in __init__()
is the object itself (object already exists). The function __init__()
is called immediately after the object is created and is used to initialize it.
Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__()
. A common signature of this method is:
__new__(cls, *args, **kwargs)
When __new__()
is called, the class itself is passed as the first argument automatically(cls
).
Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.
Some important things to remember when implementing __new__()
are:
__new__()
is always called before__init__()
.- First argument is the class itself which is passed implicitly.
- Always return a valid object from
__new__()
. Not mandatory, but its main use is to create and return an object.
Let's take a look at an example:
class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y
Now, let's now instantiate it.
>>> p2 = Point(3,4) From new (3, 4) () From init
This example illustrates that __new__()
is called before __init__()
. We can also see that the parameter cls in __new__()
is the class itself (Point
). Finally, the object is created by calling the __new__()
method on object base class.
In Python, object
is the base class from which all other classes are derived. In the above example, we have done this using super().
Use __new__ or __init__?
You might have seen __init__()
very often but the use of __new__()
is rare. This is because most of the time you don't need to override it. Generally, __init__()
is used to initialize a newly created object while __new__()
is used to control the way an object is created.
We can also use __new__()
to initialize attributes of an object, but logically it should be inside __init__()
.
One practical use of __new__()
, however, could be to restrict the number of objects created from a class.
Suppose we wanted a class SqPoint
for creating instances to represent the four vertices of a square. We can inherit from our previous class Point
(the second example in this article) and use __new__()
to implement this restriction. Here is an example to restrict a class to have only four instances.
class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)
Un exemple d'exécution:
>>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects