如何将 functools.singledispatch 与实例方法一起使用?

新手上路,请多包涵

Python 3.4 添加 了使用静态方法定义函数重载的功能。这本质上是文档中的示例:

 from functools import singledispatch

class TestClass(object):
    @singledispatch
    def test_method(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")

        print(arg)

    @test_method.register(int)
    def _(arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)

    @test_method.register(list)
    def _(arg):
        print("Enumerate this:")

        for i, elem in enumerate(arg):
            print(i, elem)

if __name__ == '__main__':
    TestClass.test_method(55555)
    TestClass.test_method([33, 22, 11])

在其最纯粹的形式中, singledispatch 实现依赖于第一个参数来识别类型,因此很难将此功能扩展到实例方法。

有没有人对如何使用(或偷工减料)此功能使其与实例方法一起工作有任何建议?

原文由 Dustin Oprea 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 593
2 个回答

更新: 从 Python 3.8 开始, functools.singledispatchmethod 允许对方法、类方法、抽象方法和静态方法进行单一分派。

对于较旧的 Python 版本,请参阅此答案的其余部分。

查看 singledispatch源码,我们可以看到装饰器返回了一个函数 wrapper() ,它根据 args[0] 的类型从注册的函数中选择一个函数来调用 ---

     def wrapper(*args, **kw):
        return dispatch(args[0].__class__)(*args, **kw)

…这对于常规函数来说很好,但对于实例方法没有多大用处,它的第一个参数总是 self

然而,我们可以编写一个新的装饰器 methdispatch ,它依赖于 singledispatch 来完成繁重的工作,而是返回一个包装函数,该函数根据类型选择要调用的注册函数的 args[1]

 from functools import singledispatch, update_wrapper

def methdispatch(func):
    dispatcher = singledispatch(func)
    def wrapper(*args, **kw):
        return dispatcher.dispatch(args[1].__class__)(*args, **kw)
    wrapper.register = dispatcher.register
    update_wrapper(wrapper, func)
    return wrapper

这是一个使用装饰器的简单示例:

 class Patchwork(object):

    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    @methdispatch
    def get(self, arg):
        return getattr(self, arg, None)

    @get.register(list)
    def _(self, arg):
        return [self.get(x) for x in arg]

请注意,修饰的 get() 方法和注册到 list 的方法都像往常一样具有初始 self 参数。

测试 Patchwork 类:

 >>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a", "c"])
[1, 3]

原文由 Zero Piraeus 发布,翻译遵循 CC BY-SA 4.0 许可协议

装饰器本质上是一个包装器,它将包装函数作为参数并返回另一个函数。

如接受的答案中所述, singledispatch 返回一个 wrapper 将第一个参数作为注册类型 - self 在实例方法中。

如该答案所示,在这种情况下,您可以编写另一个包装器来猴子修补装饰器。但这种骇人听闻的修复并不总是最好的选择。

与任何其他函数一样,您可以调用包装器并将参数显式传递给它,如果这种方法重载很少在包中进行,那么这对我来说似乎更简单、更扁平且更具可读性。

 from functools import singledispatch

class TestClass(object):

    def __init__(self):
        self.test_method = singledispatch(self.test_method)
        self.test_method.register(int, self._test_method_int)
        self.test_method.register(list, self._test_method_list)

    def test_method(self, arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")

        print(arg)

    def _test_method_int(self, arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)

    def _test_method_list(self, arg):
        print("Enumerate this:")

        for i, elem in enumerate(arg):
            print(i, elem)

if __name__ == '__main__':
    test = TestClass()
    test.test_method(55555)
    test.test_method([33, 22, 11])

还有另一个模块 multipledispatch (不是标准的,但包含在 Anaconda 中并且没有任何非标准依赖项),正如名称已经表明的那样,与 singledispatch 不同,它允许使用多种方法。

除了 Dispatcher 对象,与 singledispatch 兼容的语法,它提供了一个 dispatch 装饰器,它隐藏了这些对象的创建和操作。

dispatch 装饰器使用函数的名称来选择适当的 Dispatcher 对象,并向其添加新的签名/函数。当它遇到一个新的函数名称时,它会创建一个新的 Dispatcher 对象并将名称/Dispatcher 对存储在命名空间中以供将来参考。

例如:

 from types import LambdaType
from multipledispatch import dispatch

class TestClass(object):

    @dispatch(object)
    def test_method(self, arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")

        print(arg)

    @dispatch(int, float)
    def test_method(self, arg, arg2):
        print("Strength in numbers, eh?", end=" ")
        print(arg + arg2)

    @dispatch((list, tuple), LambdaType, type)
    def test_method(self, arg, arg2, arg3):
        print("Enumerate this:")

        for i, elem in enumerate(arg):
            print(i, arg3(arg2(elem)))

if __name__ == '__main__':

    test = TestClass()
    test.test_method(55555, 9.5)
    test.test_method([33, 22, 11], lambda x: x*2, float)

原文由 Nuno André 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题