为什么getattr()的行为和我想的不一样?我认为这段代码应该输出'sss

2 投票
2 回答
6443 浏览
提问于 2025-04-15 17:23

这是我的代码:

class foo:
    def __init__(self):
        self.a = "a"
    def __getattr__(self,x,defalut):
        if x in self:
            return x
        else:return defalut

a=foo()
print getattr(a,'b','sss')

我知道 __getattr__ 这个方法必须有两个参数,但我想在属性不存在的时候获取一个默认的属性。

我该怎么做呢,谢谢。


还有,

我发现如果定义了 __setattr__,我的下一段代码也无法运行。

class foo:
    def __init__(self):
        self.a={}
    def __setattr__(self,name,value):
            self.a[name]=value

a=foo()#error ,why

嗨,亚历克斯,

我修改了你的例子:

class foo(object):
    def __init__(self):
        self.a = {'a': 'boh'}
    def __getattr__(self, x):
        if x in self.a:
            return self.a[x]
        raise AttributeError

a=foo()
print getattr(a,'a','sss')

它打印的是 {'a': 'boh'},而不是 'boh'。

我认为它应该打印 self.a 而不是 self.a['a'],这显然不是我想要的结果。

为什么会这样?有没有办法避免这种情况?

2 个回答

3

你把 getattr 这个内置函数和 __getattr__ 这个方法搞混了。getattr 是用来动态获取对象某个属性的,按名字来找,发生在程序运行的时候。而 __getattr__ 是当你访问一个对象里不存在的属性时会被调用的方法。

你不能在 __getattr__ 里面使用

if x in self:

,因为这样会导致 in 操作符调用 __getattr__,这会造成无限循环。

如果你只是想让所有未定义的属性都返回一个默认值,那么

def __getattr__(self, ignored):
    return "Bob Dobbs"
5

你的第一个问题是:你在定义一个旧式类(我们知道你在用Python 2.x,因为你用print作为关键字;-)。在Python 2中:

class foo:

这意味着你在定义一个旧式类,也叫遗留类,它的行为有时候会很奇怪。千万不要这样做——没有任何好理由!旧式类只是为了兼容那些依赖它们奇怪行为的老旧代码而存在(在Python 3中已经被淘汰)。应该使用新式类:

class foo(object):

这样的话,检查if x in self:就不会导致递归调用__getattr__。不过,它还是会失败,因为你的类没有定义__contains__方法,所以你无法检查x是否在这个类的实例中。

如果你想知道x是否在实例字典中,不用费心:在这种情况下__getattr__根本不会被调用——它只在属性没有self中找到时才会被调用。

如果你想支持三参数调用内置的getattr,只需在你的__getattr__方法中根据需要raise AttributeError(就像你根本没有__getattr__方法时会发生的那样),内置函数会正常工作(这是内置函数的职责,拦截这种情况并返回提供的默认值)。这就是为什么我们从不直接调用特殊方法,比如__getattr__,而是使用内置函数和运算符,它们会在内部调用这些特殊方法——内置函数和运算符提供了额外的价值。

举个例子,比较容易理解:

class foo(object):
    def __init__(self):
        self.blah = {'a': 'boh'}
    def __getattr__(self, x):
        if x in self.blah:
            return self.blah[x]
        raise AttributeError

a=foo()
print getattr(a,'b','sss')

这会打印出sss,正如你所期望的那样。

如果你添加一个__setattr__方法,那么这个方法会拦截每一个self设置属性的尝试——包括self.blah =任何东西。所以——当你需要绕过你自己定义的__setattr__时——你必须使用不同的方法。例如:

class foo(object):
    def __init__(self):
        self.__dict__['blah'] = {}
    def __setattr__(self, name, value):
        self.blah[name] = value
    def __getattr__(self, x):
        if x in self.blah:
            return self.blah[x]
        raise AttributeError

a=foo()
print getattr(a,'b','sss')

这也会打印出sss。而不是

        self.__dict__['blah'] = {}

你也可以使用

        object.__setattr__(self, 'blah', {})

这种“调用超类实现”的方式(你也可以通过super内置函数获得)是“不要直接调用特殊方法,而是调用内置函数或使用运算符”的规则中的少数例外之一——在这里,你想特别绕过正常行为,所以显式调用特殊方法是一个可能的选择。

撰写回答