web-dev-qa-db-fra.com

Comprendre la différence entre `self` et` cls` et s'ils se réfèrent aux mêmes attributs

J'essaie de comprendre s'il y a des différences entre self et cls mais j'ai du mal, même s'il y a beaucoup de discussions sur ce sujet. Par exemple:

class maclass():
    A = "class method"

    def __init__(self):
        self.B = "instance method"

    def getA_s(self):
        print(self.A)

    def getA_c(cls):
        print(cls.A)

    def getB_s(self):
        print(self.B)

    def getB_c(cls):
        print(cls.B)

C = maclass()
C.getA_s()
C.getA_c()
C.getB_s()
C.getB_c()

ce qui me donne:

class method
class method
instance method
instance method

Donc, que j'utilise self ou cls, cela fait toujours référence à la même variable. Lorsque j'ajoute un self.A dans le Init__, le cls.A vient d'être remplacé

def __init__(self):
        self.B = "instance method"
        self.A = "new instance method"

et je reçois:

new instance method
new instance method
instance method
instance method

Je ne comprends pas l'intérêt d'avoir deux façons d'appeler un membre de la classe s'ils sont les mêmes? Je sais que c'est une question courante sur ce forum, mais je ne comprends vraiment pas pourquoi nous utiliserions des mots différents pour désigner la même chose (nous pourrions même utiliser n'importe quel nom de variable au lieu de self ou cls).

mettre à jour

Dans le cas suivant:

class maclass():
    A = "class method, "

    def __init__(self):
        self.A = "instance method, "

    def getA_s(self):
        print(self.A) #give me "instance method, "

    @classmethod
    def getA_c(cls):
        print(cls.A) #give me "class method, "

C = maclass()
C.getA_s()
C.getA_c()
print(' ')
print(C.A) #give me "instance method, "

Je reçois :

instance method, 
class method, 

instance method,    

Donc dans ce cas, dans maclass: cls.A et self.A ne fait pas référence à la même variable.

6
ymmx

Toutes vos méthodes sont des méthodes d'instance. Aucune d'entre elles n'est une méthode de classe.

Le premier argument d'une méthode est nommé self uniquement par convention. Vous pouvez le nommer comme vous voulez et le nommer cls à la place ne fera pas de lui une référence à la classe. Le fait que le premier argument soit lié à une instance est dû au fonctionnement de la recherche de méthode (accéder à C.getA_s Produit un objet de méthode lié , et appeler cet objet fait passer C dans la fonction d'origine getA_s), les noms des paramètres ne jouent aucun rôle.

Dans vos méthodes, vous faites simplement référence à des attributs d'instance. Que l'attribut A ne soit finalement défini que sur la classe n'a pas d'importance, vous accédez toujours à cet attribut via C.A (Où C est l'instance que vous avez créée), pas maclass.A. La recherche d'un attribut sur l'instance trouvera également des attributs définis sur la classe s'il n'y a pas d'attribut d'instance qui le suit.

Pour faire d'une méthode une méthode de classe, décorez-la avec le décorateur @classmethod :

@classmethod
def getA_c(cls):
    print(cls.A)

Maintenant, cls sera toujours une référence à la classe, jamais à l'instance. Je dois souligner à nouveau qu'il n'a pas vraiment d'importance à Python quel nom j'ai choisi pour ce premier argument, mais cls est la convention ici car cela facilite le rappel le lecteur que cette méthode est liée à l'objet de classe.

Notez que si vous le faites pour la méthode getB_c(), alors essayer d'accéder à cls.B Dans la méthode échouera car il n'y a pas d'attribut B sur le maclass objet de classe.

En effet, classmethod enveloppe la fonction dans un objet descripteur qui remplace le comportement normal de liaison de fonction. C'est le descripteur protocole qui fait que les méthodes sont liées aux instances lorsqu'elles sont accédées en tant qu'attributs sur l'instance, un objet classmethod redirige cette liaison processus.

Voici une courte démonstration avec des commentaires en ligne, j'ai utilisé les conversions Python pour nommer les classes (en utilisant CamelCase), et pour les instances, les attributs, les fonctions et les méthodes (en utilisant snake_case):

>>> class MyClass():
...     class_attribute = "String attribute on the class"
...     def __init__(self):
...         self.instance_attribute = "String attribute on the instance"
...     @classmethod
...     def get_class_attribute(cls):
...         return cls.class_attribute
...     def get_instance_attribute(self):
...         return self.instance_attribute
...     @classmethod
...     def get_instance_attribute_on_class(cls):
...         return cls.instance_attribute
...
>>> instance = MyClass()
>>> instance.class_attribute  # class attributes are visible on the instance
'String attribute on the class'
>>> MyClass.class_attribute   # class attributes are also visible on the class
'String attribute on the class'
>>> instance.get_class_attribute()  # bound to the class, but that doesn't matter here
'String attribute on the class'
>>> instance.class_attribute = "String attribute value overriding the class attribute"
>>> instance.get_class_attribute()  # bound to the class, so the class attribute is found
'String attribute on the class'
>>> MyClass.get_instance_attribute_on_class()   # fails, there is instance_attribute on the class
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 12, in get_instance_attribute_on_class
AttributeError: type object 'MyClass' has no attribute 'instance_attribute'

Notez que la méthode class accède à l'attribut class même si nous définissons un attribut avec le même nom sur l'instance.

Vient ensuite le comportement contraignant:

>>> MyClass.get_instance_attribute   # accessing the method on the class gives you the function
<function MyClass.get_instance_attribute at 0x10f94f268>
>>> instance.get_instance_attribute  # accessing the method on the instance gives you the bound method
<bound method MyClass.get_instance_attribute of <__main__.MyClass object at 0x10f92b5f8>>
>>> MyClass.get_class_attribute      # class methods are always bound, to the class
<bound method MyClass.get_class_attribute of <class '__main__.MyClass'>>
>>> instance.get_class_attribute     # class methods are always bound, to the class
<bound method MyClass.get_class_attribute of <class '__main__.MyClass'>>

Les méthodes liées vous indiquent à quoi elles sont liées à , l'appel de la méthode passe dans cet objet lié comme premier argument. Cet objet peut également être inspecté en regardant l'attribut __self__ D'une méthode liée:

>>> instance.get_instance_attribute.__self__  # the instance
<__main__.MyClass object at 0x10f92b5f8>
>>> instance.get_class_attribute.__self__     # the class
<class '__main__.MyClass'>
20
Martijn Pieters