RE: [Python-Dev] @decoration of classes

Josiah Carlson writes: [... stuff about reST and TeX ...]
Hmm... the only bit that I found particularly interesting there was the bit where you mention that you've used class decorators (without the syntax) before. What did you use them for? After all, the current state of things is "don't bother implementing class decorators because there is no compelling use case". If you've got some sample use cases, what were they? For my own part, I observe the following. Because a function decorator acts after the function object is created, there are limits to what it can do to the function. It can add some markup (eg: set properties or doc strings). It can "hook" either before or after the function is called. Or it can "veto" the function call and do something else instead. In the case of function calls, these are pretty much the interesting things you would want to do. Similarly, because a class decorator acts after the class is created there are limits on what it can do. It can modify the class object (replacing methods and such). It can add markup. It can replace the class with another (perhaps a proxy or some such). But most of these are things which are more easily done by a metaclass... and all of them *can* be done by metaclasses. The only noticable advantage that I see to class decorators over metaclasses is that there's a more straightforward way to combine them. And I'm not sure that combining metaclasses (or class decorators) is something I want to encourage. So I'm inclined to use different tools for modifying functions and modifying classes because the ways you want to modify them are different, and decorators are "tuned" to what people normally want to do with functions (like simple wrapping) while metaclasses are "tuned" to what people normally want to do with classes (like support for inheritance. But a few *good* use cases would change my mind. -- Michael Chermside

Michael Chermside <mcherm@mcherm.com> wrote:
99% of my use cases have been of the form of decorating all of the methods of a class at once (optionally excluding __init__ and __new__) using @sync or binding constants [1].
Unless one is willing to rewrite the bytecode; in which case things like Raymond's binding constants decorator or even the inline decorator (that I found and then lost) are possible and useful.
Indeed, though people do combine metaclasses (or at least attempt to do so), often in strange, wonderful, and not so wonderful ways. I actually learned metaclasses because someone asked a question about combining them, and I wanted to understand the question (if not answer it).
While one can call what is being done with decorators "simple wrapping", I believe it goes a bit beyond that. With properly defined @accepts and @returns decorators, certainly one can perform runtime validation of call/return, but with a proper validation mechanism, one can do compile-time type inference and call type validation/verification (PyChecker with types, not just number of arguments). This kind of thing would give us the (desired by some) optional typing information for passed and returned arguments. Of course none of the above is a new idea, but I'm pointing it out so that we remember that there already exists a mechanism for doing this without further syntax changes to the language (someone once offered "def f(int:foo=3)" or some such, and this along with may other things are possible with decorators). As a side-note, I personally think of function/method decoration as a kind of subclassing of a function (as I have mentioned here before[2]); and if it were treated as such (with an attribute that references the previous function/method), one wouldn't need to copy __doc__, __name__, etc., attributes onto certain decorated functions. As for what most people use metaclasses for, I don't know, I try not to use metaclasses if possible (I haven't had a _compelling_ need so far), and don't use class decoration very often either.
But a few *good* use cases would change my mind.
As I have said from the beginning, I don't believe any of my use cases are compelling, as I don't believe that sprinkles on a banana split are compelling. I also don't believe that someone is going to come forward with a compelling use case when compared against function/method decorators (like PyObjC wrapping, @accepts, @returns, @dispatch, @memoize, @synchronize, @classmethod, @staticmethod, ...), as I don't believe that even metaclasses are as compelling as function/method decoration. With that said; if it is there, people will use it. Someone will even post on their blog about how it is the best thing ever, or even how it ruined the language. So be it. In any case, I've spent more time writing emails about this particular topic than I will ever see returned by using class decoration syntax myself (over some equivalent method), so this is probably my last email on the subject. - Josiah [1] - Raymond's recipie for binding constants at compile time. A user also provides a metaclass version, but I prefer the class decoration. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277940 [2] - [Python-Dev] decorator support http://mail.python.org/pipermail/python-dev/2004-September/048631.html

Michael Chermside wrote:
Until this weekend, I really had no idea what a good use case for class decorators would look like. However, I stumbled upon something interesting. I was refactoring Kirby Urner's "hypertoons" code this weekend and hit upon an interesting use for decorators. On reflection, this might well be a candidate for class decorators. The unusual thing is that it does nothing to the decorated function; it simply stores it in a data structure. The "converter" decorator builder can be used for data values, including classes, but there is an advantage to having them at the top of the definition. Using such decorators looks like: @converter('inch', 'foot') def someconversion(... @converter('foot', 'yard') def anotherconversion(... @converter('yard', 'furlong') def yetanotherconversion(... Classes can be put into the data structures with: converter('flour', 'bread')(BakingClass) _But_ (at least for the app I was fiddling with) decorating at the top of declaration helps show the purpose of the class. Have a look at: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/393010 and see what you think. -- Scott David Daniels Scott.Daniels@Acm.Org

