繁体   English   中英

旧式的班级作品,新式的班级破裂

[英]An old-style class works, new-style class broken

class A_old:
    def __getattr__(self, attr):
        print 'getattr', attr
        return super(A_old, self).__getattr__(attr)  # <-- note: don't do this!
    def __trunc__(self):
        return 3

class A_new(object):
    def __getattr__(self, attr):
        print 'getattr', attr
        return super(A_new, self).__getattr__(attr)
    def __trunc__(self):
        return 3

旧样式的类有效,而新样式的类无效。

>>> range(A_old())
getattr __int__
[0, 1, 2]
>>> range(A_new())
TypeError: range() integer end argument expected, got A_new.

为什么?


注意 :我使用的是2.7以上版本。 在Python 3中,这都不适用。在Python 3中, range被记录为对__index__响应,而老式的类已经__index__了挪威蓝。

老式类实现了另一种方法来测试是否可以转换为数字,如果__int__不存在,则该方法支持使用__trunc__

range() (Python 2) 使用Py_TYPE(arg)->tp_as_number->nb_int()将值转换为整数,该整数大致 (但不完全),类似于使用int() 因此,我们必须在这里查看新旧类的nb_int()插槽。

旧式类nb_int插槽实现instance_int() ,它使用hasattr() (或更确切地说是C等效项 )来测试__int__

if (PyObject_HasAttr((PyObject*)self, int_name))
    return generic_unary_op(self, int_name);

hasattr()吞下所有异常,包括旧类抛出的TypeError

>>> A_old().__int__
getattr __int__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getattr__
TypeError: super() argument 1 must be type, not classobj

因为hasattr()吞下了异常,所以hasattr()返回False

>>> hasattr(A_old(), '__int__')
getattr __int__
False

然后instance_int()的下一行使用__trunc__

truncated = _instance_trunc(self);

当您要求__trunc__时,新型类也永远不会使用nb_int 他们想要__int__或破产。 那是因为它们直接支持插槽。 tp_as_number->nb_int() 直接调用__int__ __getattribute__ (如果有)(完全绕过__getattribute__ )。

请注意,当显式使用int()进行转换时,底层的C代码将显式查找__trunc__属性(仅当没有tp_as_number->nb_int()插槽可用时才使用该属性),但是至少它不会使用hasattr() 这意味着在新样式的类上使用int()仍然有效:

>>> int(A_new())
3

在Python 3中,所有__trunc__使用__trunc__将其视为适当的特殊方法

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM