简体   繁体   中英

Re-use a global function within a class decorator

Suppose I have a function that generates a router function which invokes specified callbacks depending on whether a number is odd or even:

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

I also have a class decorator that attaches a similar router method, named check_number , to a class:

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

Then, a class decorated with @attach_default_router has check_number() defined automatically, and only has to implement on_odd() and on_even() :

@attach_default_router
class A(object):
    def on_odd(self, n):
        print 'Odd number'

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

If I want to re-use odd_even_router() , the router function generator, inside attach_default_router() , I may do this:

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

However, an undesirable effect is that, every time check_number() gets called, a new (but identical) router function is generated. So here is my question: How could I re-use odd_even_router() generator inside attach_default_router() decorator, but without generating a new router function every time?

The issue at heart is: odd_even_router() returns a function that takes one argument, but check_number() , being an instance method, needs two (the first being the object's self ). If I don't get hold of self , I cannot generate the router function yet. When I get hold of self , I am already inside the method, and generating it there entails generating every time the method is called.

How could I resolve this dilemma?

You can, but you'd have to bind your odd and even hooks at runtime, requiring a slightly different implementation of your factory.

That's because not only is your route function produced a-new each time, *so are the odd and even methods. self.odd creates a new method wrapper every time you execute that expression, because functions are descriptors , and are being bound to the instance ( self here) each time you need one.

So, if you want to generate one route() function for use with all instances of your decorated class, you have to then manually make sure the binding still takes place:

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

Note that Python still will create a route method object now. Each time you access instance_of_your_decorated_class.route , a method object is created through the descriptor protocol. And the same happens when calling odd.__get__() and even.__get__() . You may as well stick to your original version, and generate a new route() function for each call, passing in self.odd and self.even , as that's probably more readable, and keeps your original odd_even_router() factory function usable for use both as methods and for functions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM