【问题标题】:Overloading operators using __getattr__ in Python在 Python 中使用 __getattr__ 重载运算符
【发布时间】:2021-10-14 17:33:24
【问题描述】:

我正在尝试使用__getattr__ 函数一次重载多个运算符。在我的代码中,如果我调用foo.__add__(other),它会按预期工作,但是当我尝试foo + bar 时,它不会。这是一个最小的例子:

class Foo():
    
    
    def add(self, other):
        return 1 + other
    
    def sub(self, other):
        return 1 - other
    
    def __getattr__(self, name):
                
        stripped = name.strip('_')
        if stripped in {'sub', 'add'}:
            return getattr(self, stripped)
        else:
            return
    
if __name__=='__main__':
    
    bar = Foo()
    
    print(bar.__add__(1)) # works
    print(bar + 1) # doesn't work

我意识到在这个例子中只定义 __add____sub__ 会更容易,但在我的情况下这不是一个选项。

另外,作为一个小问题,如果我替换该行:

if stripped in {'sub', 'add'}:

if hasattr(self, name):

代码有效,但随后我的 iPython 内核崩溃了。为什么会发生这种情况,我该如何预防?

【问题讨论】:

  • 我很确定,但目前无法证明,bar.__add__(1)bar + 1 之间的差异是因为后一种行为直接调用了方法 __add__() 中的行为,实际上没有以通常的方式访问它。我认为这是解释器为 most 内置函数所做的事情——至少在 cpython 中,其他实现可能会有所不同。
  • “如果我替换该行......代码可以工作,但是我的 iPython 内核会崩溃” - 替换不会使任何工作。你一定误解了你所看到的。
  • 另外,Python 会绕过正常的属性查找来进行此类操作,因此不可能实现 __getattr__ 实现您的目标。只需实现__add____sub__
  • 我也认为(但不能证明)像加法/减法这样的重要操作绕过了__getattr__ 机制,所以你的__getattr__ 不会被bar + 1 调用
  • 我不想实现__add____sub__ 的原因是我想为一大堆运营商做这件事,我想避免冗余代码。但我想这是要走的路……

标签: python


【解决方案1】:

发生这种情况是因为 python 运算符使用优化来查找实现该运算符的函数。以下几行大致等价:

foo + 1
type(foo).__add__(foo, 1)

仅在类对象上专门找到运算符,而不是在实例上。

bar.__add__(1) 调用__getattr__ 以查找bar 上缺少的属性。这是可行的,因为它绕过了正常的运算符查找过程。

bar + 1 调用Foo.__add__(bar, 1) 后跟int.__radd(1, bar)。第一个属性查找失败,第二个选项引发TypeError

【讨论】:

  • 谢谢,我不知道这个区别。你有什么不同的方法来解决这个问题吗?
  • @drmaettu。您可以尝试使用您的元类玩游戏。这可能会让您对查找在类对象上的工作方式收费。取决于你真正想要做什么。鉴于 python 已经有一个非常可靠的继承机制,我看不出你在这里要完成什么。
  • 感谢您的提示,这可能是如何解决这个问题(供参考):stackoverflow.com/questions/9057669/…
  • @drmaettu。我很高兴你能找到那个。希望它能提供您需要的解决方法,或者说服您不要这样做。
【解决方案2】:

另外,作为一个小问题,如果我替换以下行:[...]

hasattr 在后台调用__getattr__。这解释了您在执行if hasattr(self, name): 时看到的情况,因为您已经覆盖了__getattr__,实际上您进入了一个无限递归。

自己看

class O:
    def __getattr__(self, attr):
        print('yooo')
        return super().__getattr__(attr)
        #      self.__getattr__(attr) -> RecursionError
>>> hasattr(O(), 'ya')
yooo
False

【讨论】:

  • 谢谢,一切顺利!
【解决方案3】:

我发现了一个不涉及元类的半令人满意的解决方法:

class Foo():
    
    def add(self, other):
        return 1 + other
    
    def sub(self, other):
        return 1 - other

# Add operators to the type object
def get_operator(name):
    return getattr(Foo, name.strip('_'))

for op in ['__add__', '__sub__']:
    setattr(Foo, op, get_operator(op))

    
if __name__=='__main__':
    
    bar = Foo()
    
    print(bar + 2)
    print(bar - 2)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    • 2013-03-05
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多