如何使用__getattr__函数处理和返回缺失的属性和方法?
在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 个回答
没有办法知道返回的属性是打算怎么使用的。在 Python 中,所有的东西都是属性,包括方法:
>>> class Foo(object):
... def bar(self): print 'bar called'
... spam='eggs'
...
>>> Foo.bar
<unbound method Foo.bar>
>>> Foo.spam
'eggs'
Python 首先会查找属性(比如 bar
或 spam
),如果你想要 调用 它(也就是加上了括号),那么 Python 会在查找完属性后再去执行这个可调用的东西:
>>> foo = Foo()
>>> fbar = foo.bar
>>> fbar()
'bar called'
在上面的代码中,我把查找 bar
和调用 bar
分开了。
因为没有区别,所以你无法在 __getattr__
中判断返回的属性会被用来做什么。
__getattr__
会在正常的属性访问失败时被调用;在下面的例子中,monty
是在类中定义的,所以 __getattr__
不会 被调用;它只会在 bar.eric
和 bar.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__
返回的对象,这些对象可以在不同的上下文中既被调用又可以作为值使用。
如何判断请求的属性是以属性的方式访问,还是以带括号的方法方式访问,并分别返回一个值或一个函数呢?
其实你无法做到这一点。你也无法判断一个请求的方法是实例方法、类方法还是静态方法等等。你能知道的只是有人在尝试获取一个属性的值。其他的信息在获取属性的过程中并没有传递给你的代码。
所以,你需要一种额外的方法来判断是创建一个函数还是其他类型的值。这种情况其实很常见——你可能是在为某个其他对象做代理,而那个对象确实有值和函数的区别(比如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