[Python-Dev] Class decorators

Nick Coghlan ncoghlan at gmail.com
Fri Mar 31 16:55:54 CEST 2006


Michael Chermside wrote:
> In the discussion over class decorators, Jim Jewett writes:
>> I have often started with a function, and ended up replacing it with a
>> callable object so that I could save state without resorting to
>> "defalt args" or worse.
>>
>> I would prefer to decorate these exactly like the functions they replace.
> 
> I have observed the entire discussion about class decorators with absolutely
> no opinion, until I read Jim's brief post quoted above. I am now completely
> convinced that class decorators ought to exist and behave exactly like
> function decorators. Thanks, Jim for pointing out what should have been
> obvious to me from the start. The ability to use callable objects as
> functions is a powerful tool in Python, and ought not be broken by decorator
> inconsistencies.

While I agree with you, I don't think this conclusion is as obviously correct 
as it might first appear, because you don't want to decorate the class itself 
in such cases. You don't even want to decorate the class's __call__ method - 
you want to decorate an *instance* of the class, as that is what will mimic 
the interface of the original function.

Compare:

Py> def f():
...    print "Hi World from <something>!"
...
Py> f()
Hi World from <something>!

and:

Py> class f(object):
...     def __call__(self):
...         print "Hi world from %s!" % self
...
Py> f()
<__main__.f object at 0x00AE1F70>
Py> f()()
Hi world from <__main__.f object at 0x00AE7130>!

Clearly, these two definitions of 'f' are _not_ equivalent - the first one is 
a callable, but the latter is a callable factory. Applying the original 
function's decorators to the class or its __call__ method will yield nonsense.

To get an object from the second approach with an interface equivalent to the 
original f (only with an automatically supplied mutable data store as its 
first argument), you instead want to write:

Py> class f(object):
...     def __call__(self):
...         print "Hi world from %s!" % self
...
Py> f = f()
Py> f()
Hi world from <__main__.f object at 0x00AE7190>!

If the original "f" had decorators applied to it, then you would have to apply 
those to the final resulting bound method in the example, not to the class 
definition.

OTOH, all is not lost, as a simple class decorator would allow Jim's use case 
to be satisfied quite handily:

   def instance(cls):
       return cls()

Then:
   @deco1
   @deco2
   def f():
       pass

Could easily be replaced with:

   @deco1
   @deco2
   @instance
   class f(object):
       def __call__(self):
          pass

Cheers,
Nick.

P.S. If all you want is somewhere to store mutable state between invocations, 
you can always use the function's own attribute space:

Py> def f():
...     print "Hi world from %s!" % f
...
Py> f()
Hi world from <function f at 0x00AE90B0>!

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-Dev mailing list