关于python:如何在实例方法中使用functools.singledispatch?

How can I use functools.singledispatch with instance methods?

python 3.4增加了用静态方法定义函数重载的能力。这基本上是文档中的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)此功能使其与实例方法一起工作,是否有人有任何建议?


singledispatch的源代码可以看出,decorator返回一个函数wrapper(),该函数根据args[0]的类型从注册的函数中选择一个要调用的函数。

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

…对于一个正则函数来说是可以的,但是对于一个实例方法却没有太大的用处,它的第一个参数总是self

但是,我们可以编写一个新的decorator methdispatch,它依赖singledispatch来进行重载提升,而返回一个包装函数,根据args[1]的类型选择要调用的注册函数:

1
2
3
4
5
6
7
8
9
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

下面是正在使用的装饰器的一个简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
2
3
4
5
>>> pw = Patchwork(a=1, b=2, c=3)
>>> pw.get("b")
2
>>> pw.get(["a","c"])
[1, 3]


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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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(不是标准的,但包含在Python中,没有任何非标准的依赖关系),正如名称已经表明的,与singledispatch不同,它允许多种方法。

除了Dispatcher对象外,它还提供了singledispatch兼容语法的dispatch修饰器,用于向用户隐藏这些对象的创建和操作。

The dispatch decorator uses the name of the function to select the
appropriate Dispatcher object to which it adds the new
signature/function. When it encounters a new function name it creates
a new Dispatcher object and stores name/Dispatcher pair in a namespace
for future reference.

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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)