有没有办法在__getattr__中检查传入参数或根据传入参数重定向调用?
一些背景信息:
我们有一个处理交易的系统,根据交易的账单国家来分流。我们有一个日志表,这个表在两个实例中存在,一个是记录欧盟的交易,另一个是记录其他地方的交易。我们还有一个测试库,它管理和隐藏与数据库打交道的复杂部分,简单来说,每个表都用一个类来表示。我有一个类代表这个表,而数据库会话管理类则为这两个类的实例各有两个成员。我想做的是创建一个通用的“元数据访问对象(meta dao)”类,它可以接受任何调用,检查参数,然后根据其中一个输入参数,把调用分发到正确的数据库实例类上。起初我考虑过重载每个方法,但这样做太麻烦了。
我在考虑使用 __getattr__
来重写方法查找,这样我就可以根据 __getattr__
接收到的方法名称,调用正确的实例。但据我了解,我不能在 __getattr__
中检查传入的方法参数,所以在这种情况下我无法正确分发调用。有没有人有其他的想法,或者有没有办法在 __getattr__
中“检查”参数,而不仅仅是方法名称?
[编辑] 这是我所说的一个通用版本:
class BarBase(object):
def __init__(self, x):
self.x = x
def do_bar(self, i):
return self.x * i
class FooBar(BarBase):
def __init__(self, x):
super(FooBar, self).__init__(x)
def do_foo(self, i):
return self.x + i
class MetaFoo(object):
def __init__(self, bar_manager):
self.foo_manager = bar_manager
#something here that will take an arbitrary methodname and args as
#long as args includes a value named i, inspect i, and call
#bar_manager.fooa.[methodname](args) if i < 10,
#and bar_manager.foob.[methodname](args) if i >= 10
class BarManager(object):
def __init__(self):
self.bar_list = {}
def __get_fooa(self):
if 'fooa' not in self.bar_list.keys():
self.bar_list['fooa'] = FooBar('a')
return self.bar_list['fooa']
fooa = property(__get_fooa)
def __get_foob(self):
if 'foob' not in self.bar_list.keys():
self.bar_list['foob'] = FooBar('b')
return self.bar_list['foob']
foob = property(__get_foob)
def __get_foo(self):
if 'foo' not in self.bar_list.keys():
self.bar_list['foo'] = MetaFoo(self)
return self.bar_list['foo']
3 个回答
根据传入的参数进行调度其实是一个两步的过程:
__getattr__
会返回一个代理方法- 然后 Python 会调用这个代理,代理再决定真正要调用哪个方法
下面是一个例子:
from functools import partial
class TwoFold(object):
EU = ('GERMANY','FRANCE','ITALY','GREECE',)
def __getattr__(self, name):
try:
EU = object.__getattribute__(self, 'EU_' + name)
Other = object.__getattribute__(self, 'Other_' + name)
except AttributeError:
raise AttributeError(
"%r is missing an EU_%s or Other_%s" % (self, name, name)
)
judge = partial(self._judge, name, EU, Other)
return judge
def _judge(self, method_name, EU, Other, *args, **kwargs):
if kwargs['country'].upper() in self.EU:
method = EU
else:
method = Other
return method(*args, **kwargs)
def EU_log(self, tax, country):
print "logging EU access for %s, tax rate of %r" % (country, tax)
def Other_log(self, tax, country):
print "logging non-EU access for %s, tax rate of %r" % (country, tax)
if __name__ == '__main__':
test = TwoFold()
test.log(7.5, country='France')
test.log(10.1, country='Greece')
test.log(8.9, country='Brazil')
test.howsat('blah')
运行这个例子会得到:
logging EU access for France, tax rate of 7.5
logging EU access for Greece, tax rate of 10.1
logging non-EU access for Brazil, tax rate of 8.9
接下来是:
Traceback (most recent call last):
File "test.py", line 29, in <module>
test.howsat('blah')
File "test.py", line 10, in __getattr__
raise AttributeError("%r is missing an EU_%s or Other_%s" % (self, name, name))
AttributeError: <__main__.TwoFold object at 0x00B4A970> is missing an
EU_howsat or Other_howsat
要让这个工作,你要么总是使用相同的关键字参数(并在调用函数时给它命名),要么总是把参数放在同一个位置。或者你可以为每种风格/类别/方法类型创建几个不同的代理。
Python 装饰器 是个很好的工具。你可以像这样使用它:
class MetaFoo(object):
def overload(func):
"""
we need to check a named variable so for simplicity just checking kwargs
"""
def _wrapper(*args, **kwargs):
if kwargs.get('i',0) < 10:
# get func.func_name from foo and call it
print "calling foo.",func.func_name
else:
print "calling bar.",func.func_name
func(*args, **kwargs)
return _wrapper
@overload
def func1(self, i):
print "default functionality"
MetaFoo().func1(i=5)
MetaFoo().func1(i=10)
输出结果:
calling foo. func1
default functionality
calling bar. func1
default functionality
如果你只需要重写几个方法,可以单独给每个方法加上装饰器,甚至可以给不同的方法传递不同的参数,比如差异阈值。但是如果你想重写所有的方法,可能可以通过添加一个元类来实现,这样就能重载这个类的所有方法。不过在这种情况下,像 __getattr__
这样的方法重写,正如 sth
所建议的,也是一个不错的选择。
类似这样的东西应该可以用:
class ProxyCall(object):
'''Class implementing the dispatch for a certain method call'''
def __init__(self, proxy, methodname):
self.proxy = proxy
self.methodname = methodname
def __call__(self, *p, **kw):
if p[0] == "EU": # or however you determine the destination
return getattr(self.proxy.EU, self.methodname)(*p, **kw);
else:
return getattr(self.proxy.OTHER, self.methodname)(*p, **kw);
class Proxy(object):
'''Class managing the different "equivalent" instances'''
def __init__(self, EU, OTHER):
self.EU = EU
self.OTHER = OTHER
def __getattr__(self, name):
if not hasattr(self.EU, name):
# no such method
raise AttributeError()
else:
# return object that supports __call__ and will make the dispatch
return ProxyCall(self, name)
然后你可以创建两个实例,并把它们组合到一个代理对象里:
eu = make_instance(...)
other = make_instance(...)
p = Proxy(eu, other)
p.somemethod(foo)