В чем разница между старым и новым стилем классов в Python?

994

В чем разница между старым и новым стилем классов в Python? Когда я должен использовать один или другой?

Readonly
источник

Ответы:

560

Из новых и классических классов :

До Python 2.1 классы старого стиля были единственным вариантом, доступным пользователю.

Понятие класса (старого стиля) не связано с понятием типа: если xявляется экземпляром класса старого стиля, то x.__class__ обозначает класс x, но type(x)всегда<type 'instance'> .

Это отражает тот факт, что все экземпляры старого стиля, независимо от их класса, реализованы с помощью одного встроенного типа, называемого экземпляром.

Классы нового стиля были введены в Python 2.2, чтобы объединить понятия класса и типа . Класс нового стиля - это просто определенный пользователем тип, не больше, не меньше.

Если x является экземпляром класса нового стиля, то type(x)обычно он такой же x.__class__(хотя это не гарантировано - экземпляру класса нового стиля разрешено переопределять значение, возвращаемое для x.__class__).

Основной мотивацией для введения классов нового стиля является предоставление единой объектной модели с полной метамоделью .

Он также имеет ряд непосредственных преимуществ, таких как возможность создавать подклассы для большинства встроенных типов или введение «дескрипторов», которые включают вычисляемые свойства.

По причинам совместимости классы по-прежнему в старом стиле .

Классы нового стиля создаются путем указания другого класса нового стиля (т. Е. Типа) в качестве родительского класса или объекта «тип верхнего уровня», если нет другого родителя.

Поведение классов нового стиля отличается от поведения классов старого стиля в ряде важных деталей в дополнение к тому, что возвращает тип.

Некоторые из этих изменений имеют фундаментальное значение для новой объектной модели, например, для вызова специальных методов. Другие являются «исправлениями», которые не могли быть реализованы ранее из-за проблем совместимости, например, порядок разрешения методов в случае множественного наследования.

В Python 3 есть только классы нового стиля .

Независимо от того, являетесь ли вы подклассом из objectили нет, классы являются новым стилем в Python 3.

Projesh Bhoumik
источник
41
Ни одно из этих различий не звучит как веские причины для использования классов в новом стиле, но все говорят, что вы всегда должны использовать новый стиль. Если я использую утку, как я должен, мне никогда не понадобится type(x). Если я не делю на подклассы встроенный тип, то, похоже, я не вижу никакого преимущества в классах нового стиля. Есть недостаток, который заключается в дополнительном наборе текста (object).
рекурсивный
78
Некоторые функции, такие как super()не работают на классах старого стиля. Не говоря уже о том, что, как говорится в этой статье, существуют фундаментальные исправления, такие как MRO, и специальные методы, что является более чем веским основанием для его использования.
Джон Доу
21
@User: Классы старого стиля в 2.7 ведут себя так же, как и в 2.1 - и, поскольку мало кто даже помнит причуды, а документация больше не обсуждает большинство из них, они еще хуже. Приведенная выше цитата из документации прямо говорит об этом: есть «исправления», которые невозможно реализовать в классах старого стиля. Если вы не хотите сталкиваться с причудами, с которыми никто не сталкивался после Python 2.1, и которые документация больше не объясняет, не используйте классы старого стиля.
abarnert
10
Вот пример причуды, на которую вы можете наткнуться, если вы используете классы старого стиля в 2.7: bugs.python.org/issue21785
KT.
5
Для всех, кто интересуется, хорошей причиной явного наследования от объекта в Python 3 является то, что он облегчает поддержку нескольких версий Python.
jpmc26
308

Декларация-накрест:

Классы нового стиля наследуются от объекта или другого класса нового стиля.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

Классы старого стиля не делают.

class OldStyleClass():
    pass

Python 3 Примечание:

Python 3 не поддерживает классы старого стиля, поэтому любая из указанных выше форм приводит к классу нового стиля.

Марк Харрисон
источник
24
если класс нового стиля наследуется от другого класса нового стиля, то по расширению он наследуется от object.
Ааронастерлинг
2
Это неправильный пример класса Python старого стиля? class AnotherOldStyleClass: pass
Анкур Агарвал
11
@abc Я считаю , что class A: passи class A(): passстрого эквивалентны. Первый означает «А не наследует ни один родительский класс», а второй означает «А не наследует ни один родительский класс» . Это очень похоже на not isиis not
eyquem
5
Как примечание, для 3.X наследование «объекта» автоматически принимается (это означает, что у нас нет способа не наследовать «объект» в 3.X). По причине обратной совместимости неплохо держать там "(объект)".
Йо Сяо
1
Если мы собираемся получить техническую информацию о унаследованных классах, этот ответ должен учитывать, что вы создаете другой класс старого стиля, унаследовав его от класса старого стиля. (Как написано, этот ответ оставляет пользователю вопрос о том, можете ли вы наследовать класс старого стиля. Вы можете.)
jpmc26
224

Важные изменения поведения между старыми и новыми классами стиля

  • супер добавил
  • MRO изменено (объяснено ниже)
  • добавлены дескрипторы
  • Объекты нового стиля не могут быть созданы, если они не получены из Exception(пример ниже)
  • __slots__ добавленной

MRO (порядок разрешения методов) изменен

Это было упомянуто в других ответах, но здесь приведен конкретный пример различия между классическим MRO и C3 MRO (используется в новых классах стиля).

Вопрос заключается в порядке поиска атрибутов (которые включают методы и переменные-члены) в множественном наследовании.

Классические занятия выполняют поиск в глубину слева направо. Остановись на первом матче. У них нет __mro__атрибута.

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

