Variables d'instance et variables de classe en Python

121

J'ai des classes Python, dont je n'ai besoin que d'une seule instance à l'exécution, il serait donc suffisant d'avoir les attributs une seule fois par classe et non par instance. S'il y a plus d'une instance (ce qui ne se produira pas), toutes les instances doivent avoir la même configuration. Je me demande laquelle des options suivantes serait la meilleure ou la plus "idiomatique" Python.

Variables de classe:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Variables d'instance:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
démon
la source
4
Après avoir lu cette question et vu la réponse, l'une de mes premières questions était: "Alors, comment puis-je accéder aux variables de classe?" - c'est parce que jusqu'à présent, je n'ai utilisé que des variables d'instance. En réponse à ma propre question, vous le faites via le nom de classe lui-même, bien que techniquement vous puissiez le faire via une instance aussi. Voici un lien à lire pour toute autre personne ayant la même question: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Réponses:

159

Si vous n'avez de toute façon qu'une seule instance, il est préférable de rendre toutes les variables par instance, simplement parce qu'elles seront accédées (un peu) plus rapidement (un niveau de "recherche" de moins en raison de "l'héritage" d'une classe à l'autre), et il n'y a pas d'inconvénients à peser contre ce petit avantage.

Alex Martelli
la source
7
Vous n'avez jamais entendu parler du motif Borg? Une seule instance n'était pas la bonne manière de l'avoir en premier lieu.
Devin Jeanpierre
435
@Devin, oui, j'ai entendu parler du pattern Borg, puisque c'est moi qui l'ai introduit (en 2001, cfr code.activestate.com/recipes/… ;-). Mais il n'y a rien de mal, dans les cas simples, à avoir simplement une seule instance sans application.
Alex Martelli
2
@ user1767754, facile à créer vous-même avec python -mtimeit- mais je viens de le faire en python3.4 Je note que l'accès à une intvariable de classe est en fait environ 5 à 11 nanosecondes plus rapide que la même variable d'instance sur mon ancien poste de travail - je ne sais pas quoi codepath le rend ainsi.
Alex Martelli
45

Reprenant les conseils de Mike et Alex et ajoutant ma propre couleur ...

L'utilisation d'attributs d'instance est typique ... le Python le plus idiomatique. Les attributs de classe ne sont pas utilisés autant, car leurs cas d'utilisation sont spécifiques. Il en va de même pour les méthodes statiques et de classe par rapport aux méthodes «normales». Ce sont des constructions spéciales traitant des cas d'utilisation spécifiques, sinon c'est du code créé par un programmeur aberrant qui veut montrer qu'il connaît un coin obscur de la programmation Python.

Alex mentionne dans sa réponse que l'accès sera (un peu) plus rapide en raison d'un niveau de recherche en moins ... laissez-moi clarifier davantage pour ceux qui ne savent pas encore comment cela fonctionne. Il est très similaire à l'accès aux variables - dont l'ordre de recherche est:

  1. des locaux
  2. non locaux
  3. globaux
  4. intégrés

Pour l'accès aux attributs, l'ordre est:

  1. exemple
  2. classe
  3. classes de base telles que déterminées par le MRO (ordre de résolution des méthodes)

Les deux techniques fonctionnent de manière «à l'envers», ce qui signifie que les objets les plus locaux sont vérifiés en premier, puis les couches externes sont vérifiées successivement.

Dans votre exemple ci-dessus, disons que vous recherchez l' pathattribut. Lorsqu'il rencontre une référence telle que " self.path", Python examinera d'abord les attributs de l'instance pour une correspondance. Lorsque cela échoue, il vérifie la classe à partir de laquelle l'objet a été instancié. Enfin, il recherchera les classes de base. Comme Alex l'a déclaré, si votre attribut est trouvé dans l'instance, il n'a pas besoin de chercher ailleurs, d'où votre petit gain de temps.

Cependant, si vous insistez sur les attributs de classe, vous avez besoin de cette recherche supplémentaire. Ou , votre autre alternative est de faire référence à l'objet via la classe au lieu de l'instance, par exemple MyController.pathau lieu de self.path. C'est une recherche directe qui contournera la recherche différée, mais comme Alex le mentionne ci-dessous, c'est une variable globale, donc vous perdez ce bit que vous pensiez que vous alliez enregistrer (à moins que vous ne créiez une référence locale au nom de la classe [global] ).

L'essentiel est que vous devez utiliser les attributs d'instance la plupart du temps. Cependant, il y aura des occasions où un attribut de classe est le bon outil pour le travail. Le code utilisant les deux en même temps nécessitera le plus de diligence, car l'utilisation selfne vous donnera que l'objet d'attribut d'instance et l' accès des ombres à l'attribut de classe du même nom. Dans ce cas, vous devez utiliser l'accès à l'attribut par le nom de classe afin de le référencer.

wescpy
la source
@wescpy, mais MyControllerest recherché dans les globaux, donc le coût total est plus élevé que celui self.pathoù se pathtrouve une variable d'instance (puisque selfest local à la méthode == recherche ultra-rapide).
Alex Martelli
ah, c'est vrai. bonne prise. Je suppose que la seule solution de contournement est de créer une référence locale ... à ce stade, cela n'en vaut pas vraiment la peine.
wescpy
24

En cas de doute, vous souhaitez probablement un attribut d'instance.

Les attributs de classe sont mieux réservés aux cas spéciaux où ils ont un sens. Le seul cas d'utilisation très courant concerne les méthodes. Il n'est pas rare d'utiliser des attributs de classe pour les constantes en lecture seule que les instances ont besoin de connaître (bien que le seul avantage à cela soit si vous souhaitez également un accès depuis l' extérieur de la classe), mais vous devez certainement être prudent lorsque vous stockez un état dans ces dernières. , ce qui est rarement ce que vous voulez. Même si vous n'avez qu'une seule instance, vous devez écrire la classe comme vous le feriez pour n'importe quelle autre, ce qui signifie généralement utiliser des attributs d'instance.

Mike Graham
la source
1
Les variables de classe sont une sorte de constantes en lecture seule. Si Python m'avait laissé définir des constantes, je l'aurais écrite sous forme de constantes.
deamon
1
@deamon, je suis légèrement plus susceptible de mettre mes constantes complètement en dehors des définitions de classe et de les nommer en majuscules. Les mettre dans la classe est également très bien. Leur attribuer des attributs d'instance ne nuira à rien, mais peut être un peu étrange. Je ne pense pas que ce soit un problème où la communauté est trop derrière l'une des options.
Mike Graham
@MikeGraham FWIW, le guide de style Python de Google suggère d'éviter les variables globales en faveur des variables de classe. Il y a cependant des exceptions.
Dennis
Voici un nouveau lien vers le guide de style Python de Google . Maintenant, il est simplement écrit: avoid global variableset leur définition est, que les variables globales sont aussi des variables qui sont déclarées comme attributs de classe. Cependant, le propre guide de style de Python ( PEP-8 ) devrait être le premier endroit où aller pour des questions de ce type. Ensuite, votre propre esprit devrait être l'outil de choix (bien sûr, vous pouvez également obtenir des idées de Google, par exemple).
colidyre
4

Même question à Performance d'accès aux variables de classe en Python - le code ici adapté de @Edward Loper

Les variables locales sont les plus rapides d'accès, à peu près liées aux variables de module, suivies des variables de classe, suivies des variables d'instance.

Il y a 4 portées auxquelles vous pouvez accéder aux variables:

  1. Variables d'instance (self.varname)
  2. Variables de classe (nom de classe.nom de variable)
  3. Variables du module (VARNAME)
  4. Variables locales (varname)

Le test:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Le résultat:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
la source