[Python-3000] PEP Draft: Class Decorators (original) (raw)
Jack Diederich jackdied at jackdied.com
Sat Mar 10 01:10:54 CET 2007
- Previous message: [Python-3000] [Python-Dev] Policy Decisions, Judgment Calls, and Backwards Compatibility (was Re: splitext('.cshrc'))
- Next message: [Python-3000] PEP Draft: Class Decorators
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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 peps at python.org 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/
- Previous message: [Python-3000] [Python-Dev] Policy Decisions, Judgment Calls, and Backwards Compatibility (was Re: splitext('.cshrc'))
- Next message: [Python-3000] PEP Draft: Class Decorators
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]