添加自定义对象会忽略__getattr__与运算符

时间:2018-03-19 03:40:01

标签: python windows python-3.x cpython getattr

我正在尝试创建一个自定义对象,将所有不存在的方法调用传递给成员属性。这在普通的自定义方法调用下有效,但在尝试调用算术运算符时失败。

下面是一个示例类的控制台片段,一个测试函数以及一个清理过的测试函数反汇编。

>>> 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__正在做什么?我不确定我是否能解决这个问题,但我们将不胜感激。

3 个答案:

答案 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)

经过一些更激烈的研究(并遵循一些最初不太可能的路径),我找到了答案。我的思维过程没有提出相同的搜索关键字,这就是为什么我之前没有找到这些。我的问题可能被视为与以下链接之一重复。

我找到了最简单/最好的解释为什么会发生here。我找到了一些很好的参考资料来解析这个herehereOliver's答案中的链接也很有用。

总之,Python不会对__add____str__等魔术方法使用标准方法查找过程。看起来解决方法是从here创建Wrapper课程。