I got lots of feedback on the PEP at PyCon and the two main responses
were "heck yeah!" and "what does a class decorator look like?"
I've added some simple examples for the "what is?" and some other
examples that should help explain the "heck yeah" responses too.
I'm cc'ing [EMAIL PROTECTED] for a number assignment (or can I just
check it in myself?)
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.
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]_.
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.
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/
_______________________________________________
Python-3000 mailing list
[email protected]
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe:
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com