Qual è la differenza tra vecchio stile e nuove classi di stile in Python? Quando dovrei usare l'uno o l'altro?
Qual è la differenza tra vecchio stile e nuove classi di stile in Python? Quando dovrei usare l'uno o l'altro?
Risposte:
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, allorax.__class__
designa la classe dix
, ma lotype(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 ax.__class__
(sebbene ciò non sia garantito - un'istanza di classe di nuovo stile è autorizzata a sovrascrivere il valore restituitox.__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
object
o meno, le classi sono di nuovo stile in Python 3.
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.
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.
object
.
class AnotherOldStyleClass: pass
class A: pass
e class A(): pass
sono 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 is
eis not
Cambiamenti importanti nel comportamento tra classi di stile vecchie e nuove
Exception
(esempio di seguito)__slots__
aggiuntoÈ 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)
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
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
%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
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 super
il nuovo C3 mro , alcuni metodi magici, ecc.
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:
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>
>>>
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>
>>>
_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à.
__new__()
viene sempre chiamato e costruisce sempre un nuovo oggetto, quindi lo lancia. In questo caso a if
è preferibile a .setdefault()
.
__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.
Le classi di nuovo stile ereditano object
e 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.
Nuove classi di stile possono usare super(Foo, self)
dov'è Foo
una 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.
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)
.