有没有办法检查 __getattr__ 中传入的参数或以其他方式根据传入参数重定向调用?

发布于 2024-12-21 09:09:38 字数 1777 浏览 2 评论 0原文

一些背景: 我们有一个交易系统,我们根据交易账单所在的国家/地区划分流量。我们有一个存在于 2 个实例中的日志记录表,一个数据库将事务记录到欧盟,另一个记录到其他任何地方。我们还有一个测试库,用于管理和隐藏使用数据库的内部内容,粗略地说,每个表都由一个类表示。我有一个表示表的类,并且数据库会话管理器类对于该类的两个实例中的每一个实例都有两个成员。我想要做的是创建一个通用的“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']

Some background:
We have a system for transactions where we devide the flow based upon the country the transaction bills to. We have a logging table that exists in 2 instances, one DB logging transactions to the EU, the other to anywhere else. We also have a test library that manages and hides the guts of working with the DB where roughly speaking each table is represented by a class. I have a class that represents the table, and the db session manager class has two members for each of the two instances of the class. What I want to do is create a generic 'meta dao' class that will take any arbitrary call to it, inspect the args, and based upon one of the input arguments, subsequently dispatch the call to the correct db instance-representing class instance. I initially thought about just overloading every method, but that's clunky and dirty.

I was looking at using __getattr__ to override the method lookup so that I could then call down to the correct instance based upon the name of the method __getattr__ recieves, but from what I understand, I can't inspect the incoming method arguments from within __getattr__, so I can't properly dispatch from within it in this case. Does anyone have any ideas of a different direction I can pursue, or a way to 'inspect' the arguments, not just the method name, from within __getattr__?

[edit] Here's a genericized version of what I'm talking about:

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']

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

寂寞笑我太脆弱 2024-12-28 09:09:38

按照这些思路应该可以工作:

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) 

Somethings along these lines should work:

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)

Then you would create the two instances and combine them in a proxy object:

eu = make_instance(...)
other = make_instance(...)
p = Proxy(eu, other)
p.somemethod(foo) 
野鹿林 2024-12-28 09:09:38

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

如果你有几个方法要重写,你可以单独应用装饰器,甚至可以将参数(例如差异阈值)传递给不同的方法,但如果想重写所有方法,你可以添加一个重载所有方法的元类给定类的,但在这种情况下,按照 sth 的建议覆盖 __getattr__ 是一个不错的选择

python decorators are your friend. You can do something like this

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)

output:

calling foo. func1
default functionality
calling bar. func1
default functionality

If you have few methods to override you can individually apply decorator and even can pass parameters e.g. diff threshold to different methods, but if want to override all methods may be you can add a metaclass which overloads all method of given class, but in this case overriding __getattr__ as suggested by sth is a good alternative

且行且努力 2024-12-28 09:09:38

基于传递的参数进行调度是一个两步过程:

  1. __getattr__ 返回一个代理方法
  2. 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

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

Dispatching based on passed arguments is a two-step process:

  1. __getattr__ returns a proxy method
  2. python calls the proxy which then decides which real method to call

Here's an example:

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')

When run, this gives:

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

Followed by:

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

To make this work you would either have to always use the same keyword argument (and name it when you call the function) or always have the argument in the same position. Or you can make several different proxies for each style/category/whatever type of method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文