我正在尝试创建一个自定义对象,将所有不存在的方法调用传递给成员属性。这在普通的自定义方法调用下有效,但在尝试调用算术运算符时失败。
下面是一个示例类的控制台片段,一个测试函数以及一个清理过的测试函数反汇编。
>>> class NoAdd(object):
... member = 0
... def __getattr__(self, item):
... print('NoAdd __getattr__')
... # return getattr(self.member, item)
... if item == '__add__':
... return self.member.__add__
>>> def test():
... print('Call __add__ directly.')
... NoAdd().__add__(5) # 5
... print('Implicit __add__.')
... NoAdd() + 5 # TypeError
>>> dis(test)
3 8 LOAD_GLOBAL 1 (NoAdd)
10 CALL_FUNCTION 0
12 LOAD_ATTR 2 (__add__)
14 LOAD_CONST 2 (5)
16 CALL_FUNCTION 1
18 POP_TOP
5 28 LOAD_GLOBAL 1 (NoAdd)
30 CALL_FUNCTION 0
32 LOAD_CONST 2 (5)
34 BINARY_ADD
36 POP_TOP
38 LOAD_CONST 0 (None)
40 RETURN_VALUE
>>> test()
Call __add__ directly.
NoAdd __getattr__
Implicit __add__.
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 5, in test
TypeError: unsupported operand type(s) for +: 'NoAdd' and 'int'
我认为Python解释器会在查找{之前}在对象的方法列表中找不到方法时使用调用__add__
的标准过程来查找__getattr__
方法在int中{1}}。这显然不是正在发生的事情。
有人可以通过解释发生了什么来帮助我,或者帮助我找出Python源代码中的哪些内容我可以找到__radd__
正在做什么?我不确定我是否能解决这个问题,但我们将不胜感激。
答案 0 :(得分:4)
方法__getattr__
和__getattribute__
仅用于在您明确调用时恢复属性,例如执行foo.bar
时。它们不用于隐式调用。
此行为在documentation
中指定注意:查找特殊方法时,仍可以绕过此方法 作为通过语言语法或内置的隐式调用的结果 功能
here解释了实施此类实施的原因。
以这种方式绕过
__getattribute__()
机器 解释器内速度优化的重要范围,at 处理特殊方法的一些灵活性的成本
总而言之,即。使用__getattr__
重定向隐式特殊方法调用,已经自愿牺牲了速度。
您所描述的行为可以通过类继承来实现。虽然将参数传递给类构造函数,但需要使用__new__
方法进行以下操作。
class NoAdd(int):
def __new__(cls, x, *args, **kwargs):
return super().__new__(cls, x)
def __init__(self, x, *args, **kwargs):
...
x = NoAdd(0)
x + 5 # 5
x * 2 # 0
现在,假设您确实需要捕获对特殊方法的隐式调用。我觉得这很有用,但这是一个有趣的练习。在这种情况下,我们可以依赖元类来填充member
中的缺失方法。
class ProxyToMember(type):
def __init__(cls, name, bases, name_space):
super().__init__(name, bases, name_space)
if hasattr(cls, 'member'):
proxy_attrs = (attr for attr in dir(cls.member) if not hasattr(cls, attr))
def make_proxy(attr):
attr_value = getattr(cls.member, attr)
def proxy(_, *args, **kwargs):
return attr_value(*args, **kwargs)
if callable(attr_value):
return proxy
else:
return property(lambda _: getattr(cls.member, attr))
for attr in proxy_attrs:
setattr(cls, attr, make_proxy(attr))
class A(metaclass=ProxyToMember):
member = 0
class B(metaclass=ProxyToMember):
member = 'foo'
A() + 1 # 1
B().startswith('f') # True
答案 1 :(得分:-1)
我不了解你。
为什么你不能这样做:
class MyInt(int):
pass
def test():
print(MyInt(5) + 5)
test()
答案 2 :(得分:-1)