在类装饰器

时间:2016-02-11 08:19:00

标签: python decorator python-decorators

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

def odd_even_router(odd, even):
    def r(n):
        if n % 2:
            odd(n)
        else:
            even(n)
    return r

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

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()

@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(),我可以这样做:

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()时,都会生成一个新的(但相同的)路由器函数。所以这是我的问题:如何在odd_even_router()装饰器中重新使用attach_default_router()生成器,但每次都不生成新的路由器功能?

问题的核心是:odd_even_router()返回一个接受一个参数的函数,但作为实例方法的check_number()需要两个(第一个是对象的self)。如果我没有抓住self,我还无法生成路由器功能。当我抓住self时,我已经在方法中,并且每次生成它都需要在每次调用方法时生成。

我怎样才能解决这个难题?

1 个答案:

答案 0 :(得分:2)

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

这是因为您的route函数不仅每次都生成一个新的*,oddeven方法也是如此。 self.odd每次执行该表达式时都会创建一个新的方法包装器,因为functions are descriptors,并且每次需要时都绑定到实例(self)。

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

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,因为它可能更具可读性,并保留原始版本{{ 1}}工厂函数可用作方法和函数。