Тип Python() или __class__, == или
Я хочу проверить, является ли объект экземпляром класса, и только этот класс (без подклассов). Я мог бы сделать это с помощью:
obj.__class__ == Foo
obj.__class__ is Foo
type(obj) == Foo
type(obj) is Foo
Есть ли причины выбирать один за другим? (различия в производительности, ловушки и т.д.)
Другими словами: а) существует ли какая-либо практическая разница между использованием __class__
и type(x)
? b) являются объектами класса, всегда безопасными для сравнения, используя is
?
Обновление: Спасибо всем за отзыв. Я все еще озадачен тем, являются ли объекты класса одиночными, мой здравый смысл говорит, что это так, но было действительно сложно получить подтверждение (попробуйте googling для "python", "class" и "unique" или "singleton" ).
Я также хотел бы пояснить, что для моих конкретных нужд лучшее "дешевое" решение, которое работает, является лучшим, поскольку я пытаюсь оптимизировать большинство из нескольких специализированных классов (почти достигая точки где разумная задача - сбросить Python и развить этот конкретный модуль на C). Но причина этого вопроса заключалась в том, чтобы лучше понять язык, поскольку некоторые из его функций немного неясны для меня, чтобы легко найти эту информацию. Вот почему я позволяю дискуссиям немного расшириться, а не поселиться за __class__ is
, поэтому я могу услышать мнение более опытных людей. Пока это было очень плодотворно!
Я провел небольшой тест, чтобы сравнить производительность 4 альтернатив. Результаты профилировщика были:
Python PyPy (4x)
type() is 2.138 2.594
__class__ is 2.185 2.437
type() == 2.213 2.625
__class__ == 2.271 2.453
Неудивительно, что is
работает лучше, чем ==
для всех случаев. type()
лучше работает на Python (на 2% быстрее) и __class__
лучше работает в PyPy (на 6% быстрее). Интересно отметить, что __class__ ==
лучше работает в PyPy, чем type() is
.
Обновление 2: многие люди, похоже, не понимают, что я имею в виду, когда "класс является одноэлементным", поэтому я приведу пример:
>>> class Foo(object): pass
...
>>> X = Foo
>>> class Foo(object): pass
...
>>> X == Foo
False
>>> isinstance(X(), Foo)
False
>>> isinstance(Foo(), X)
False
>>> x = type('Foo', (object,), dict())
>>> y = type('Foo', (object,), dict())
>>> x == y
False
>>> isinstance(x(), y)
False
>>> y = copy.copy(x)
>>> x == y
True
>>> x is y
True
>>> isinstance(x(), y)
True
>>> y = copy.deepcopy(x)
>>> x == y
True
>>> x is y
True
>>> isinstance(x(), y)
True
Не имеет значения, есть ли N объектов типа type
, заданных объектом, только один будет его классом, поэтому в этом случае безопасно сравнивать для справки. И поскольку сравнение ссылок всегда будет дешевле сравнения значений, я хотел бы знать, действительно ли мое утверждение выше. Я пришел к выводу, что это так, если только кто-то не докажет доказательства.
Ответы
Ответ 1
Для классов старого стиля существует разница:
>>> class X: pass
...
>>> type(X)
<type 'classobj'>
>>> X.__class__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class X has no attribute '__class__'
>>> x = X()
>>> x.__class__
<class __main__.X at 0x171b5d50>
>>> type(x)
<type 'instance'>
Точкой классов нового стиля было унифицировать класс и тип. Технически говоря, __class__
- единственное решение, которое будет работать как для экземпляров класса new, так и для старого стиля, но также будет генерировать исключение для объектов класса старого стиля. Вы можете вызвать type()
для любого объекта, но не для каждого объекта __class__
. Кроме того, вы можете гадать с __class__
таким образом, что вы не можете гадать с type()
.
>>> class Z(object):
... def __getattribute__(self, name):
... return "ham"
...
>>> z = Z()
>>> z.__class__
'ham'
>>> type(z)
<class '__main__.Z'>
Лично у меня обычно есть среда только с классами нового стиля, и в качестве стиля предпочитают использовать type()
, поскольку я обычно предпочитаю встроенные функции, когда они существуют для использования магических атрибутов. Например, я также предпочел бы bool(x)
на x.__nonzero__()
.
Ответ 2
Результат type()
эквивалентен obj.__class__
в новых классах стилей, а объекты класса небезопасны для сравнения, используя is
, вместо этого используйте ==
.
Для новых классов стиля предпочтительный путь здесь будет type(obj) == Foo
.
Как отметил Майкл Хоффман в своем ответе, между новыми и старыми классами стилей есть разница, поэтому для обратного совместимого кода вам может понадобиться использовать obj.__class__ == Foo
.
Для тех, кто утверждает, что isinstance(obj, Foo)
является предпочтительным, рассмотрим следующий сценарий:
class Foo(object):
pass
class Bar(Foo):
pass
>>> obj = Bar()
>>> isinstance(obj, Foo)
True
>>> type(obj) == Foo
False
OP хочет поведение type(obj) == Foo
, где оно будет ложным, хотя Foo
является базовым классом Bar
.
Ответ 3
is
следует использовать только для проверки личности, а не для проверки типа (существует исключение из правила, в котором вы можете и должны использовать is
для проверки против одиночных чисел).
Примечание. Обычно я бы не использовал type
и ==
для проверок типов. Предпочтительным способом проверки типа является isinstance(obj, Foo)
. Если у вас когда-либо есть причина проверить, что что-то не является экземпляром подкласса, это пахнет мне как рыбный дизайн. Если class Foo(Bar):
, то Bar
- Foo
, и вам следует избегать ситуаций, когда какая-то часть вашего кода должна работать с экземпляром Foo
, но разбивается на Bar
экземпляр.
Ответ 4
Обновление: Спасибо всем за отзыв. Я все еще озадачен тем, являются ли объекты класса одиночными, мой здравый смысл говорит, что это так, но было действительно сложно получить подтверждение (попробуйте googling для "python", "class" и "unique" или "singleton" ).
Я могу подтвердить, что __instance__
является одноэлементным. Вот доказательство.
>>> t1=File.test()
made class
>>> t2=File.test()
made class
>>> print t1.__class__()
made class
<File.test object at 0x1101bdd10>
>>> print t2.__class__()
made class
<File.test object at 0x1101bdd10>
Как вы можете видеть, как t1
и t2
распечатывают одно и то же значение в памяти, даже если t1
и t2
находятся в разных значениях в памяти. Вот доказательство этого.
>>> print t1
<File.test object at 0x1101bdc90>
>>> print t2
<File.test object at 0x1101bdcd0>
Метод __instance__
существует только в том случае, если вы использовали class "name"(object):
. Если вы используете классический класс стиля class "name":
, то метод __instance__
не существует.
То, что это означает, должно быть наиболее общим, которое вы, вероятно, хотите использовать type
, если вы не знаете, что экземпляр факта существует.