Jaka jest różnica między klasami starego i nowego stylu w Pythonie? Kiedy powinienem użyć jednego lub drugiego?
Jaka jest różnica między klasami starego i nowego stylu w Pythonie? Kiedy powinienem użyć jednego lub drugiego?
Odpowiedzi:
Z klas w nowym stylu i klasycznych :
Do Pythona 2.1 klasy w starym stylu były jedynym dostępnym smakiem dla użytkownika.
Pojęcie klasy (w starym stylu) nie jest powiązane z pojęciem typu: jeśli
x
jest instancją klasy w starym stylu,x.__class__
oznacza klasęx
, aletype(x)
zawsze jest<type 'instance'>
.Odzwierciedla to fakt, że wszystkie instancje w starym stylu, niezależnie od ich klasy, są implementowane za pomocą jednego wbudowanego typu, zwanego instancją.
Klasy nowego stylu zostały wprowadzone w Pythonie 2.2, aby ujednolicić pojęcia klasy i typu . Klasa nowego stylu jest po prostu typem zdefiniowanym przez użytkownika, nie więcej, nie mniej.
Jeśli x jest instancją klasy nowego stylu,
type(x)
to zwykle jest takie samo jakx.__class__
(chociaż nie jest to gwarantowane - instancja klasy nowego stylu może zastąpić zwracaną wartośćx.__class__
).Główną motywacją do wprowadzenia klas w nowym stylu jest zapewnienie zunifikowanego modelu obiektowego z pełnym meta-modelem .
Ma również szereg natychmiastowych korzyści, takich jak możliwość podklasowania większości typów wbudowanych lub wprowadzenie „deskryptorów”, które umożliwiają obliczanie właściwości.
Ze względu na kompatybilność klasy są nadal domyślnie w starym stylu .
Klasy nowego stylu są tworzone przez określenie innej klasy nowego stylu (tj. Typu) jako klasy nadrzędnej lub obiektu „typu najwyższego poziomu”, jeśli nie jest potrzebny żaden inny element nadrzędny.
Zachowanie klas w nowym stylu różni się od zachowania klas w starym stylu wieloma ważnymi szczegółami oprócz tego, jaki typ zwraca.
Niektóre z tych zmian są fundamentalne dla nowego modelu obiektowego, na przykład sposób wywoływania specjalnych metod. Inne to „poprawki”, których wcześniej nie można było wdrożyć z powodów związanych ze zgodnością, na przykład kolejność rozwiązywania metod w przypadku wielokrotnego dziedziczenia.
Python 3 ma tylko klasy w nowym stylu .
Bez względu na to, czy podklasujesz,
object
czy nie, klasy w Pythonie 3 są w nowym stylu.
super()
nie działają na klasach w starym stylu. Nie wspominając już o tym, jak mówi ten artykuł, istnieją podstawowe poprawki, takie jak MRO i specjalne metody, które są więcej niż dobrym powodem do korzystania z niego.
Pod względem deklaracji:
Klasy w nowym stylu dziedziczą po obiekcie lub z innej klasy w nowym stylu.
class NewStyleClass(object):
pass
class AnotherNewStyleClass(NewStyleClass):
pass
Zajęcia w starym stylu nie.
class OldStyleClass():
pass
Uwaga do Python 3:
Python 3 nie obsługuje klas starego stylu, więc każda z wymienionych powyżej form powoduje klasę nowego stylu.
object
.
class AnotherOldStyleClass: pass
class A: pass
i class A(): pass
są ściśle równoważne. Pierwszy oznacza „A nie dziedziczy żadnej klasy nadrzędnej”, a drugi oznacza „A dziedziczy brak klasy nadrzędnej” . To dość podobne do not is
iis not
Ważne zmiany zachowania między klasami starego i nowego stylu
Exception
(przykład poniżej)__slots__
dodanyZostało to wspomniane w innych odpowiedziach, ale oto konkretny przykład różnicy między klasycznym MRO a C3 MRO (używanym w klasach nowego stylu).
Pytanie dotyczy kolejności wyszukiwania atrybutów (które obejmują metody i zmienne składowe) w wielokrotnym dziedziczeniu.
Klasy klasyczne przeprowadzają wyszukiwanie od głębokości od lewej do prawej. Zatrzymaj się przy pierwszym meczu. Nie mają __mro__
atrybutu.
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
Klasy w nowym stylu MRO jest bardziej skomplikowane do syntezy w jednym zdaniu w języku angielskim. Jest to szczegółowo wyjaśnione tutaj . Jedną z jego właściwości jest to, że klasa podstawowa jest przeszukiwana dopiero po przeszukaniu wszystkich jej klas pochodnych. Mają __mro__
atrybut, który pokazuje kolejność wyszukiwania.
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
W środowisku Python 2.5 można było stworzyć wiele klas, a w Pythonie 2.6 zostało to usunięte. W Pythonie 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
Klasy starego stylu są wciąż nieznacznie szybsze w wyszukiwaniu atrybutów. Nie jest to zwykle ważne, ale może być przydatne w wrażliwym na wydajność kodzie Python 2.x:
W [3]: klasa A: ...: def __init __ (self): ...: self.a = 'cześć tam' ...: W [4]: klasa B (obiekt): ...: def __init __ (self): ...: self.a = 'cześć tam' ...: W [6]: aobj = A () W [7]: bobj = B () W [8]:% timeit aobj.a 10000000 pętli, najlepiej 3: 78,7 ns na pętlę W [10]:% timeit bobj.a 10000000 pętli, najlepiej 3: 86,9 ns na pętlę
%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 napisał „ The Story Story on New-Class Classes” , naprawdę świetny artykuł o nowej i starej klasie w Pythonie.
Python 3 ma tylko klasę w nowym stylu. Nawet jeśli napiszesz „klasę w starym stylu”, jest ona domyślnie wywodzona z object
.
Klasy w nowym stylu mają pewne zaawansowane funkcje, których brakuje w klasach w starym stylu, takie jak super
nowe mro C3 , niektóre magiczne metody itp.
Oto bardzo praktyczna, prawdziwa / fałszywa różnica. Jedyną różnicą między dwiema wersjami następującego kodu jest to, że w drugiej wersji Person dziedziczy po obiekcie . Poza tym dwie wersje są identyczne, ale z różnymi wynikami:
Zajęcia w starym stylu
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>
>>>
Zajęcia w nowym stylu
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
to słownik, który buforuje (przechowuje do przyszłego wyszukiwania) każdą przekazywaną nazwę Person.__new__
. Metoda setdefault (zdefiniowana w dowolnym słowniku) przyjmuje dwa argumenty: klucz i wartość. Jeśli klucz znajduje się w nagraniu, zwróci jego wartość. Jeśli nie ma go w dyktonie, najpierw ustawi wartość przekazaną jako drugi argument, a następnie zwróci.
__new__()
zawsze jest wywoływany i zawsze konstruuje nowy obiekt, a następnie go rzuca. W takim przypadku if
preferowane jest a .setdefault()
.
__new__
tak naprawdę nie jest rzeczą dla klas w starym stylu, nie przyzwyczaja się do budowy instancji (to po prostu losowa nazwa, która wygląda wyjątkowo, jak definiowanie __spam__
). Tak więc konstruowanie klasy w starym stylu wywołuje tylko __init__
, podczas gdy konstrukcja w nowym stylu wywołuje __new__
(łączenie się w instancję singletona według nazwy), aby skonstruować i __init__
zainicjować ją.
Klasy nowego stylu dziedziczą object
po Pythonie 2.2 i muszą być zapisane jako takie (tj. class Classname(object):
Zamiast class Classname:
). Podstawową zmianą jest ujednolicenie typów i klas, a przyjemnym efektem ubocznym jest to, że pozwala ci dziedziczyć po typach wbudowanych.
Przeczytaj descrintro po więcej szczegółów.
Klasy nowego stylu mogą używać super(Foo, self)
gdzie Foo
jest klasą i self
instancją.
super(type[, object-or-type])
Zwraca obiekt proxy, który deleguje wywołania metod do nadrzędnej lub siostrzanej klasy typu. Jest to przydatne do uzyskiwania dostępu do odziedziczonych metod, które zostały zastąpione w klasie. Kolejność wyszukiwania jest taka sama, jak używana przez getattr (), tyle że sam typ jest pomijany.
W Pythonie 3.x można po prostu używać super()
wewnątrz klasy bez żadnych parametrów.
type(x)
. Jeśli nie podklasuję typu wbudowanego, wydaje mi się, że nie widzę żadnej korzyści z klas nowego stylu. Wadą jest dodatkowe pisanie(object)
.