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

D

Dan Stromberg

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

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.
 
S

Steven D'Aprano

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,696
Latest member
BarbraOLog

Latest Threads

Top