【问题标题】:Method delegation in pythonpython中的方法委托
【发布时间】:2024-01-21 00:27:01
【问题描述】:

我正在编写一个用于编排 AWS 集群的小型框架,并且有一些常见的分层模式会反复出现。一种这样的模式是将一组实例收集到一个更大的对象中,然后将一些方法直接委托给所有实例。因此,我没有一遍又一遍地复制和粘贴相同的样板代码,而是使用以下模式对其进行抽象:

def __getattr__(self, item):
    if not item in self._allowed_items:
        raise NonDelegatableItem

    def delegator():
        for instance in self.all_instances:
            getattr(instance, item)()

    return delegator

有没有更好的方式或模式来完成委托?

【问题讨论】:

  • 使用not in 是惯用的,而不是item,我会说method

标签: python python-2.7 reflection delegation message-passing


【解决方案1】:

__getattr__在遍历整个类层次结构并且没有找到属性时被调用。所以最好生成一次方法并将其存储在类中。那么下次找方法的时间会更短。

>>> X.a

Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    X.a
AttributeError: class X has no attribute 'a'
>>> x.a
new delegator
<function delegator at 0x02937D30>
>>> x.a
<bound method X.delegator of <__main__.X instance at 0x028DBC60>>
>>> X.a
<unbound method X.delegator>

在这里你可以看到你的代码的调整:

class NonDelegatableItem(AttributeError):
    pass

class X:
    def __getattr__(self, method_name):
        self.check_method_name_is_delegator(method_name)
        return self.create_delegator(method_name)

    def check_method_name_is_delegator(self, method_name):
        if method_name not in self._allowed_items:
            raise NonDelegatableItem('{} can not be delegated'.format(method_name))

    @classmethod
    def create_delegator(cls, method_name):
        print 'new delegator'
        def delegator(self, *args, **kw):
            self.check_method_name_is_delegator(method_name)
            for instance in self.all_instances:
                getattr(instance, method_name)(*args, **kw)
        setattr(cls, method_name, delegator)
        return delegator


x = X()

x._allowed_items = ['a', 'b']

【讨论】:

  • 好点。我没想到一路上也分配方法。
  • 不错的一个。不应该省略__getattr__check_method_name_is_delegator 的调用,这样就不会再次查找不可委托的属性(毕竟有人可以通过except AttributeErrorcatch NonDelegatableItem)?第二个调用可以放在delegator 之外,使用except NonDelegatableItem: def delegator(self, *args, **kw): raise NonDelegatableItem(...,因此只检查一次可委托性。
  • @TobiasKienzler 听起来很合理。你可以用这个创建一个新的答案。我们可以稍后再讨论。
  • @User 嗯,* 应该有一个 github 式的“fork answer”选项;)实际上我很快就谈到了:你的 setattr(cls, method_name, delegator) 已经负责防止再次调用 __getattr__ (我很困惑它与一直称为__getattribute__),并通过在def delegator 中拆分check_method_name_is_delegator-check 来取消动态修改_allowed_items 的选项。所以剩下的唯一建议是忽略__getattr__check_method_name_is_delegator 的调用,尽管这会略微提高非委托人的性能...
【解决方案2】:

我一直在研究这个并找到了两个解决方案。使用装饰器更改类并创建委托者,或使用委托者的描述符。我从第一个开始,然后发展到我更喜欢的第二个,所以我将从它开始。两者都可以在这里找到:https://gist.github.com/dhilst/7435a09b4419da349bb4cc4ae855a451 with doctests :)

-- 编辑--

对于任何感兴趣的人,我将其设为图书馆:https://pypi.org/project/delegateto/

gist 实现中存在错误,人们在 github 上对此做出了贡献,pypi 项目已更新,gist 没有。我强烈推荐你使用 pypi 版本。

使用描述符

描述符是可以获取和设置的东西。在这种情况下,我们对描述符的可获取能力感兴趣。像这样定义的委托描述符

class DelegateTo:
    def __init__(self, to, method=None):
        self.to = to
        self.method = method
    def __get__(self, obj, objecttype):
        if self.method is not None:
            return getattr(getattr(obj, self.to), self.method)

        for method, v in obj.__class__.__dict__.items():
            if v is self:
                self.method = method
                return getattr(getattr(obj, self.to), method)

而且是这样使用的

class Foo:
    upper = DelegateTo('v')
    __len__ = DelegateTo('l')
    __iter__ = DelegateTo('l')
    def __init__(self, v, l):
         self.v = v
         self.l = l

要调用描述符,只需调用方法Foo('hello').upper()。魔术方法也有效len(Foo('', [1,2,3,4])) 返回 4。上面的 gist 链接具有更强大的实现,但基础是相同的。

使用装饰器

每当您需要以重复的方式更改类行为时,装饰器都是候选者。在这种情况下,装饰器将在类中调用setattr 来创建委托。

def delegate(to, *methods):
    def dec(klass):
        def create_delegator(method):
            def delegator(self, *args, **kwargs):
                obj = getattr(self, to)
                m = getattr(obj, method)
                return m(*args, **kwargs)
            return delegator
        for m in methods:
            setattr(klass, m, create_delegator(m))
        return klass
    return dec

用法也很简单,只要装饰类,想多少次就行。装饰器将就地修改类,因此返回相同的类。

这是一个用法

@delegate('v', 'upper', 'lower')
class Foo:
   def __init__(self, v):
       self.v = v

并且委托方法的调用也是透明的Foo('hello').upper()。我更喜欢第二个,因为它对我来说似乎更惯用。装饰器具有支持多种方法的优势,但这也可以在描述符表单上实现。

再次,我真的建议您查看要点:https://gist.github.com/dhilst/7435a09b4419da349bb4cc4ae855a451 文档字符串中有大量示例。只需修改它们并执行脚本即可。

-- 编辑--

对于任何感兴趣的人,我把它做成一个 pip 包https://pypi.org/project/delegateto/

-- 编辑--

gist 实现中存在错误,人们在 github 上对此做出了贡献,pypi 项目已更新,gist 没有。我强烈推荐你使用 pypi 版本。

问候

【讨论】:

    最近更新 更多