On Mon, Mar 28, 2005 at 09:25:18AM -0800, Michael Chermside wrote:
Metaclasses are a muddle because they can do everything a class can do and more, since metclasses are to classes as classes are to objects.
From the bottom up:
objects: get magic methods from their class can manipulate non-magic methods and attributes on a per-instance basis classes: get magic methods from their metaclass can create magic methods and attributes used by objects. Plus anything objects can do metaclasses: can create magic methods of a class can define class methods and attributes Plus anything a class can do. Metaclasses are a muddle because we (the royal we) are used to doing most things at the class level. Defining class methods in a metaclass like this probably isn't your regular style
I'm happy using classmethod instead of writing a metaclass everytime I need a classmethod. I think everyone is class-centric, or we could define static methods using class MetaK(type): def foo(): pass foo = metaclassmethod(foo) # class method of a type is a static method? Since I've never wanted to set a type's __repr__ I use metaclasses as a handy place to do mundane class-level manipulations. I don't actually need to do type level manipulations. So that's why I like class decorators, it lets me push type level manipulations (manipulating a class from its type's __init__ or __new__) down to the class level where my brain normally hangs out. I just want to change an object's behavior and I'd welcome a chance to do it by manipulating the class and not the class's class (which is the object's class's class, yikes!) -jackdied ps, I tried to raise a simliar point at PyCon during Alex Martelli's Q&A but got flustered and screwed it all up.

Michael Chermside wrote:
The area where I can see an overlap is in those decorators which, rather than altering the behaviour of the function itself, serve to register it with an external framework of some description. At the moment, a factory function that is actually implemented as a function can be registered with such a system using an @decorator. If you use the __new__/__init__ methods of a class as your factory, however, you need to either post-decorate the class or create a metaclass that performs the registration. It would be nice if decorators that worked for any callable (like the registration example) could be easily used with classes as well as standard functions. (The adaptation PEP's adapter registry is an actual situation that comes to mind) # A decorator that does not alter its argument def register(callable): # Register the callable somewhere ... return callable # Decorated factory function @register def factory(): pass # Post-decorated class class factory: pass register(factory) # Metaclass class RegisteredType(type): def __init__(self, name, bases, dict): super(self, RegisteredType).__init__(self, name, bases, dict) register(self) class factory: __metaclass__ = RegisteredType # Class decorator (not currently possible) @register class factory: pass PJE's example of moving the decoration near the top of the class definition without allowing class decoration contains an important caveat: it requires that the decorators be written to support doing that. Allowing class decoration means any appropriate decorators can be used, unmodified, to affect classes as well as functions. Cheers, Nick. -- Nick Coghlan | ncoghlan@email.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net

At 10:05 AM 3/31/05 +1000, Nick Coghlan wrote:
Yeah, but that can be trivially worked around using a 'decorate' function, e.g.: from protocols.advice import addClassAdvisor def decorate(*decorators): decorators = list(decorators)[::-1] def callback(cls): for dec in decorators: cls = cls(dec) return cls addClassAdvisor(callback) class SomeClass: decorate(dec1,dec2,...)

Michael Chermside <mcherm@mcherm.com> wrote:
99% of my use cases have been of the form of decorating all of the methods of a class at once (optionally excluding __init__ and __new__) using @sync or binding constants [1].
Unless one is willing to rewrite the bytecode; in which case things like Raymond's binding constants decorator or even the inline decorator (that I found and then lost) are possible and useful.
Indeed, though people do combine metaclasses (or at least attempt to do so), often in strange, wonderful, and not so wonderful ways. I actually learned metaclasses because someone asked a question about combining them, and I wanted to understand the question (if not answer it).
While one can call what is being done with decorators "simple wrapping", I believe it goes a bit beyond that. With properly defined @accepts and @returns decorators, certainly one can perform runtime validation of call/return, but with a proper validation mechanism, one can do compile-time type inference and call type validation/verification (PyChecker with types, not just number of arguments). This kind of thing would give us the (desired by some) optional typing information for passed and returned arguments. Of course none of the above is a new idea, but I'm pointing it out so that we remember that there already exists a mechanism for doing this without further syntax changes to the language (someone once offered "def f(int:foo=3)" or some such, and this along with may other things are possible with decorators). As a side-note, I personally think of function/method decoration as a kind of subclassing of a function (as I have mentioned here before[2]); and if it were treated as such (with an attribute that references the previous function/method), one wouldn't need to copy __doc__, __name__, etc., attributes onto certain decorated functions. As for what most people use metaclasses for, I don't know, I try not to use metaclasses if possible (I haven't had a _compelling_ need so far), and don't use class decoration very often either.
But a few *good* use cases would change my mind.
As I have said from the beginning, I don't believe any of my use cases are compelling, as I don't believe that sprinkles on a banana split are compelling. I also don't believe that someone is going to come forward with a compelling use case when compared against function/method decorators (like PyObjC wrapping, @accepts, @returns, @dispatch, @memoize, @synchronize, @classmethod, @staticmethod, ...), as I don't believe that even metaclasses are as compelling as function/method decoration. With that said; if it is there, people will use it. Someone will even post on their blog about how it is the best thing ever, or even how it ruined the language. So be it. In any case, I've spent more time writing emails about this particular topic than I will ever see returned by using class decoration syntax myself (over some equivalent method), so this is probably my last email on the subject. - Josiah [1] - Raymond's recipie for binding constants at compile time. A user also provides a metaclass version, but I prefer the class decoration. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277940 [2] - [Python-Dev] decorator support http://mail.python.org/pipermail/python-dev/2004-September/048631.html

