web-dev-qa-db-fra.com

Quelle est la différence entre les variables de classe et d'instance?

Quelle est la différence entre les variables de classe et d'instance en Python?

class Complex:
    a = 1

et

class Complex:
    def __init__(self):
        self.a = 1

En utilisant l'appel: x = Complex().a dans les deux cas assigne x à 1.

Une réponse plus approfondie sur __init__() et self sera appréciée.

59

Lorsque vous écrivez un bloc de classe, vous créez des attributs de classe (ou des variables de classe). Tous les noms que vous attribuez dans le bloc de classe, y compris les méthodes que vous définissez avec def deviennent des attributs de classe.

Après la création d'une instance de classe, tout élément faisant référence à l'instance peut créer des attributs d'instance sur celle-ci. Dans les méthodes, l'instance "courante" est presque toujours liée au nom self, c'est pourquoi vous les considérez comme des "variables autonomes". Habituellement, dans la conception orientée objet, le code attaché à une classe est censé avoir le contrôle sur les attributs des instances de cette classe, donc presque toute l'attribution d'attribut d'instance se fait à l'intérieur des méthodes, en utilisant la référence à l'instance reçue dans le self paramètre de la méthode.

Les attributs de classe sont souvent comparés à variables (ou méthodes) statiques comme dans les langages comme Java, C # ou C++. Cependant, si vous voulez viser une compréhension plus approfondie, j'éviterais de penser que les attributs de classe sont "les mêmes" que les variables statiques. Bien qu'ils soient souvent utilisés aux mêmes fins, le concept sous-jacent est assez différent. Plus d'informations à ce sujet dans la section "avancée" sous la ligne.

Un exemple!

class SomeClass:
    def __init__(self):
        self.foo = 'I am an instance attribute called foo'
        self.foo_list = []

    bar = 'I am a class attribute called bar'
    bar_list = []

Après avoir exécuté ce bloc, il existe une classe SomeClass, avec 3 attributs de classe: __init__, bar et bar_list.

Ensuite, nous allons créer une instance:

instance = SomeClass()

Lorsque cela se produit, la méthode __init__ De SomeClass est exécutée, recevant la nouvelle instance dans son paramètre self. Cette méthode crée deux attributs d'instance: foo et foo_list. Ensuite, cette instance est affectée à la variable instance, elle est donc liée à une chose avec ces deux attributs d'instance: foo et foo_list.

Mais:

print instance.bar

donne:

I am a class attribute called bar

Comment est-ce arrivé? Lorsque nous essayons de récupérer un attribut via la syntaxe à points, et que l'attribut n'existe pas, Python passe par un tas d'étapes pour essayer de répondre à votre demande de toute façon. La prochaine chose qu'il va essayer est de regarder les attributs de classe de la classe de votre instance. Dans ce cas, il a trouvé un attribut bar dans SomeClass, il l'a donc renvoyé.

C'est aussi ainsi que les appels de méthode fonctionnent. Lorsque vous appelez mylist.append(5), par exemple, mylist n'a pas d'attribut nommé append. Mais la classe de mylist le fait, et elle est liée à un objet de méthode. Cet objet de méthode est renvoyé par le bit mylist.append, Puis le bit (5) Appelle la méthode avec l'argument 5.

Cela est utile parce que toutes les instances de SomeClass auront accès au même attribut bar. Nous pourrions créer un million d'instances, mais nous n'avons besoin que de stocker cette chaîne en mémoire, car ils peuvent tous la trouver.

Mais il faut être un peu prudent. Jetez un œil aux opérations suivantes:

sc1 = SomeClass()
sc1.foo_list.append(1)
sc1.bar_list.append(2)

sc2 = SomeClass()
sc2.foo_list.append(10)
sc2.bar_list.append(20)

print sc1.foo_list
print sc1.bar_list

print sc2.foo_list
print sc2.bar_list

Que pensez-vous que cela imprime?

[1]
[2, 20]
[10]
[2, 20]

En effet, chaque instance a sa propre copie de foo_list, Elles ont donc été ajoutées séparément. Mais toutes les instances partagent l'accès au même bar_list. Donc quand nous avons fait sc1.bar_list.append(2) cela a affecté sc2, Même si sc2 N'existait pas encore! De même, sc2.bar_list.append(20) a affecté le bar_list Récupéré via sc1. Ce n'est souvent pas ce que vous voulez.


Une étude avancée suit. :)

Pour vraiment grogner Python, provenant de langages OO typiquement statiques traditionnels comme Java et C #, vous devez apprendre à repenser un peu les classes.

En Java, une classe n'est pas vraiment une chose à part entière. Lorsque vous écrivez une classe, vous déclarez davantage un tas de choses que toutes les instances de cette classe ont en commun. Au moment de l'exécution, il n'y a que des instances (et des méthodes/variables statiques, mais ce ne sont vraiment que des variables et des fonctions globales dans un espace de noms associé à une classe, rien à voir avec OO vraiment). Les classes sont les de la façon dont vous écrivez dans votre code source à quoi ressembleront les instances au moment de l'exécution, elles "n'existent" que dans votre code source, pas dans le programme en cours d'exécution.

