Qual è la differenza tra vecchio stile e nuove classi di stile in Python?


Risposte:


560

Dalle lezioni di nuovo stile e classiche :

Fino a Python 2.1, le classi vecchio stile erano l'unico sapore disponibile per l'utente.

Il concetto di classe (vecchio stile) non è correlato al concetto di tipo: se xè un'istanza di una classe di vecchio stile, allora x.__class__ designa la classe di x, ma lo type(x)è sempre <type 'instance'>.

Ciò riflette il fatto che tutte le istanze vecchio stile, indipendentemente dalla loro classe, sono implementate con un singolo tipo incorporato, chiamato istanza.

Le classi di nuovo stile sono state introdotte in Python 2.2 per unificare i concetti di classe e tipo . Una nuova classe di stile è semplicemente un tipo definito dall'utente, né più né meno.

Se x è un'istanza di una classe di nuovo stile, in type(x)genere è uguale a x.__class__(sebbene ciò non sia garantito - un'istanza di classe di nuovo stile è autorizzata a sovrascrivere il valore restituito x.__class__).

La principale motivazione per l'introduzione di nuove classi di stile è quella di fornire un modello a oggetti unificato con un metamodello completo .

Ha anche una serie di vantaggi immediati, come la possibilità di sottoclassare la maggior parte dei tipi predefiniti o l'introduzione di "descrittori", che abilitano le proprietà calcolate.

Per motivi di compatibilità, le classi sono ancora vecchio stile per impostazione predefinita .

Le classi di nuovo stile vengono create specificando un'altra classe di nuovo stile (cioè un tipo) come classe genitore o l'oggetto "tipo di livello superiore" se non è necessario nessun altro genitore.

Il comportamento delle classi di nuovo stile differisce da quello delle classi di vecchio stile in una serie di dettagli importanti oltre al tipo restituito.

Alcune di queste modifiche sono fondamentali per il nuovo modello a oggetti, come il modo in cui vengono invocati metodi speciali. Altri sono "correzioni" che prima non potevano essere implementate per problemi di compatibilità, come l'ordine di risoluzione del metodo in caso di ereditarietà multipla.

Python 3 ha solo classi di nuovo stile .

Non importa se si esegue la sottoclasse objecto meno, le classi sono di nuovo stile in Python 3.


41
Nessuna di queste differenze suona come ragioni convincenti per usare le lezioni di nuovo stile, ma tutti dicono che dovresti sempre usare il nuovo stile. Se sto usando la tipizzazione anatra come dovrei, non ho mai bisogno di usare type(x). Se non sto eseguendo la sottoclasse di un tipo incorporato, allora non sembra esserci alcun vantaggio che posso vedere delle nuove classi di stile. C'è uno svantaggio, che è la tipizzazione extra di (object).
ricorsivo

78
Alcune funzionalità come super()non funzionano su classi vecchio stile. Per non parlare, come dice quell'articolo, ci sono correzioni fondamentali, come MRO, e metodi speciali, che è più che un buon motivo per usarlo.
John Doe,

21
@Utente: le lezioni di vecchio stile si comportano allo stesso modo in 2.7 come in 2.1 e, poiché poche persone ricordano persino le stranezze e la documentazione non ne discute più la maggior parte, sono anche peggio. La citazione della documentazione sopra dice direttamente questo: ci sono "correzioni" che non potevano essere implementate su classi vecchio stile. A meno che tu non voglia imbatterti in stranezze che nessun altro ha affrontato da Python 2.1 e che la documentazione non spiega più, non usare classi vecchio stile.
Abarnert,

10
Ecco un esempio di una stranezza su cui potresti imbatterti se usi le classi vecchio stile in 2.7: bugs.python.org/issue21785
KT.

5
Per chiunque si chieda, un buon motivo per ereditare esplicitamente dall'oggetto in Python 3 è che semplifica il supporto di più versioni di Python.
jpmc26,

308

Dichiarazione-saggio:

Le classi di nuovo stile ereditano dall'oggetto o da un'altra classe di nuovo stile.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

Le lezioni di vecchio stile no.

class OldStyleClass():
    pass

