Quelle est la différence entre les classes de style ancien et nouveau en Python?

994

Quelle est la différence entre les classes de style ancien et nouveau en Python? Quand dois-je utiliser l'un ou l'autre?

Lecture seulement
la source

Réponses:

560

Des classes New-style et classiques :

Jusqu'à Python 2.1, les classes à l'ancienne étaient la seule version disponible pour l'utilisateur.

Le concept de classe (à l'ancienne) n'est pas lié au concept de type: si xest une instance d'une classe à l'ancienne, il x.__class__ désigne alors la classe de x, mais l' type(x)est toujours <type 'instance'>.

Cela reflète le fait que toutes les instances de style ancien, indépendamment de leur classe, sont implémentées avec un seul type intégré, appelé instance.

Des classes de nouveau style ont été introduites dans Python 2.2 pour unifier les concepts de classe et de type . Une classe de nouveau style est simplement un type défini par l'utilisateur, ni plus, ni moins.

Si x est une instance d'une classe de nouveau style, alors type(x)est généralement le même que x.__class__(bien que cela ne soit pas garanti - une instance de classe de nouveau style est autorisée à remplacer la valeur renvoyée pour x.__class__).

La principale motivation pour introduire des classes de nouveau style est de fournir un modèle objet unifié avec un méta-modèle complet .

Il présente également un certain nombre d'avantages immédiats, comme la possibilité de sous-classer la plupart des types intégrés ou l'introduction de «descripteurs», qui permettent des propriétés calculées.

Pour des raisons de compatibilité, les classes sont toujours à l'ancienne par défaut .

Les classes de nouveau style sont créées en spécifiant une autre classe de nouveau style (c'est-à-dire un type) comme classe parente, ou l'objet "type de niveau supérieur" si aucun autre parent n'est nécessaire.

Le comportement des classes de style nouveau diffère de celui des classes de style ancien par un certain nombre de détails importants en plus du type renvoyé.

Certaines de ces modifications sont fondamentales pour le nouveau modèle d'objet, comme la façon dont les méthodes spéciales sont appelées. D'autres sont des "correctifs" qui ne pouvaient pas être implémentés auparavant pour des raisons de compatibilité, comme l'ordre de résolution des méthodes en cas d'héritage multiple.

Python 3 n'a que des classes de nouveau style .

Peu importe si vous sous-classe objectou non, les classes sont de nouveau style en Python 3.

Projesh Bhoumik
la source
41
Aucune de ces différences ne semble être une raison impérieuse d'utiliser des classes de nouveau style, mais tout le monde dit que vous devriez toujours utiliser le nouveau style. Si j'utilise le typage canard comme je le devrais, je n'ai jamais besoin de l'utiliser type(x). Si je ne sous-classe pas un type intégré, il ne semble y avoir aucun avantage que je puisse voir des classes de nouveau style. Il y a un inconvénient, qui est le typage supplémentaire de (object).
récursif
78
Certaines fonctionnalités comme super()ne fonctionnent pas sur les classes à l'ancienne. Sans oublier, comme le dit cet article, qu'il existe des correctifs fondamentaux, comme MRO, et des méthodes spéciales, ce qui est plus qu'une bonne raison de l'utiliser.
John Doe
21
@User: les classes à l'ancienne se comportent de la même manière en 2.7 qu'en 2.1 - et, parce que peu de gens se souviennent même des bizarreries et que la documentation ne traite plus de la plupart d'entre elles, elles sont encore pires. La citation de documentation ci-dessus dit directement ceci: il existe des "correctifs" qui ne peuvent pas être implémentés sur des classes à l'ancienne. À moins que vous ne vouliez rencontrer des bizarreries que personne d'autre n'a traitées depuis Python 2.1 et que la documentation n'explique même plus, n'utilisez pas de classes à l'ancienne.
abarnert
10
Voici un exemple d'une bizarrerie sur laquelle vous pouvez tomber si vous utilisez des classes à l'ancienne dans 2.7: bugs.python.org/issue21785
KT.
5
Pour quiconque se demande, une bonne raison d'hériter explicitement d'un objet dans Python 3 est qu'il facilite la prise en charge de plusieurs versions de Python.
jpmc26
308

Côté déclaration:

Les classes de nouveau style héritent de l' objet ou d'une autre classe de nouveau style.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

Les classes à l'ancienne ne le font pas.

class OldStyleClass():
    pass

Python 3 Remarque:

Python 3 ne prend pas en charge les anciennes classes de style, donc l'une ou l'autre forme notée ci-dessus donne une classe de nouveau style.

Mark Harrison
la source
24
si une classe de nouveau style hérite d'une autre classe de nouveau style, alors par extension, elle hérite de object.
aaronasterling
2
Est-ce un exemple incorrect de classe python à l'ancienne? class AnotherOldStyleClass: pass
Ankur Agarwal
11
@abc Je crois que class A: passet class A(): passsont strictement équivalents. Le premier signifie "A n'hérite d'aucune classe parent" et le second signifie "A n'hérite d'aucune classe parent" . C'est assez similaire à not isetis not
eyquem
5
Tout comme une remarque, pour 3.X, l'héritage de "objet" est automatiquement supposé (ce qui signifie que nous n'avons aucun moyen de ne pas hériter "objet" dans 3.X). Pour des raisons de compatibilité ascendante, il n'est pas mauvais de garder "(objet)" cependant.
Yo Hsiao
1
Si nous voulons obtenir des informations techniques sur les classes héritées, cette réponse doit noter que vous pouvez créer une autre classe à l'ancienne en héritant d'une classe à l'ancienne. (Comme écrit, cette réponse laisse l'utilisateur se demander si vous pouvez hériter d'une classe à l'ancienne. Vous le pouvez.)
jpmc26
224

