Jaka jest różnica między klasami starego i nowego stylu w Pythonie?

993

Jaka jest różnica między klasami starego i nowego stylu w Pythonie? Kiedy powinienem użyć jednego lub drugiego?

Tylko czytać
źródło

Odpowiedzi:

559

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 xjest instancją klasy w starym stylu, x.__class__ oznacza klasę x, ale type(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 jak x.__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, objectczy nie, klasy w Pythonie 3 są w nowym stylu.

Projesh Bhoumik
źródło
41
Żadna z tych różnic nie brzmi jak ważny powód do korzystania z klas w nowym stylu, jednak wszyscy mówią, że zawsze powinieneś używać nowego stylu. Jeśli używam pisania kaczego tak, jak powinienem, nigdy nie muszę tego używać 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).
rekursywny
78
Niektóre funkcje 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.
John Doe,
21
@ Użytkownik: Klasy w starym stylu zachowują się tak samo w wersji 2.7, jak w wersji 2.1, a ponieważ niewiele osób pamięta nawet dziwactwa, a dokumentacja nie omawia już większości z nich, są nawet gorsze. Cytat z powyższej dokumentacji mówi wprost: istnieją „poprawki”, których nie można wdrożyć w klasach w starym stylu. O ile nie chcesz wpaść w dziwactwa, z którymi nikt inny nie miał do czynienia od Pythona 2.1 i których dokumentacja już nawet nie wyjaśnia, nie używaj klas w starym stylu.
abarnert
10
Oto przykład dziwactwa, na które możesz natknąć się, jeśli używasz klas starego stylu w wersji 2.7: bugs.python.org/issue21785
KT.
5
Dla każdego, kto zastanawia się, dobrym powodem do jawnego dziedziczenia po obiekcie w Pythonie 3 jest to, że ułatwia on obsługę wielu wersji Pythona.
jpmc26,
308

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.

Mark Harrison
źródło
24
jeśli klasa nowego stylu dziedziczy po innej klasie nowego stylu, to po rozszerzeniu dziedziczy po object.
aaronasterling
2
Czy to niepoprawny przykład starej klasy języka python? class AnotherOldStyleClass: pass
Ankur Agarwal,
11
@abc Wierzę w to class A: passi class A(): passsą ś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 isiis not
eyquem
5
Na marginesie, w wersji 3.X dziedziczenie „obiektu” jest automatycznie zakładane (co oznacza, że ​​nie mamy możliwości, aby nie dziedziczyć „obiektu” w wersji 3.X). Jednak z powodu kompatybilności wstecznej nie jest źle utrzymywać tam „(obiekt)”.
Yo Hsiao
1
Jeśli zamierzamy uzyskać techniczne informacje na temat klas dziedziczonych, ta odpowiedź powinna zauważyć, że tworzysz kolejną klasę w starym stylu, dziedzicząc po klasie w starym stylu. (Jak napisano, ta odpowiedź pozostawia pytanie, czy możesz odziedziczyć klasę w starym stylu. Możesz.)
jpmc26
224

Ważne zmiany zachowania między klasami starego i nowego stylu

  • super dodane
  • Zmieniono MRO (wyjaśniono poniżej)
  • dodano deskryptory
  • obiekty nowej klasy stylu nie mogą zostać podniesione, chyba że pochodzą od Exception(przykład poniżej)
  • __slots__ dodany

Zmieniono MRO (Method Resolution Order)

Został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)

Obiekty klasy nowego stylu nie mogą zostać podniesione, chyba że pochodzą z 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
Ciro Santilli
źródło
8
Ładne, jasne podsumowanie, dzięki. Kiedy mówisz „trudny do wyjaśnienia po angielsku”, myślę, że opisujesz wyszukiwanie w pierwszej kolejności w głębokości w przeciwieństwie do starej klasy, która korzysta z wyszukiwania w pierwszej kolejności w głębokości. (zamówienie przedpremierowe oznacza, że ​​szukamy się przed naszym pierwszym dzieckiem, a zamówienie przedpremierowe oznacza, że ​​szukamy się przed naszym ostatnim dzieckiem).
Steve Carter
40

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ę
xioxox
źródło
5
Interesujące, co zauważyłeś w praktyce, po prostu przeczytałem, że dzieje się tak, ponieważ klasy w nowym stylu, po znalezieniu atrybutu w dykcie instancji, muszą wykonać dodatkowe sprawdzenie, czy jest to opis, tj. Ma pobierz metodę, którą należy wywołać, aby uzyskać wartość do zwrócenia. Klasy starego stylu po prostu zwracają znaleziony obiekt bez dodatkowych obliczeń (ale nie obsługują deskryptorów). Możesz przeczytać więcej w tym znakomitym poście Guido python-history.blogspot.co.uk/2010/06/… , w szczególności w sekcji dotyczącej automatów
xuloChavez
1
nie wydaje się być prawdą z 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
Dla mnie jeszcze szybsze dla aobj w CPython 2.7.2 na x86-64 Linux.
xioxox
41
Prawdopodobnie złym pomysłem jest poleganie na czystym kodzie Python dla aplikacji wrażliwych na wydajność. Nikt nie mówi: „Potrzebuję szybkiego kodu, więc będę używać klas Pythona w starym stylu”. Numpy nie liczy się jako czysty Python.
Phillip Cloud,
również w IPython 2.7.6, to nie jest prawda. '' 477 ns vs. 456 ns na pętlę ''
kmonsoor
37

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 supernowe mro C3 , niektóre magiczne metody itp.

Xiao Hanyu
źródło
24

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:

  1. 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>
    >>>
    
  2. 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>
    >>>
ychaouche
źródło
2
co robi „_names_cache”? Czy możesz podzielić się referencją?
Muatik
4
_names_cacheto 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.
ychaouche
4
Użycie jest nieprawidłowe. Chodzi o to, aby nie budować nowego obiektu, jeśli już istnieje, ale w twoim przypadku __new__()zawsze jest wywoływany i zawsze konstruuje nowy obiekt, a następnie go rzuca. W takim przypadku ifpreferowane jest a .setdefault().
Amit Upadhyay
Ale nie zrozumiałem, dlaczego jest różnica w danych wyjściowych, tj. W klasach starego stylu dwie instancje były różne, dlatego zwrócono wartość False, ale w nowej klasie stylów obie instancje są takie same. W jaki sposób ? Jaka jest zmiana w nowej klasie stylów, która spowodowała, że ​​dwa wystąpienia były takie same, a które nie były w klasie starego stylu?
Pabitra Pati
1
@PabitraPati: To rodzaj taniej demonstracji tutaj. __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ą.
ShadowRanger
10

Klasy nowego stylu dziedziczą objectpo 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.

Aureola
źródło
8

Klasy nowego stylu mogą używać super(Foo, self)gdzie Foojest klasą i selfinstancją.

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.

jamylak
źródło