重载一个类的某个实例的 __getattr__ 方法?

3

我现在正在处理一项涉及抽象数学函数的项目。不想让读者感到无聊,我只说之前版本中存在以下结构:

class Foo(Bar):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.sub_unit = Foo(a, not b)

当然,我对Foo的参数所做的更改并不完全是这样,但必须保证如果重复访问此属性,则会产生无限长的Foo对象链。显然,当实例化Foo时,这将导致无限递归。我通过删除init的最后一行并向Foo类添加以下内容来解决早期版本中的问题:
def __getattr__(self, attr: str):
    if attr == 'sub_unit':
        return Foo(self.a, not self.b)
    else:
        return super().__getattr__(attr)

这种方法还是很不错的,因为我可以根据需要计算链条中的下一个对象。

然而,在检查代码时,我意识到出于其他原因,我需要一个Bar的实例,而不是它的子类。为了看看是否可以覆盖单个实例的getattr,我尝试了以下操作:

>>> foo = Bar(a=1, b=2) # sub_unit doesn't get set here.
>>> foo.__getattr__ = lambda attr: 'foo'
>>> foo.a
1
>>> foo.__getattr__('a')
'foo'

这里发生了什么我不理解的事情?为什么foo.a没有调用foo.__getattr__('a')
有没有一种好的方法可以覆盖单个实例的__getattr__,或者我的最佳选择是重新设计所有读取sub_unit及其相关内容的代码来将其作为函数调用,以处理这种特殊情况?

1
请查看其他评论中的链接 - 你无法覆盖它的原因是__getattr__是一个特殊方法。 - Harry Harrison
此外,__getattr__ 仅在缺失的属性上调用,即那些无法通过正常方式找到的属性。如果您想拦截对所有属性的访问,请实现 __getattribute__ - jonrsharpe
我并没有完全意识到特殊方法的微妙之处,这似乎就是问题所在。针对您的评论,jonrsharpe,我也尝试重写__getattribute__(虽然我忘了提到,我的错),但没有效果。另外,如果是因为缺少参数,为什么在基于类的情况下可以工作?在init中,我将sub_unit设置为None - David Legg
1
也许你想要 sub_unit 成为一个 property,当访问时返回 return Foo(self.a, not self.b) - Dan D.
@DanD。属性起初看起来很有前途,但我似乎只能通过将它们包含在类定义中才能让它们工作。有没有一种好的方法将其应用于单个实例? - David Legg
1个回答

1
当您使用foo.a查找属性a时,Python会在实例的属性字典中查找。如果未找到,则会调用__getattr__方法。
相反,如果实例中存在属性a,则不会调用__getattr__。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接