Michael Chermside wrote:
Until this weekend, I really had no idea what a good use case for class decorators would look like. However, I stumbled upon something interesting. I was refactoring Kirby Urner's "hypertoons" code this weekend and hit upon an interesting use for decorators. On reflection, this might well be a candidate for class decorators. The unusual thing is that it does nothing to the decorated function; it simply stores it in a data structure. The "converter" decorator builder can be used for data values, including classes, but there is an advantage to having them at the top of the definition. Using such decorators looks like: @converter('inch', 'foot') def someconversion(... @converter('foot', 'yard') def anotherconversion(... @converter('yard', 'furlong') def yetanotherconversion(... Classes can be put into the data structures with: converter('flour', 'bread')(BakingClass) _But_ (at least for the app I was fiddling with) decorating at the top of declaration helps show the purpose of the class. Have a look at: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/393010 and see what you think. -- Scott David Daniels Scott.Daniels@Acm.Org

On Mon, Mar 28, 2005 at 09:25:18AM -0800, Michael Chermside wrote:
Metaclasses are a muddle because they can do everything a class can do and more, since metclasses are to classes as classes are to objects.
From the bottom up:
objects: get magic methods from their class can manipulate non-magic methods and attributes on a per-instance basis classes: get magic methods from their metaclass can create magic methods and attributes used by objects. Plus anything objects can do metaclasses: can create magic methods of a class can define class methods and attributes Plus anything a class can do. Metaclasses are a muddle because we (the royal we) are used to doing most things at the class level. Defining class methods in a metaclass like this probably isn't your regular style
I'm happy using classmethod instead of writing a metaclass everytime I need a classmethod. I think everyone is class-centric, or we could define static methods using class MetaK(type): def foo(): pass foo = metaclassmethod(foo) # class method of a type is a static method? Since I've never wanted to set a type's __repr__ I use metaclasses as a handy place to do mundane class-level manipulations. I don't actually need to do type level manipulations. So that's why I like class decorators, it lets me push type level manipulations (manipulating a class from its type's __init__ or __new__) down to the class level where my brain normally hangs out. I just want to change an object's behavior and I'd welcome a chance to do it by manipulating the class and not the class's class (which is the object's class's class, yikes!) -jackdied ps, I tried to raise a simliar point at PyCon during Alex Martelli's Q&A but got flustered and screwed it all up.

Michael Chermside wrote:
The area where I can see an overlap is in those decorators which, rather than altering the behaviour of the function itself, serve to register it with an external framework of some description. At the moment, a factory function that is actually implemented as a function can be registered with such a system using an @decorator. If you use the __new__/__init__ methods of a class as your factory, however, you need to either post-decorate the class or create a metaclass that performs the registration. It would be nice if decorators that worked for any callable (like the registration example) could be easily used with classes as well as standard functions. (The adaptation PEP's adapter registry is an actual situation that comes to mind) # A decorator that does not alter its argument def register(callable): # Register the callable somewhere ... return callable # Decorated factory function @register def factory(): pass # Post-decorated class class factory: pass register(factory) # Metaclass class RegisteredType(type): def __init__(self, name, bases, dict): super(self, RegisteredType).__init__(self, name, bases, dict) register(self) class factory: __metaclass__ = RegisteredType # Class decorator (not currently possible) @register class factory: pass PJE's example of moving the decoration near the top of the class definition without allowing class decoration contains an important caveat: it requires that the decorators be written to support doing that. Allowing class decoration means any appropriate decorators can be used, unmodified, to affect classes as well as functions. Cheers, Nick. -- Nick Coghlan | ncoghlan@email.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.skystorm.net

At 10:05 AM 3/31/05 +1000, Nick Coghlan wrote:
Yeah, but that can be trivially worked around using a 'decorate' function, e.g.: from protocols.advice import addClassAdvisor def decorate(*decorators): decorators = list(decorators)[::-1] def callback(cls): for dec in decorators: cls = cls(dec) return cls addClassAdvisor(callback) class SomeClass: decorate(dec1,dec2,...)
participants (6)
-
Jack Diederich
-
Josiah Carlson
-
Michael Chermside
-
Nick Coghlan
-
Phillip J. Eby
-
Scott David Daniels