有没有办法从__getattr__中检查传入的args,或者以某种方式根据传入的参数重定向调用?

时间:2011-12-14 19:14:14

标签: python reflection inspect getattr

一些背景: 我们有一个交易系统,我们根据交易账单所在的国家/地区划分流量。我们有一个记录表,存在于2个实例中,一个DB记录到EU的事务,另一个记录到其他任何地方。我们还有一个测试库,用于管理和隐藏与DB一起工作的内容,粗略地说每个表由一个类表示。我有一个表示表的类,db会话管理器类为该类的两个实例中的每一个都有两个成员。我想要做的是创建一个通用的'meta dao'类,它将对它进行任意调用,检查args,并根据其中一个输入参数,随后将调用分派给正确的db instance-representation类实例。我最初想过只是重载每一种方法,但这很笨拙。

我正在考虑使用__getattr__覆盖方法查找,以便我可以根据方法__getattr__收到的名称调用正确的实例,但据我所知,我不能inspect来自__getattr__内的传入方法参数,因此在这种情况下我无法正确地从其中发送。有没有人对我可以追求的不同方向有任何想法,或者从__getattr__内“检查”参数而不仅仅是方法名称?

[edit]这是我所说的通用版本:

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 个答案:

答案 0 :(得分:2)

这些方面的某些事情应该有效:

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) 

答案 1 :(得分:2)

python decorators是你的朋友。你可以做这样的事情

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

如果您没有多少方法可以覆盖,您可以单独应用装饰器,甚至可以传递参数,例如diff阈值到不同的方法,但是如果想要覆盖所有方法,你可以添加一个重载给定类的所有方法的元类,但在这种情况下,按__getattr__的建议覆盖sth是一个很好的选择

答案 2 :(得分:0)

基于传递的参数进行调度分为两步:

  1. __getattr__返回代理方法
  2. python调用代理,然后决定调用哪个实际方法
  3. 以下是一个例子:

    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
    

    要完成这项工作,您必须始终使用相同的关键字参数(并在调用函数时将其命名)或始终使参数位于相同位置。或者,您可以为每种样式/类别/任何类型的方法创建几个不同的代理。