Python 3 Nota:

Python 3 non supporta le classi di vecchio stile, quindi entrambi i moduli indicati sopra risultano in una nuova classe di stile.


24
se una classe di nuovo stile eredita da un'altra classe di nuovo stile, quindi per estensione, eredita da object.
aaronasterling,

2
È un esempio errato di classe Python vecchio stile? class AnotherOldStyleClass: pass
Ankur Agarwal,

11
@abc Lo credo class A: passe class A(): passsono strettamente equivalenti. Il primo significa "A non eredita alcuna classe genitore" e il secondo significa "A non eredita alcuna classe genitore" . È abbastanza simile a not iseis not
eyquem il

5
Proprio come una nota a margine, per 3.X, l'ereditarietà di "oggetto" viene automaticamente assunta (nel senso che non abbiamo modo di non ereditare "oggetto" in 3.X). Per motivi di compatibilità con le versioni precedenti, non è male mantenere "(oggetto)" lì.
Yo Hsiao,

1
Se avremo informazioni tecniche sulle classi ereditate, questa risposta dovrebbe tenere presente che puoi creare un'altra classe di vecchio stile ereditando da una classe di vecchio stile. (Come scritto, questa risposta lascia all'utente la domanda se è possibile ereditare da una classe di vecchio stile. Puoi.)
jpmc26

224

Cambiamenti importanti nel comportamento tra classi di stile vecchie e nuove

  • super aggiunto
  • MRO è cambiato (spiegato di seguito)
  • descrittori aggiunti
  • i nuovi oggetti della classe di stile non possono essere generati se non derivati ​​da Exception(esempio di seguito)
  • __slots__ aggiunto

MRO (Metodo risoluzione ordine) è cambiato

È stato menzionato in altre risposte, ma ecco un esempio concreto della differenza tra MRO classico e C3 MRO (utilizzato in nuove classi di stile).

La domanda è l'ordine in cui gli attributi (che includono metodi e variabili membro) vengono cercati in eredità multipla.

Le lezioni classiche effettuano una ricerca approfondita da sinistra a destra. Stop al primo incontro. Non hanno l' __mro__attributo.

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

Classi di nuovo stile MRO è più complicato da sintetizzare in una singola frase inglese. È spiegato in dettaglio qui . Una delle sue proprietà è che una classe base viene cercata solo una volta che tutte le sue classi derivate sono state. Hanno l' __mro__attributo che mostra l'ordine di ricerca.

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)

I nuovi oggetti di classe di stile non possono essere generati se non derivati ​​da Exception

Intorno a Python 2.5 potevano essere sollevate molte classi e intorno a Python 2.6 questo veniva rimosso. Su 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

8
Bel riassunto chiaro, grazie. Quando dici "difficile da spiegare in inglese", penso che tu stia descrivendo una ricerca approfondita in profondità del post-ordine in contrapposizione alla classe vecchio stile che usa una ricerca in profondità del preordine. (preordine significa che cerchiamo noi stessi prima del nostro primo figlio e postordine significa che cerchiamo noi stessi dopo il nostro ultimo figlio).
Steve Carter,

40

Le classi di vecchio stile sono ancora leggermente più veloci per la ricerca degli attributi. Questo di solito non è importante, ma può essere utile nel codice Python 2.x sensibile alle prestazioni:

In [3]: classe A:
   ...: def __init __ (self):
   ...: self.a = 'ciao là'
   ...:

In [4]: ​​classe B (oggetto):
   ...: def __init __ (self):
   ...: self.a = 'ciao là'
   ...:

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

In [8]:% timeit aobj.a
10000000 loop, meglio di 3: 78,7 ns per loop

In [10]:% timeit bobj.a
10000000 loop, meglio di 3: 86,9 ns per loop

5
Interessante che tu abbia notato in pratica, ho appena letto che questo è perché le classi di nuovo stile, una volta trovato l'attributo nel dict dell'istanza, devono fare una ricerca aggiuntiva per capire se si tratta di una descrizione, cioè ha un metodo get che deve essere richiamato per ottenere il valore da restituire. Le classi di vecchio stile restituiscono semplicemente l'oggetto trovato senza calcoli di addizione (ma quindi non supportano i descrittori). Puoi leggere di più in questo eccellente post di Guido python-history.blogspot.co.uk/2010/06/… , in particolare la sezione sulle slot
xuloChavez