Changements de comportement importants entre les anciennes et les nouvelles classes de style

  • super ajouté
  • MRO modifié (expliqué ci-dessous)
  • descripteurs ajoutés
  • les nouveaux objets de classe de style ne peuvent pas être levés sauf s'ils sont dérivés de Exception(exemple ci-dessous)
  • __slots__ ajoutée

MRO (Ordonnance de résolution de méthode) modifié

Il a été mentionné dans d'autres réponses, mais voici un exemple concret de la différence entre le MRO classique et le C3 MRO (utilisé dans les nouvelles classes de style).

La question est l'ordre dans lequel les attributs (qui incluent les méthodes et les variables membres) sont recherchés dans l'héritage multiple.

Les classes classiques effectuent une recherche approfondie de gauche à droite. Arrêtez-vous au premier match. Ils n'ont pas d' __mro__attribut.

class C: i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 0
assert C21().i == 2

try:
    C12.__mro__
except AttributeError:
    pass
else:
    assert False

Les classes de nouveau style MRO sont plus compliquées à synthétiser en une seule phrase anglaise. Il est expliqué en détail ici . L'une de ses propriétés est qu'une classe de base n'est recherchée qu'une fois que toutes ses classes dérivées l'ont été. Ils ont l' __mro__attribut qui montre l'ordre de recherche.

class C(object): i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 2
assert C21().i == 2

assert C12.__mro__ == (C12, C1, C2, C, object)
assert C21.__mro__ == (C21, C2, C1, C, object)

Les nouveaux objets de classe de style ne peuvent pas être levés sauf s'ils sont dérivés de Exception

Autour de Python 2.5, de nombreuses classes pouvaient être augmentées, et autour de Python 2.6, cela a été supprimé. Sur Python 2.7.3:

# OK, old:
class Old: pass
try:
    raise Old()
except Old:
    pass
else:
    assert False

# TypeError, new not derived from `Exception`.
class New(object): pass
try:
    raise New()
except TypeError:
    pass
else:
    assert False

# OK, derived from `Exception`.
class New(Exception): pass
try:
    raise New()
except New:
    pass
else:
    assert False

# `'str'` is a new style object, so you can't raise it:
try:
    raise 'str'
except TypeError:
    pass
else:
    assert False
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
8
Beau résumé clair, merci. Lorsque vous dites "difficile à expliquer en anglais", je pense que vous décrivez une recherche en profondeur en premier ordre par opposition à la classe à l'ancienne qui utilise une recherche en profondeur en premier ordre. (précommande signifie que nous nous recherchons avant notre premier enfant et post-commande signifie que nous nous recherchons après notre dernier enfant).
Steve Carter
40

Les anciennes classes de style sont encore légèrement plus rapides pour la recherche d'attributs. Ce n'est généralement pas important, mais il peut être utile dans le code Python 2.x sensible aux performances:

Dans [3]: classe A:
   ...: def __init __ (auto):
   ...: self.a = 'salut là'
   ...:

Dans [4]: ​​classe B (objet):
   ...: def __init __ (auto):
   ...: self.a = 'salut là'
   ...:

Dans [6]: aobj = A ()
Dans [7]: bobj = B ()

Dans [8]:% timeit aobj.a
10000000 boucles, meilleur de 3: 78,7 ns par boucle

