如何使用__getattr__函数处理和返回缺失的属性和方法?

4 投票
2 回答
2471 浏览
提问于 2025-04-17 14:14

在Python类中,使用__getattr__这个特殊方法来处理缺失的属性或函数其实很简单,但同时处理这两种情况似乎就没那么容易了。

先来看一个例子,这个例子处理的是类中没有明确定义的任何属性请求...

class Props:
    def __getattr__(self, attr):
        return 'some_new_value'

>>> p = Props()
>>> p.prop                          # Property get handled
'some_new_value'

>>> p.func('an_arg', kw='keyword')  # Function call NOT handled
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: 'str' object is not callable

接下来,再看一个例子,这个例子处理的是类中没有明确定义的任何函数调用...

class Funcs:
    def __getattr__(self, attr):
        def fn(*args, **kwargs):
            # Do something with the function name and any passed arguments or keywords
            print attr
            print args
            print kwargs
            return 
        return fn

>>> f = Funcs()
>>> f.prop                          # Property get NOT handled
<function fn at 0x10df23b90>

>>> f.func('an_arg', kw='keyword')  # Function call handled
func
('an_arg',)
{'kw': 'keyword'}

问题是,如何在同一个__getattr__中同时处理这两种缺失的属性呢?我们怎么判断请求的属性是以属性的形式表示,还是以带括号的方法形式表示,并分别返回一个值或一个函数?简单来说,我想处理一些缺失的属性和一些缺失的函数,然后对于其他情况使用默认的处理方式。

有什么建议吗?

2 个回答

2

没有办法知道返回的属性是打算怎么使用的。 Python 中,所有的东西都是属性,包括方法:

>>> class Foo(object):
...     def bar(self): print 'bar called'
...     spam='eggs'
... 
>>> Foo.bar
<unbound method Foo.bar>
>>> Foo.spam
'eggs'

Python 首先会查找属性(比如 barspam),如果你想要 调用 它(也就是加上了括号),那么 Python 会在查找完属性后再去执行这个可调用的东西:

>>> foo = Foo()
>>> fbar = foo.bar
>>> fbar()
'bar called'

在上面的代码中,我把查找 bar 和调用 bar 分开了。

因为没有区别,所以你无法在 __getattr__ 中判断返回的属性会被用来做什么。

__getattr__ 会在正常的属性访问失败时被调用;在下面的例子中,monty 是在类中定义的,所以 __getattr__ 不会 被调用;它只会在 bar.ericbar.john 的时候被调用:

>>> class Bar(object):
...     monty = 'python'
...     def __getattr__(self, name):
...         print 'Attribute access for {0}'.format(name)
...         if name == 'eric':
...             return 'idle'
...         raise AttributeError(name)
... 
>>> bar = Bar()
>>> bar.monty
'python'
>>> bar.eric
Attribute access for eric
'idle'
>>> bar.john
Attribute access for john
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __getattr__
AttributeError: john

注意,不仅仅是函数可以被调用;任何自定义的类只要实现了 __call__ 方法,都可以被调用:

>>> class Baz(object):
...    def __call__(self, name):
...        print 'Baz sez: "Hello {0}!"'.format(name)
...
>>> baz = Baz()
>>> baz('John Cleese')
Baz sez: "Hello John Cleese!"

你可以使用从 __getattr__ 返回的对象,这些对象可以在不同的上下文中既被调用又可以作为值使用。

4

如何判断请求的属性是以属性的方式访问,还是以带括号的方法方式访问,并分别返回一个值或一个函数呢?

其实你无法做到这一点。你也无法判断一个请求的方法是实例方法、类方法还是静态方法等等。你能知道的只是有人在尝试获取一个属性的值。其他的信息在获取属性的过程中并没有传递给你的代码。

所以,你需要一种额外的方法来判断是创建一个函数还是其他类型的值。这种情况其实很常见——你可能是在为某个其他对象做代理,而那个对象确实有值和函数的区别(比如ctypes或PyObjC),或者你可能有某种命名约定等等。

不过,你总是可以返回一个可以两种方式使用的对象。例如,如果你的“默认行为”是返回整数属性,或者返回一个返回整数的函数,你可以返回类似这样的东西:

class Integerizer(object):
    def __init__(self, value):
        self.value = value
    def __int__(self):
        return self.value
    def __call__(self, *args, **kw):
        return self.value

撰写回答