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
The old-style class works, but the new-style class doesn't.
>>> range(A_old())
getattr __int__
[0, 1, 2]
>>> range(A_new())
TypeError: range() integer end argument expected, got A_new.
Why?
Note : I'm using 2.7 above. None of this applies in Python 3, where range
is documented as responding to __index__
and old-style classes have gone the way of the Norwegian Blue.
Old-style classes implement a different method to test if you can convert to a number, one that supports using __trunc__
if __int__
doesn't exist.
range()
(Python 2), uses Py_TYPE(arg)->tp_as_number->nb_int()
to convert the value to an integer, which is roughly , but not quite, like using int()
. So we have to look at the nb_int()
slot for both old and new-style classes here.
Old-style classes implement the nb_int
slot as instance_int()
, which uses hasattr()
(or rather, the C equivalent ) to test for __int__
:
if (PyObject_HasAttr((PyObject*)self, int_name))
return generic_unary_op(self, int_name);
hasattr()
swallows all exceptions, including the TypeError
your old-style class throws:
>>> 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
Because hasattr()
swallows the exception, hasattr()
returns False
:
>>> hasattr(A_old(), '__int__')
getattr __int__
False
and the next line in instance_int()
then uses __trunc__
:
truncated = _instance_trunc(self);
New-style classes never also use __trunc__
when you ask for nb_int
; they want __int__
or bust. That's because they support slots directly; tp_as_number->nb_int()
directly calls __int__
if available (bypassing __getattribute__
altogether).
Note that when explicitly using int()
to convert, then the underlying C code will look for a __trunc__
attribute explicitly (using it only if no tp_as_number->nb_int()
slot is available), but at least it won't use hasattr()
for this. This means using int()
on your new-style class still works:
>>> int(A_new())
3
In Python 3, all use of __trunc__
treats it as a proper special method .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.