Dans [10]:% timeit bobj.a
10000000 boucles, meilleur de 3: 86,9 ns par boucle
xioxox
la source
5
Intéressant que vous ayez remarqué dans la pratique, je viens de lire que c'est parce que les classes de nouveau style, une fois qu'elles ont trouvé l'attribut dans le dict d'instance, doivent faire une recherche supplémentaire pour déterminer s'il s'agit d'une description, c'est-à-dire qu'elle a un get, méthode qui doit être invoquée pour obtenir la valeur à renvoyer. Les classes de style ancien renvoient simplement l'objet trouvé sans calculs supplémentaires (mais ne prennent pas en charge les descripteurs). Vous pouvez en lire plus dans cet excellent article de Guido python-history.blogspot.co.uk/2010/06/… , en particulier la section sur les machines
xuloChavez
1
ne semble pas être vrai avec CPython 2.7.2:%timeit aobj.a 10000000 loops, best of 3: 66.1 ns per loop %timeit bobj.a 10000000 loops, best of 3: 53.9 ns per loop
Benedikt Waldvogel
1
Encore plus rapide pour aobj dans CPython 2.7.2 sur Linux x86-64 pour moi.
xioxox
41
C'est probablement une mauvaise idée de s'appuyer sur du code Python pur pour les applications sensibles aux performances. Personne ne dit: "J'ai besoin de code rapide, je vais donc utiliser des classes Python à l'ancienne." Numpy ne compte pas comme pur Python.
Phillip Cloud
également dans IPython 2.7.6, ce n'est pas vrai. '' '' 477 ns contre 456 ns par boucle '' ''
kmonsoor
37

Guido a écrit The Inside Story on New-Style Classes , un très bon article sur les classes de style nouveau et ancien en Python.

Python 3 n'a qu'une nouvelle classe de style. Même si vous écrivez une «classe à l'ancienne», elle est implicitement dérivée de object.

Les classes de nouveau style ont des fonctionnalités avancées qui font défaut aux classes de style ancien, telles que superla nouvelle C3 mro , certaines méthodes magiques, etc.

Xiao Hanyu
la source
24

Voici une différence très pratique, vraie / fausse. La seule différence entre les deux versions du code suivant est que dans la deuxième version, Person hérite de object . En dehors de cela, les deux versions sont identiques, mais avec des résultats différents:

  1. Cours à l'ancienne

    class Person():
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed1 is ahmed2
    print ahmed1
    print ahmed2
    
    
    >>> False
    <__main__.Person instance at 0xb74acf8c>
    <__main__.Person instance at 0xb74ac6cc>
    >>>
    
  2. Classes de nouveau style

    class Person(object):
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed2 is ahmed1
    print ahmed1
    print ahmed2
    
    >>> True
    <__main__.Person object at 0xb74ac66c>
    <__main__.Person object at 0xb74ac66c>
    >>>
ychaouche
la source
2
que fait «_names_cache»? Pourriez-vous partager une référence?
Muatik
4
_names_cacheest un dictionnaire qui met en cache (stocke pour une récupération future) chaque nom que vous passez Person.__new__. La méthode setdefault (définie dans n'importe quel dictionnaire) prend deux arguments: une clé et une valeur. Si la clé est dans le dict, elle renverra sa valeur. Si ce n'est pas dans le dict, il le mettra d'abord à la valeur passée comme deuxième argument, puis le renverra.
ychaouche
4
L'utilisation est mauvaise. L'idée est de ne pas construire un nouvel objet s'il existe déjà, mais dans votre cas __new__()est toujours appelé, et il construit toujours un nouvel objet, puis le lance. Dans ce cas, a ifest préférable à .setdefault().
Amit Upadhyay
Mais, je n'ai pas compris pourquoi la différence dans la sortie, c'est-à-dire que dans l'ancienne classe de style, les deux instances étaient différentes, est donc retournée False, mais dans la nouvelle classe de style, les deux instances sont identiques. Comment ? Quel est le changement dans la nouvelle classe de style, qui a rendu les deux instances identiques, qui n'était pas dans l'ancienne classe de style?
Pabitra Pati
1
@PabitraPati: C'est une sorte de démonstration bon marché ici. __new__n'est pas vraiment une chose pour les classes à l'ancienne, il n'est pas utilisé dans la construction d'instance (c'est juste un nom aléatoire qui a l'air spécial, comme définir __spam__). Ainsi, la construction de la classe à l'ancienne ne fait qu'appeler __init__, tandis que la construction à nouveau style invoque __new__(fusionnant à une instance singleton par son nom) pour la construire et l' __init__initialiser.
ShadowRanger
10

Les classes de nouveau style héritent de objectet doivent être écrites comme telles dans Python 2.2 (c'est- class Classname(object):à- dire au lieu de class Classname:). Le principal changement consiste à unifier les types et les classes, et le bel effet secondaire de cela est qu'il vous permet d'hériter des types intégrés.

Lisez descrintro pour plus de détails.

Halo
la source
8

Les nouvelles classes de style peuvent utiliser super(Foo, self)Fooest une classe et selfest l'instance.

super(type[, object-or-type])

Renvoie un objet proxy qui délègue les appels de méthode à une classe parent ou frère de type. Ceci est utile pour accéder aux méthodes héritées qui ont été remplacées dans une classe. L'ordre de recherche est le même que celui utilisé par getattr () sauf que le type lui-même est ignoré.

Et dans Python 3.x, vous pouvez simplement utiliser à l' super()intérieur d'une classe sans aucun paramètre.

jamylak
la source