web-dev-qa-db-fra.com

Qu'est-ce qui est dans le monde l'attribut "__class__" en python

J'ai une question à propos de __class__ en python.

La documentation indique que __class__ est la classe à laquelle appartient une instance de classe. J'ai donc mené une série d'expériences:

class counter:
    count = 0
    def __init__(self):
            self.__class__.count += 1

NewCounter1 = counter()
print NewCounter1.count   #The result is 1
NewCounter2 = counter()
print NewCounter2.count   #The result is 2
print NewCounter2.__class__.count is NewCounter2.count  #result: True

Tout va bien.

Puis j'entre le code comme suit:

NewCounter2.__class__.count = 3

print NewCounter1.count                    #result:3
print NewCounter1.__class__.count      #result:3
print NewCounter2.count                    #result:3
print NewCounter2.__class__.count      #result:3
print NewCounter2.__class__.count is NewCounter2.count      #result: True

D'après le code ci-dessus, j'ai pensé que peut-être NewCounter1.count est égal à NewCounter1 ou __class__.count, mais le code suivant m'a surpris

NewCounter2.count = 5

print NewCounter1.count                 #result:3
print NewCounter1.__class__.count   #result:3
print NewCounter2.count                 #result:5
print NewCounter2.__class__.count   #result:3
print NewCounter2.__class__.count is NewCounter2.count       #result: False

Pourquoi NewCounter2.count a-t-il changé mais NewCounter2.__class__.count est resté à 3? De plus, lorsque j'ai changé NewCounter2.count, NewCounter2.__class__.count is NewCounter2.count est devenu False. Qu'est-ce qui est dans le monde l'attribut __class__

30
Searene

"D'après les codes ci-dessus, j'ai pensé que peut-être NewCounter1.count est égal à NewCounter1. _Class_.count"

Le problème est qu’au moment de cette phrase de votre question, après les seules instructions:

NewCounter1 = counter()
NewCounter2 = counter()
NewCounter2.__class__.count = 3

ayant créé NewCounter1 et NewCounter2
et ayant modifié l'attribut de classe counter.count,
il n'y a pas d'objets NewCounter1.count ni NewCounter2.count, puis "égal" n'a pas de véritable sens.

.

Voir la création de NewCounter1 et juste après:

class counter:
    count = 0
    def __init__(self):
        self.__class__.count += 1

print 'counter.count BEFORE ==',counter.count  # The result is 0
NewCounter1 = counter()
print '\nNewCounter1.__dict__ ==',NewCounter1.__dict__  # The result is {}
print 'NewCounter1.count    ==',NewCounter1.count # The result is 1
print 'counter.count AFTER  ==',counter.count  # The result is 1

NewCounter ._dict_ est l'espace de noms de l'instance NewCounter1
print NewCounter1.count imprime le même que print counter.count
Cependant, 'count' (la chaîne 'count') n'est pas dans l'espace de nommage de NewCounter1, c'est-à-dire qu'il n'y a pas d'attribut count dans l'espace de nommage de l'instance créée! 

Comment est-ce possible ?

En effet, l'instance est créée sans assignation à un identifiant 'count' à l'intérieur du INIT
-> il n'y a pas de création réelle d'attribut en tant que champ dans NewCounter1, c'est-à-dire pas de création d'attribut INSTANCE. 

La conséquence est que lorsque l'instruction
print 'NewCounter1.count ==',NewCounter1.count
est évalué, l'interprète ne trouve pas d'attribut d'instance dans l'espace de nommage de NewCounter1, puis se rend dans la classe de l'instance pour rechercher la clé 'count' dans l'espace de nommage de cette classe; là, il trouve 'count' en tant que clé d'un attribut CLASS et peut prendre la valeur de l'objet counter.count en tant que valeur à afficher en réponse à l'instruction.

Une instance de classe a un espace de noms implémenté sous la forme d'un dictionnaire qui est le premier endroit où les références d'attribut sont recherchées. Quand un attribut n’y est pas trouvé et la classe de l’instance a un attribut par ce nom, la recherche continue avec la classe les attributs. http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

Donc, NewCounter1.count equals NewCounter1.__class__.count signifie ici que la valeur de NewCounter1.count, même si celle-ci n'existe pas réellement, est la valeur de l'attribut de classe NewCounter1. classe. compte. Ici "is" est le verbe anglais, pas la caractéristique est du langage qui teste l’identité de deux objets, et cela signifie "est considéré comme ayant"

Lorsque NewCounter2.__class__.count = 3 est exécuté, seul l'attribut de classe counter.count est affecté. Les espaces de noms NewCounter1 et NewCounter2 restent vides et le même mécanisme consistant à se rendre dans la classe pour trouver la valeur de counter.count est suivi.

.

