[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