En Python, une classe n'a rien de spécial. C'est un objet comme tout le reste. Ainsi, les "attributs de classe" sont en fait exactement la même chose que les "attributs d'instance"; en réalité, il n'y a que des "attributs". La seule raison de faire une distinction est que nous avons tendance à utiliser des objets qui sont des classes différemment des objets qui ne sont pas des classes. Le mécanisme sous-jacent est tout de même. C'est pourquoi je dis que ce serait une erreur de considérer les attributs de classe comme des variables statiques d'autres langages.

Mais ce qui rend vraiment Python différentes des classes de style Java, c'est que comme tout autre objet chaque classe est une instance d'une classe!

En Python, la plupart des classes sont des instances d'une classe intégrée appelée type. C'est cette classe qui contrôle le comportement commun des classes et fait tout ce qui est OO comme ça. La manière par défaut OO d'avoir des instances de classes) qui ont leurs propres attributs et ont des méthodes/attributs communs définis par leur classe, n'est qu'un protocole en Python. Vous pouvez en modifier la plupart des aspects si vous le souhaitez. Si vous avez déjà entendu parler de l'utilisation d'un métaclasse , il suffit de définir une classe qui est une instance d'une classe différente de type.

La seule chose vraiment "spéciale" à propos des classes (à part toutes les machines intégrées pour les faire fonctionner comme elles le font par défaut), est la syntaxe du bloc de classe, pour vous faciliter la création d'instances de type . Cette:

class Foo(BaseFoo):
    def __init__(self, foo):
        self.foo = foo

    z = 28

est à peu près équivalent à ce qui suit:

def __init__(self, foo):
    self.foo = foo

classdict = {'__init__': __init__, 'z': 28 }

Foo = type('Foo', (BaseFoo,) classdict)

Et il fera en sorte que tout le contenu de classdict devienne des attributs de l'objet qui est créé.

Ainsi, il devient presque trivial de voir que vous pouvez accéder à un attribut de classe par Class.attribute Tout aussi facilement que i = Class(); i.attribute. i et Class sont des objets et les objets ont des attributs. Cela permet également de comprendre facilement comment modifier une classe après sa création; attribuez simplement ses attributs de la même manière que vous le feriez avec n'importe quel autre objet!

En fait, les instances n'ont pas de relation particulière particulière avec la classe utilisée pour les créer. La manière Python sait quelle classe rechercher des attributs qui ne sont pas trouvés dans l'instance est par l'attribut caché __class__. Que vous pouvez lire pour savoir de quelle classe il s'agit une instance de, comme avec tout autre attribut: c = some_instance.__class__. Vous avez maintenant une variable c liée à une classe, même si elle n'a probablement pas le même nom que la classe. Vous peut l'utiliser pour accéder aux attributs de classe, ou même l'appeler pour en créer plus d'instances (même si vous ne savez pas de quelle classe il s'agit!).

Et vous pouvez même assigner à i.__class__ Pour changer de quelle classe il s'agit! Si vous faites cela, rien de particulier ne se produit immédiatement. Ce n'est pas bouleversant. Tout cela signifie que lorsque vous recherchez des attributs qui n'existent pas dans l'instance, Python ira regarder le nouveau contenu de __class__. Comme cela inclut la plupart des méthodes , et les méthodes s'attendent généralement à ce que l'instance sur laquelle elles opèrent se trouve dans certains états, ce qui entraîne généralement des erreurs si vous le faites au hasard, et c'est très déroutant, mais cela peut être fait. Si vous faites très attention, la chose vous stockez dans __class__ n'a même pas besoin d'être un objet de classe; tout ce que Python va en faire est de rechercher des attributs dans certaines circonstances, donc tout ce dont vous avez besoin est un objet qui a le bon type d'attributs ( quelques mises en garde de côté où Python devient difficile sur les choses étant des classes ou des instances d'une classe particulière).

C'est probablement suffisant pour l'instant. J'espère (si vous avez lu jusqu'ici) que je ne vous ai pas trop confondu. Python est soigné lorsque vous apprenez comment cela fonctionne. :)

105
Ben

Ce que vous appelez une variable "instance" n'est pas en fait une variable d'instance; c'est une variable de classe . Voir le référence de langage sur les classes .

Dans votre exemple, le a semble être une variable d'instance car il est immuable. C'est la nature comme une variable class peut être vue dans le cas où vous affectez un objet mutable:

>>> class Complex:
>>>    a = []
>>>
>>> b = Complex()
>>> c = Complex()
>>> 
>>> # What do they look like?
>>> b.a
[]
>>> c.a
[]
>>> 
>>> # Change b...
>>> b.a.append('Hello')
>>> b.a
['Hello']
>>> # What does c look like?
>>> c.a
['Hello']

Si vous utilisiez self, ce serait une vraie variable d'instance, et donc chaque instance aurait sa propre a unique. La fonction __init__ D'un objet est appelée lorsqu'une nouvelle instance est créée et self est une référence à cette instance.

10
voithos