Decorating one method of a class C with another method of class C?

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Jun 7 00:40:13 EDT 2014


On Fri, 06 Jun 2014 17:14:54 -0700, Dan Stromberg wrote:

> Is there a way of decorating method1 of class C using method2 of class
> C?

Yes. See below.

> It seems like there's a chicken-and-the-egg problem; the class doesn't
> seem to know what "self" is until later in execution so there's
> apparently no way to specify @self.method2 when def'ing method1.

Directly inside the class statement, you don't need self. The class 
defines a namespace, like inside a function or globals, so you can just 
refer to names directly, like this:


class Spam:
    def decorate(func):  # Look Ma, no self!
        @functools.wraps(func)
        def inner(self, *args):
            print(self, args)
            return func(*args)
        return inner
    @decorate
    def method(self, a, b):
        return a+b


That works fine for what you want to do. The disadvantage, however, is 
that if you call the decorate method from *outside* of the class scope 
(that is, from the outside like Spam.decorate, or inside a method like 
self.decorate) you'll run into some weird problems. Try it and see if you 
can diagnose the cause of the problem.


This problem has a trivial, simple, obvious and wrong solution: ensure 
that the decorate method is a static method. That requires Spam to be a 
new-style class (inherit from object, or a built-in), but is otherwise 
trivial:

class Spam(object):
    @staticmethod
    def decorate(func):  # Look Ma, no self!
        ...


Unfortunately, this does not work! staticmethod objects are not callable. 
It is only after you go through the descriptor protocol that you end up 
with something which is callable. Sad but true. That means that this will 
fail:

class Spam(object):
    @staticmethod
    def decorate(func): ...

    @decorate  # calling directly fails
    def method(self, args): ...


but this will be okay:

@Spam.decorate  # calling via Spam or instance works
def function(args): ...


We're *so close* it hurts :-(

Good news! staticmethod is a class, and like most classes we can subclass 
it and get the behaviour we want!

# Untested
class callablestaticmethod(staticmethod):
    def __call__(self, *args):
        return self.__func__(*args)

ought to do it. Then just use callablestaticmethod instead of 
staticmethod, and all will be ginger-peachy.


But what happens if you want the decorator to refer to the class itself? 
You can always hard-code references to Spam inside the decorate method, 
but a better solution may be to use a callableclassmethod instead. (Left 
as an exercise.)

Inside the decorated method, or the wrapper method "inner", we can refer 
to "self" as normal, and such references won't be evaluated until call-
time when self exists. It's only inside the decorator itself, and the 
body of the class, that self doesn't yet exist.



-- 
Steven D'Aprano
http://import-that.dreamwidth.org/



More information about the Python-list mailing list