如何在实例方法中使用functools.singledispatch?

时间:2014-07-07 00:50:41

标签: python python-3.x python-3.4 instance-method single-dispatch

Python 3.4 added使用静态方法定义函数重载的能力。这基本上是文档中的示例:

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实现依赖于第一个用于标识类型的参数,因此将此功能扩展到实例方法变得棘手。

有没有人对如何使用(或jerry-rig)此功能以使其与实例方法一起使用有任何建议?

2 个答案:

答案 0 :(得分:52)

查看singledispatch的{​​{3}},我们可以看到装饰器返回一个函数wrapper(),它选择一个函数来根据{{1 } ...

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

答案 1 :(得分:11)

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

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

如答案中所示,在这种情况下,您可以编写另一个封装器来修补装饰器。但是这种hacky修复并不总是最好的选择。

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

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不同,它允许多方法。

除了Dispatchersingledispatch - 兼容的语法,它还提供了一个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)