À la fin, lorsque NewCounter2.count = 5 est exécuté, un attribut INSTANCE count est créé en tant que champ dans l'objet NewCounter2 et «count» apparaît dans l'espace de nommage de NewCounter2.
Il n’écrase rien car il n'y avait rien de précédent dans le __dict__ de l’instance
Aucun autre changement n'affecte NewCounter1 et counter.count 

Le code suivant montre plus explicitement les événements sous-jacents lors de l'exécution:

from itertools import islice

class counter:
    count = 0
    def __init__(self):
        print ('  |  counter.count   first == %d  at  %d\n'
               '  |     self.count   first == %d  at  %d')\
               % (counter.count,id(counter.count),
                  self.count,id(self.count))

        self.__class__.count += 1 # <<=====

        print ('  |  counter.count  second == %d  at  %d\n'
               '  |     self.count  second == %d  at  %d\n'
               '  |  id(counter) == %d   id(self) == %d')\
               % (counter.count,id(counter.count),
                  self.count,id(self.count),
                  id(counter),id(self))



def display(*li):
    it = iter(li)
    for ch in it:
        nn = (len(ch)-len(ch.lstrip('\n')))*'\n'
        x = it.next()
        print '%s ==  %s %s' % (ch,x,'' if '__dict__' in ch else 'at '+str(id(x)))



display('counter.count AT START',counter.count)


print ('\n\n----- C1 = counter() ------------------------')
C1 = counter()
display('C1.__dict__',C1.__dict__,
        'C1.count ',C1.count,
        '\ncounter.count ',counter.count)


print ('\n\n----- C2 = counter() ------------------------')
C2 = counter()
print ('  -------------------------------------------') 
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)


print '\n\n------- C2.__class__.count = 3 ------------------------\n'
C2.__class__.count = 3
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)


print '\n\n------- C2.count = 5 ------------------------\n'
C2.count = 5
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)

résultat

counter.count AT START ==  0 at 10021628


----- C1 = counter() ------------------------
  |  counter.count   first == 0  at  10021628
  |     self.count   first == 0  at  10021628
  |  counter.count  second == 1  at  10021616
  |     self.count  second == 1  at  10021616
  |  id(counter) == 11211248   id(self) == 18735712
C1.__dict__ ==  {} 
C1.count  ==  1 at 10021616

counter.count  ==  1 at 10021616


----- C2 = counter() ------------------------
  |  counter.count   first == 1  at  10021616
  |     self.count   first == 1  at  10021616
  |  counter.count  second == 2  at  10021604
  |     self.count  second == 2  at  10021604
  |  id(counter) == 11211248   id(self) == 18736032
  -------------------------------------------
C1.__dict__ ==  {} 
C2.__dict__ ==  {} 
C1.count  ==  2 at 10021604
C2.count  ==  2 at 10021604
C1.__class__.count ==  2 at 10021604
C2.__class__.count ==  2 at 10021604

counter.count  ==  2 at 10021604


------- C2.__class__.count = 3 ------------------------

C1.__dict__ ==  {} 
C2.__dict__ ==  {} 
C1.count  ==  3 at 10021592
C2.count  ==  3 at 10021592
C1.__class__.count ==  3 at 10021592
C2.__class__.count ==  3 at 10021592

counter.count  ==  3 at 10021592


------- C2.count = 5 ------------------------

C1.__dict__ ==  {} 
C2.__dict__ ==  {'count': 5} 
C1.count  ==  3 at 10021592
C2.count  ==  5 at 10021568
C1.__class__.count ==  3 at 10021592
C2.__class__.count ==  3 at 10021592

counter.count  ==  3 at 10021592

.

Une chose intéressante à faire est d'ajouter une instruction
self.count = counter.count
AVANT la ligne
self.__class__.count += 1 # <<=====
.__ pour observer l'évolution des résultats

.

En conclusion, le point ne concernait pas __class__ mais le mécanisme de recherche d'un attribut, et ce mécanisme est trompeur lorsqu'il est ignoré.

21
eyquem

Cette ligne:

NewCounter2.__class__.count = 3

change la valeur statique count de counter, mais ici:

NewCounter2.count = 5

NewCounter2 a maintenant son propre attribut count qui cache la valeur statique count;
Cette ligne n'a donc aucun effet sur NewCounter1.
C'est aussi pourquoi NewCounter2.__class__.count != NewCounter2.count.

10
chown

La nouvelle liaison (c'est-à-dire l'affectation) d'un attribut sur un objet portant le même nom qu'un attribut sur la classe masque l'attribut sur la classe. L'objet est toujours vérifié en premier pour les attributs, suivi des classes dans l'ordre MRO.

Ligne

NewCounter2.count = 5

Crée un nouveau niveau d'instance attribut de NewCounter2. Après cela, vous accédez à deux attributs différents (NewCounter2.count - attr au niveau instance et NewCounter2.__class__.count - attr au niveau classe) qui provoquent un comportement "étrange".

0
Andriy Ivaneyko