关于python:在类装饰器中重用一个全局函数

Re-use a global function within a class decorator

假设我有一个函数,它生成一个路由器函数,根据数字是奇数还是偶数调用指定的回调:

1
2
3
4
5
6
7
def odd_even_router(odd, even):
    def r(n):
        if n % 2:
            odd(n)
        else:
            even(n)
    return r

我还有一个类修饰器,它将一个类似的路由器方法(名为check_number)附加到一个类上:

1
2
3
4
5
6
7
8
9
def attach_default_router(cls):
    def route(self, n):
        if n % 2:
            self.on_odd(n)
        else:
            self.on_even(n)

    cls.check_number = route
    return cls

然后,用@attach_default_router修饰的类自动定义了check_number(),只需要实现on_odd()on_even()

1
2
3
4
5
6
7
@attach_default_router
class A(object):
    def on_odd(self, n):
        print 'Odd number'

    def on_even(self, n):
        print 'Even number'

如果我想重新使用odd_even_router(),在attach_default_router()内部的路由器函数生成器,我可以这样做:

1
2
3
4
5
6
7
def attach_default_router(cls):
    def route(self, n):
        r = odd_even_router(self.on_odd, self.on_even)
        r(n)

    cls.check_number = route
    return cls

然而,一个不良的影响是,每次调用check_number()时,都会生成一个新的(但相同的)路由器函数。因此,我的问题是:我如何在attach_default_router()装饰器内重新使用odd_even_router()生成器,而不每次都生成新的路由器功能?

本质上的问题是:odd_even_router()返回一个接受一个参数的函数,但是check_number()作为一个实例方法,需要两个参数(第一个是对象的self)。如果我不掌握self,我还不能生成路由器功能。当我获得self的控制权时,我已经进入了方法的内部,并且每次调用方法时都需要生成它。

我如何解决这个困境?


你可以,但是你必须在运行时绑定你的oddeven钩子,这要求你的工厂实现略有不同。

这不仅是因为你的route函数每次都产生一个新的函数,*oddeven方法也是如此。每次执行表达式时,self.odd都会创建一个新的方法包装器,因为函数是描述符,每次需要时都绑定到实例(这里是self)。

因此,如果要生成一个用于修饰类的所有实例的route()函数,则必须手动确保绑定仍然发生:

1
2
3
4
5
6
7
8
9
10
11
12
def odd_even_router_method_factory(odd, even):
    def route(self, n):
        if n % 2:
            odd.__get__(self)(n)
        else:
            even.__get__(self)(n)
    return route

def attach_default_router(cls):
    route = odd_even_router_method_factory(cls.on_odd, cls.on_even)
    cls.check_number = route
    return cls

注意,python现在仍然会创建一个route方法对象。每次访问instance_of_your_decorated_class.route时,都会通过描述符协议创建一个方法对象。调用odd.__get__()even.__get__()时也会发生同样的情况。您也可以坚持原来的版本,为每个调用生成一个新的route()函数,传入self.oddself.even,因为这可能更易读,并使原来的odd_even_router()工厂函数既可用作方法也可用作函数。