[Python-3000] PEP Draft: Class Decorators

Collin Winter collinw at gmail.com
Sat Mar 10 01:19:49 CET 2007


You need to run this through a spellchecker; I noticed at least a
dozen typos. Other comments inline.

Collin Winter

On 3/9/07, Jack Diederich <jackdied at jackdied.com> wrote:
> PEP: 3XXX
> Title: Class Decorators
> Version: 1
> Last-Modified: 28-Feb-2007
> Authors: Jack Diederich
> Implementation: SF#1671208
> Status: Draft
> Type: Standards Track
> Created: 26-Feb-2007
>
> Abstract
> ========
>
> Extending the decorator syntax to allow the decoration of classes.
>
> Rationale
> =========
>
> Class decorators have an identical signature to function decorators.
> The identity decorator is defined as
>
>     def identity(cls):
>       return cls
>
>     @identity
>     class Example:
>         pass
>
> To be useful class decorators should return a class-like thing but
> as with function decorators this is not enforced by the language.
>
> All the strong existing use cases are decorators that just register
> or annotate classes.
>
>     import myfactory
>
>     @myfactory.register
>     class MyClass:
>         pass
>
> Decorators are stackable so more than one registration can happen.
>
>     import myfactory
>     import mycron
>
>     @mycron.schedule('nightly')
>     @myfactory.register
>     class MyClass:
>         pass
>
> The same effect is currently possible by making a functioon call
> after class definition time but as with function decorators decorating
> the class moves an important piece of information (i.e. 'this class
> participates in a factory') to the top.

"but as with function decorators decorating" -> "but, as with function
decorators, decorating" (adding commas makes it easier to parse).

> Decorators vs Metaclasses
> =========================
>
> Decorators are executed once for each time they appear.  Metaclasses
> are executed for every class that defines a metaclass and every class
> that inherits from a class that defines a metaclass.
>
> Decorators are also easilly stackable because of their takes-a-class
> returns-a-class signature.  Metaclasses are combinable too but with
> their richer interface it requires more trouble (see Alex Martelli's
> PyCon 2005 talk[6]).  Even with some tricks the combined metaclasses
> may need to coordinate who owns __new__ or __init__.
>
> Note that class decorators, like metaclasses, aren't garunteed to
> return the same class they were passed.
>
> History and Implementation
> ==========================
>
> Class decorators were originally proposed alongside function decorators
> in PEP318 [1]_ and were rejected by Guido [2]_ for lack of use cases.
> Two years later he saw a use case he liked and gave the go-ahead for a
> PEP and patch [3]_.

I'd still prefer it if this use case -- which is so central to the PEP
-- were provided in the PEP itself.

> The current patch is loosely based on a pre-2.4 patch [4]_ and updated to
> use the newer 2.5 (and 3k) AST.
>
> Grammar/Grammar is changed from
>
>    funcdef: [decorators] 'def' NAME parameters ['->' test] ':' suite
>
> to
>
>     decorated_thing: decorators (classdef | funcdef)
>     funcdef: 'def' NAME parameters ['->' test] ':' suite
>
> "decorated_thing"s are premitted everywhere that funcdef and classdef
> are premitted.
>
> An alternate change to the grammar would be to make a 'decoratable_thing'
> which would include funcdefs and classdefs even if they had no decorators.

Is it "decorated_thing" or "decoratable_thing"?

> Motivation
> ==========
>
> Below is an actual production metaclass followed by its class
> decorator equivalent.  It is used to produce factory classes
> which each keep track of which account_id web forms are associated
> with.
>
> class Register(type):
>   """A tiny metaclass to help classes register themselves automagically"""
>
>   def __init__(cls, name, bases, dict):
>     if 'DO_NOT_REGISTER' in dict: # this is a non-concrete class
>         pass
>     elif object not in bases: # this is yet another metaclass
>         pass
>     elif 'register' in dict: # this is a top level factory class
>       setattr(cls, 'register', staticmethod(dict['register']))p
>     else: # typical case, register with the factory
>       cls.register(name, cls)
>     return
>
> class FormFactory(object):
>     id_to_form = {} # { account_id : { form_id : form_class } }
>     def register(cls):
>         FormFactory.id_to_form.setdefault(cls.account_id, {})
>         assert cls.form_id not in FormFactory.id_to_form[cls.account_id], cls.form_id
>         FormFactory.id_to_form[cls.account_id][cls.form_id] = cls
>
> class FormCaputreA(FormFactory, Form):
>     account_id = 17
>     form_id = 3
>     # .. cgi param to sql mapping defined, etc
>
> The decorated version eliminates the metaclass and loses some of
> the if-else checks because it won't be applied by the programmer to
> intermediate classes, metaclasses, or factories.
>
> class FormFactory(Form):
>     id_to_form = {} # { account_id : { form_id : form_class } }
>     @staticmethod
>     def register(cls, account_id, form_id):
>         FormFactory.id_to_form.setdefault(cls.account_id, {})
>         assert form_id not in FormFactory.id_to_form[cls.account_id], form_id
>         FormFactory.id_to_form[cls.account_id][form_id] = cls
>         # return the class unchanged
>         return cls
>
> @FormFactory.register(account_id=17, form_id=3)
> class FormCaptureA(object):
>     # .. cgi param to sql mapping defined, etc
>
> References
> ==========
> If you enjoyed this PEP you might also enjoy:
>
> .. [1] PEP 318, "Decorators for Functions and Methods"
>   http://www.python.org/dev/peps/pep-0318/
>
> .. [2] Class decorators rejection
>   http://mail.python.org/pipermail/python-dev/2004-March/043458.html
>
> .. [3] Class decorator go-ahead
>   http://mail.python.org/pipermail/python-dev/2006-March/062942.html
>
> .. [4] 2.4 class decorator patch
>   http://python.org/sf/1007991
>
> .. [5] 3.x class decorator patch
>   http://python.org/sf/1671208
>
> .. [6] Alex Martelli's PyCon 2005 "Python's Black Magic"
>   http://www.python.org/pycon/2005/papers/36/


More information about the Python-3000 mailing list