【问题标题】:Python type() or __class__, == or isPython type() or __class__, == or is
【发布时间】:2012-03-25 13:01:12
【问题描述】:

我想测试一个对象是否是一个类的实例,并且只有这个类(没有子类)。我可以这样做:

obj.__class__ == Foo
obj.__class__ is Foo
type(obj) == Foo
type(obj) is Foo

有理由选择一个而不是另一个吗? (性能差异、陷阱等)

换句话说:a) 使用__class__type(x) 之间有什么实际区别吗? b) 使用is比较类对象总是安全的吗?


更新:感谢大家的反馈。我仍然对类对象是否是单例感到困惑,我的常识说它们是,但是很难得到确认(尝试谷歌搜索“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 类型的对象并不重要,给定一个对象,只有一个是它的类,因此在这种情况下进行比较以供参考是安全的。而且由于参考比较总是比价值比较便宜,我想知道我上面的断言是否成立。我得出的结论是确实如此,除非有人提出相反的证据。

【问题讨论】:

  • 如果你想知道哪个最适合你的使用,只需使用 timeit 模块进行测试即可。
  • @jsbueno 我不是这个意思。当然有很多实例,但只有一个是给定对象的“类”,对吧?换句话说,如果x.__class__ == aa == b 然后a is b(当然是表现良好的__eq__),这就是我要确认/反驳的猜想。
  • 标题是对我大脑语法单元的 DoS 攻击 :)
  • @mgibsonbr if x.__class__ == a and a == b then a is b 看这里-> Are classobjects singleton in python?

标签: python language-features


【解决方案1】:

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 的行为,即使 FooBar 的基类,它也会为假。

【讨论】:

  • 你能举一个类对象相等但不相同的例子吗?
  • @MichaelHoffman:如果你使用元类,你能覆盖类本身的__eq__ 方法吗?我看不出它有什么用,但这是我能想到的唯一例外。
  • @MichaelHoffman - 我不能,但这是一个值比较,不能保证等效值具有相同的标识。在这种情况下,它是一个实现细节,即使有一些实例(这可能是其中之一)实现使得不可能有相等和不相同的值,但这并不安全。
  • @F.J 是的,这正是我的观点。 Python 的style guide 说“与像 None 这样的单例的比较应该始终使用 'is' 或 'is not' 来完成,而不是相等运算符。”好吧,一个类对象就像一个单例,因为另一个类永远不会是一个对象的“那个”类……或者至少我的常识是这样说的。但是,我想找到参考资料来证实(或反驳)这一点。
  • “type() 的结果等同于新样式类中的 obj.__class__”是关于健全代码的合理假设,但 Python 不保证。如果类主体定义包含__class__ = 123,则内置type 返回正确的结果(即,它返回有效控制行为的对象)。也可以用getattribute 做坏事。
【解决方案2】:

is 应该只用于身份检查,而不是类型检查(规则有一个例外,您可以并且应该使用is 来检查单身人士)。

注意:我通常也不会使用 type== 进行类型检查。类型检查的首选方法是isinstance(obj, Foo)。如果您有理由检查某些东西是否不是子类实例,那么它对我来说就像一个可疑的设计。当class Foo(Bar):,然后Bar 是一个 Foo,你应该避免你的代码的某些部分必须在Foo实例上工作但在@987654331上中断的任何情况@ 实例。

【讨论】:

  • 对。如果你不能做到这一点,那么你的设计通常是错误的。
  • 顺便说一句,我同意我的设计不是最好的,但我正在编写一些非常专业的类,需要针对空间进行优化,并且不打算在我的库之外进行子类化。这不是我在一般情况下会做的事情......
  • 如果您正在优化空间,请考虑使用__slots__。谨慎行事! :)
  • @jathanism 非常感谢,甚至没有想到 Python 中存在这样的东西!不能解决我所有的问题,但对于大多数问题来说,它比我所知道的替代方案要好。
  • 我在代码中 99% 的时间都在使用 isinstance,但是在某些地方我使用 type(x) 时,对子类的操作不起作用。例如,假设我有一个开槽类,我知道它只有两个属性,只能是简单对象,但是被复制了很多,我可以节省大量复制覆盖 __deepcopy__ 的时间来简单地创建一个新对象这两个属性(比 copy.py 的代码快),如果它是子类,则使用 copy.py 代码。那么子类化类不需要知道覆盖__deepcopy__本身。
【解决方案3】:

对于老式类,有区别:

>>> 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__ 是唯一适用于新式和旧式类实例的解决方案,但它也会对旧式类对象本身抛出异常。你可以在任何对象上调用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__()

【讨论】:

  • 那会让__class__ 更可取,对吧?因为它适用于新旧样式类。
  • 我已经扩展了我的答案以尽可能地回答这个问题。我不认为有一个通用的答案,但如果你可以确定你不需要在旧式课程上使用它,type() 应该会更好。
【解决方案4】:

更新:感谢大家的反馈。我仍然对类对象是否是单例感到困惑,我的常识说它们是,但是很难得到确认(尝试谷歌搜索“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>

您可以看到t1t2 在内存中打印出相同的值,即使t1t2 在内存中的值不同。这是证明。

>>> print t1
<File.test object at 0x1101bdc90>
>>> print t2
<File.test object at 0x1101bdcd0>

__instance__ 方法仅在您使用 class "name"(object): 时才存在。如果您使用经典样式类class "name":,则__instance__ 方法不存在。

这意味着成为您可能想要使用的最通用的type,除非您知道事实实例确实存在。

【讨论】:

  • 感谢您的回答!关于最后一条评论,只需粘贴整个代码,然后选择它并单击“代码示例”按钮(或者在粘贴之前将代码缩进 4 个空格,以更容易的方式为准)。
  • 这不是“证明”,它只是一个例子。这是另一个 x, y = 7, 7; x is y 将评估 True 但它不能证明整数是单例(它们不是)。
  • __instance__”到底是什么?
  • 值得注意的是:虽然整数通常不是单例(这很明显,因为它们不止一个),但在 cpython 7 中确实是单例,“小”字符串和其他小整数。
猜你喜欢
  • 2021-10-13
  • 2019-04-26
  • 2014-12-08
  • 2018-04-05
  • 1970-01-01
  • 1970-01-01
  • 2022-12-02
  • 2022-01-07
  • 1970-01-01
相关资源
最近更新 更多