2008-10-15 9 views
18

Ce qui suit semble étrange .. Fondamentalement, l'attribut somedata semble partagé entre toutes les classes qui ont hérité de the_base_class.Pourquoi les références d'attributs agissent-elles de la sorte avec l'héritage Python?

class the_base_class: 
    somedata = {} 
    somedata['was_false_in_base'] = False 


class subclassthing(the_base_class): 
    def __init__(self): 
      print self.somedata 


first = subclassthing() 
{'was_false_in_base': False} 
first.somedata['was_false_in_base'] = True 
second = subclassthing() 
{'was_false_in_base': True} 
>>> del first 
>>> del second 
>>> third = subclassthing() 
{'was_false_in_base': True} 

Définition self.somedata dans la fonction __init__ est évidemment la bonne façon de contourner ce (pour chaque classe a son propre somedata dict) - mais quand est-ce comportement souhaitable?

Répondre

23

Vous avez raison, somedata est partagée entre toutes les instances de la classe et de ses sous-classes, car elle est créée à la classe définition heure. Les lignes

somedata = {} 
somedata['was_false_in_base'] = False 

sont exécutées lorsque la classe est définie, à savoir quand l'interprète rencontre la déclaration class - pas lorsque l'instance est créée (pensez blocs statiques initialiseur en Java). Si un attribut n'existe pas dans une instance de classe, l'objet de classe est vérifié pour l'attribut.

Au moment de la définition de la classe, vous pouvez exécuter du code arbritrary, comme ceci:

import sys 
class Test(object): 
    if sys.platform == "linux2": 
     def hello(self): 
       print "Hello Linux" 
    else: 
     def hello(self): 
       print "Hello ~Linux" 

Sur un système Linux, Test().hello() imprimera Hello Linux, sur tous les autres systèmes de l'autre chaîne sera imprimée.

En constraste, les objets __init__ sont créés à instanciation temps et appartiennent à l'instance que (quand ils sont affectés à self):

class Test(object): 
    def __init__(self): 
     self.inst_var = [1, 2, 3] 

objets définis sur un objet de classe plutôt que par exemple peut être utile dans de nombreux cas. Par exemple, vous voudrez peut-être mettre en cache des instances de votre classe, de sorte que les instances avec les mêmes valeurs membres peuvent être partagées (en supposant qu'ils sont censés être immuable):

class SomeClass(object): 
    __instances__ = {} 

    def __new__(cls, v1, v2, v3): 
     try: 
      return cls.__insts__[(v1, v2, v3)] 
     except KeyError: 
      return cls.__insts__.setdefault(
       (v1, v2, v3), 
       object.__new__(cls, v1, v2, v3)) 

La plupart du temps, j'utiliser des données dans les instances de classe en en conjonction avec des métaclasses ou des méthodes d'usine génériques.

+1

Comment déclarer une instance var alors? – OscarRyz

+0

J'ai ajouté un exemple dans ma réponse. –

+0

Y a-t-il une différence entre des données simples et complexes? comme dit TimB? – OscarRyz

10

Notez qu'une partie du comportement que vous voyez est dû à somedata étant un dict, par opposition à un type de données simple tel qu'un bool.

Par exemple, voir ce autre exemple qui se comporte différemment (bien que très similaire):

class the_base_class: 
    somedata = False 

class subclassthing(the_base_class): 
    def __init__(self): 
     print self.somedata 


>>> first = subclassthing() 
False 
>>> first.somedata = True 
>>> print first.somedata 
True 
>>> second = subclassthing() 
False 
>>> print first.somedata 
True 
>>> del first 
>>> del second 
>>> third = subclassthing() 
False 

La raison pour laquelle cet exemple se comporte différemment de celle donnée dans la question est parce qu'ici first.somedata est donné une nouvelle valeur (l'objet True), alors que dans le premier exemple l'objet dict référencé par first.somedata (et aussi par les autres instances de sous-classes) est en cours de modification.

Voir le commentaire de Torsten Marek à cette réponse pour plus de clarification.

+0

Êtes-vous en train de dire que les types de données simples ne sont pas partagés en même temps? – OscarRyz

+0

Non, ils sont partagés, chaque type de données en Python est un type de référence, même des entiers et des booléens etc. La raison en est que first.somedata ne contient pas la valeur False/True, il fait référence à l'objet False/True. S'il est réaffecté, il fait simplement référence à un objet différent. –

+0

@Oscar, je soupçonne que la différence est due à la nature des références. Lorsque somedata est un dictionnaire, alors les premier et second ont leur propre référence mais chaque référence pointe vers le même dictionnaire en mémoire. Quand somedate est booléen, ils ont toujours leurs propres copies, mais ils modifient directement le booléen. –

2

Je pense que la façon la plus simple de comprendre cela (pour pouvoir prédire le comportement) est de réaliser que votre somedata est un attribut de la classe et non l'instance de cette classe si vous le définissez de cette façon.

Il n'y a vraiment qu'un seul somedata à tout moment parce que dans votre exemple, vous n'avez pas attribué ce nom mais l'avez utilisé pour rechercher un dict et lui assigner un élément (clé, valeur). C'est un gotcha qui est une conséquence de la façon dont l'interpréteur python fonctionne et peut être déroutant au premier abord.