Классы нового стиля MRO сложнее синтезировать в одном английском предложении. Это подробно объясняется здесь . Одним из его свойств является то, что базовый класс ищется только после того, как все его производные классы были. У них есть __mro__атрибут, который показывает порядок поиска.

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

В Python 2.5 можно было создать много классов, а в Python 2.6 это было удалено. На 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 郝海东 冠状 病 六四 事件 法轮功
источник
8
Хорошее четкое резюме, спасибо. Когда вы говорите «трудно объяснить по-английски», я думаю, что вы описываете поиск по глубине в порядке почтового заказа, в отличие от класса старого стиля, который использует предварительный поиск по глубине. (предзаказ означает, что мы ищем себя до нашего первого ребенка, а после заказа означает, что мы ищем себя после нашего последнего ребенка).
Стив Картер
40

Классы старого стиля все еще немного быстрее для поиска атрибутов. Это обычно не важно, но может быть полезно в чувствительном к производительности коде Python 2.x:

В [3]: класс А:
   ...: def __init __ (self):
   ...: self.a = "привет"
   ...:

В [4]: ​​класс B (объект):
   ...: def __init __ (self):
   ...: self.a = "привет"
   ...:

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

В [8]:% timeit aobj.a
10000000 циклов, лучшее из 3: 78,7 нс на цикл

В [10]:% timeit bobj.a
10000000 циклов, лучшее из 3: 86,9 нс на цикл
xioxox
источник
5
Интересно, что вы заметили на практике, я просто прочитал, что это потому, что классы нового стиля, после того как они нашли атрибут в экземпляре dict, должны выполнить дополнительный поиск, чтобы выяснить, является ли это описанием, то есть имеет Метод get, который необходимо вызвать, чтобы получить возвращаемое значение. Классы старого стиля просто возвращают найденный объект без дополнительных вычислений (но затем не поддерживают дескрипторы). Вы можете прочитать больше в этом прекрасном посте Гвидо python-history.blogspot.co.uk/2010/06/… , в частности, раздел о слотах
xuloChavez
1
не похоже на правду с 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
Бенедикт Вальдвогель
1
Еще быстрее для aobj в CPython 2.7.2 на x86-64 Linux для меня.
xioxox
41
Вероятно, плохая идея полагаться на чистый код Python для чувствительных к производительности приложений. Никто не говорит: «Мне нужен быстрый код, поэтому я буду использовать классы Python старого стиля». Numpy не считается чистым Python.
Филлип Облако
также в IPython 2.7.6 это не так. '' '' 477 нс против 456 нс за цикл '' ''
kmonsoor
37

Гвидо написал «Внутреннюю историю о классах нового стиля» , действительно отличную статью о классах нового и старого стиля в Python.

Python 3 имеет только новый класс стиля. Даже если вы пишете «класс старого стиля», он неявно наследуется от object.

У классов нового стиля есть некоторые расширенные функции, которых нет в классах старого стиля, такие как superновый C3 mro , некоторые магические методы и т. Д.

Сяо Ханьюй
источник
24

Вот очень практичная, истинная / ложная разница. Единственная разница между двумя версиями следующего кода заключается в том, что во второй версии Person наследуется от объекта . Кроме этого, две версии идентичны, но с разными результатами:

  1. Уроки старого стиля

    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. Уроки нового стиля

    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
источник
2
что делает _names_cache? Не могли бы вы поделиться ссылкой?
Муатик
4
_names_cacheэто словарь, который кэширует (сохраняет для будущего поиска) каждое передаваемое вами имя Person.__new__. Метод setdefault (определенный в любом словаре) принимает два аргумента: ключ и значение. Если ключ находится в dict, он вернет свое значение. Если его нет в dict, он сначала установит для него значение, переданное в качестве второго аргумента, а затем вернет его.
ychaouche
4
Использование неверно. Идея состоит в том, чтобы не создавать новый объект, если он уже существует, но в вашем случае __new__()он всегда вызывается, и он всегда создает новый объект, а затем выбрасывает его. В этом случае ifпредпочтительнее, чем .setdefault().
Амит Упадхяй,
Но я не понял, почему разница в выводе, т. Е. В старом классе стиля два экземпляра были разными, поэтому возвращалось значение False, но в новом классе стиля оба экземпляра одинаковы. Как ? Что изменилось в новом классе стиля, который сделал два экземпляра одинаковыми, чего не было в старом классе стиля?
Пабитра Пати
1
@PabitraPati: Это своего рода дешевая демонстрация здесь. __new__на самом деле это не вещь для классов старого стиля, она не используется в конструкции экземпляра (это просто случайное имя, которое выглядит особенным, как определение __spam__). Таким образом, создание класса старого стиля только вызывает __init__, в то время как конструкция нового стиля вызывает __new__(объединение с единичным экземпляром по имени), чтобы создать и __init__инициализировать его.
ShadowRanger
10

Классы нового стиля наследуются objectи должны быть написаны как таковые в Python 2.2 и более поздних версиях (т.е. class Classname(object):вместо class Classname:). Основное изменение заключается в унификации типов и классов, и приятным побочным эффектом этого является то, что он позволяет вам наследовать от встроенных типов.

Читать descrintro для более подробной информации.

Гало
источник
8

Новые классы стилей могут использовать super(Foo, self)где Fooкласс и selfэкземпляр.

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

Возвратите прокси-объект, который делегирует вызовы метода родительскому или родственному классу типа. Это полезно для доступа к унаследованным методам, которые были переопределены в классе. Порядок поиска такой же, как в getattr (), за исключением того, что сам тип пропускается.

А в Python 3.x вы можете просто использовать super()внутри класса без каких-либо параметров.

jamylak
источник