1
non sembra essere vero con 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
Ancora più veloce per aobj in CPython 2.7.2 su Linux x86-64 per me.
xioxox

41
Probabilmente è una cattiva idea fare affidamento su puro codice Python per applicazioni sensibili alle prestazioni. Nessuno dice: "Ho bisogno di un codice veloce, quindi userò classi Python vecchio stile". Numpy non conta come Python puro.
Phillip Cloud,

anche in IPython 2.7.6, questo non è vero. '' '' 477 ns vs. 456 ns per loop '' ''
kmonsoor

37

Guido ha scritto The Inside Story on New-Style Classes , un articolo davvero eccezionale sul nuovo stile e il vecchio stile in Python.

Python 3 ha solo una classe di nuovo stile. Anche se scrivi una "classe vecchio stile", ne deriva implicitamente object.

Le classi di nuovo stile hanno alcune funzionalità avanzate che mancano nelle classi di vecchio stile, come superil nuovo C3 mro , alcuni metodi magici, ecc.


24

Ecco una differenza molto pratica, vera / falsa. L'unica differenza tra le due versioni del seguente codice è che nella seconda versione Person eredita dall'oggetto . Oltre a ciò, le due versioni sono identiche, ma con risultati diversi:

  1. Classi vecchio stile

    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. Classi di nuovo stile

    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>
    >>>

2
cosa fa '_names_cache'? Potresti condividere un riferimento?
Muatik,

4
_names_cacheè un dizionario che memorizza nella cache (memorizza per il recupero futuro) ogni nome a cui passi Person.__new__. Il metodo setdefault (definito in qualsiasi dizionario) accetta due argomenti: una chiave e un valore. Se la chiave è nel dict, restituirà il suo valore. Se non è nel dict, lo imposterà prima sul valore passato come secondo argomento e quindi lo restituirà.
ychaouche,

4
L'uso è sbagliato. L'idea è di non costruire un nuovo oggetto se esiste già, ma nel tuo caso __new__()viene sempre chiamato e costruisce sempre un nuovo oggetto, quindi lo lancia. In questo caso a ifè preferibile a .setdefault().
Amit Upadhyay, l'

Ma non ho capito perché c'è la differenza nell'output, cioè nella vecchia classe di stile le due istanze erano diverse e quindi ha restituito False, ma nella nuova classe di stile, entrambe le istanze sono uguali. Come ? Qual è il cambiamento nella nuova classe di stile, che ha reso le due istanze uguali, che non era nella vecchia classe di stile?
Pabitra Pati,

1
@PabitraPati: è una specie di dimostrazione economica qui. __new__in realtà non è una cosa per le lezioni di vecchio stile, non viene utilizzato nella costruzione dell'istanza (è solo un nome casuale che sembra speciale, come la definizione __spam__). Quindi la costruzione della classe vecchio stile invoca solo __init__, mentre la costruzione nuovo stile invoca __new__(coalescendo per istanza singleton per nome) per costruire e __init__inizializzarla.
ShadowRanger

10

Le classi di nuovo stile ereditano objecte devono essere scritte come tali in Python 2.2 e successive (ovvero class Classname(object):anziché class Classname:). La modifica principale consiste nell'unificare tipi e classi, e il piacevole effetto collaterale di questo è che ti consente di ereditare da tipi predefiniti.

Leggi descrintro per maggiori dettagli.


8

Nuove classi di stile possono usare super(Foo, self)dov'è Foouna classe ed selfè l'istanza.

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

Restituisce un oggetto proxy che delega le chiamate del metodo a una classe di tipo genitore o fratello. Ciò è utile per accedere a metodi ereditati che sono stati sovrascritti in una classe. L'ordine di ricerca è uguale a quello utilizzato da getattr () tranne per il fatto che il tipo stesso viene ignorato.

E in Python 3.x puoi semplicemente usare super()all'interno di una classe senza parametri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.