[Python-Dev] Submitting PEP 422 (Simple class initialization hook) for pronouncement (original) (raw)
Nick Coghlan ncoghlan at gmail.com
Mon Feb 11 11:33:24 CET 2013
- Previous message: [Python-Dev] Submitting PEP 422 (Simple class initialization hook) for pronouncement
- Next message: [Python-Dev] Submitting PEP 422 (Simple class initialization hook) for pronouncement
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
On Mon, Feb 11, 2013 at 7:41 AM, PJ Eby <pje at telecommunity.com> wrote:
On Sun, Feb 10, 2013 at 11:48 AM, Stefan Behnel <stefanml at behnel.de> wrote:
So, the way to explain it to users would be 1) don't use it, 2) if you really need to do something to a class, use a decorator, 3) if you need to decide dynamically what to do, define initclass() and 4) don't forget to call super's initclass() in that case, and 5) only if you need to do something substantially more involved and know what you're doing, use a metaclass. I'd revise that to: 1) if there's no harm in forgetting to decorate a subclass, use a class decorator 2) if you want to ensure that a modification is applied to every subclass of a single common base class, define initclass (and always call its super) 3) If you need to make the class object act differently (not just initialize it or trigger some other side-effect at creation time), or if you want the class suite to return some other kind of object, you'll need a metaclass.
I like that. Perhaps the PEP should propose some additional guidance in PEP 8 regarding class based metaprogramming?
Essentially, this change fixes a hole in class decorators that doesn't exist with function decorators: if you need the decoration applied to subclasses, you can end up with silent failures right now. Conversely, if you try prevent such failures using a metaclass, you not only have a big hill to climb, but the resulting code will be vulnerable to metaclass conflicts.
The proposed solution neatly fixes both of these problems, providing One Obvious Way to do subclass initialization.
I also realised last night that one significant benefit of cleanly separating class creation from class initialisation (as new and init separate instance creation and initialisation) is the ability to create a shared metaclass that just changes the namespace type with prepare, and then use init_class to control what you do with it.
Here's the more extended example I'm now considering adding to the PEP in order to show the improved composability the PEP offers (writing the below example with only metaclasses would be... challenging). It's still a toy example, but I don't believe there is any non-toy use case for metaclass composition that is going to be short enough to fit in a PEP:
# Define a metaclass as in Python 3.3 and earlier
import collections
class OrderedMeta(type):
def __prepare__(self, *args, **kwds):
return collections.OrderedDict()
# Won't be needed if we add a noop __init_class__ to type
def __init_class__(cls):
pass
class OrderedClass(metaclass=OrderedMeta):
pass
# Easily supplement the metaclass behaviour in a class definition
class SimpleRecord(OrderedClass):
"""Simple ordered record type (inheritance not supported)"""
@classmethod
def __init_class__(cls):
super().__init_class__()
cls.__fields = fields = []
for attr, obj in cls.__dict__.items():
if attr.startswith("_") or callable(obj):
continue
fields.append(attr)
def __init__(self, *values):
super().__init__(*values)
for attr, obj in zip(self.__fields, values):
setattr(self, attr, obj)
def to_dict(self):
fields = ((k, getattr(self, k)) for k in self.__fields)
return collections.OrderedDict(fields)
# Supplement the metaclass differently in another class definition
class InheritableRecord(OrderedClass):
"""More complex record type that supports inheritance"""
@classmethod
def __init_class__(cls):
super().__init_class__()
cls.__fields = fields = []
for mro_cls in cls.mro():
for attr, obj in cls.__dict__.items():
if attr.startswith("_") or callable(obj):
continue
fields.append(attr)
def __init__(self, *values):
super().__init__(*values)
for attr, obj in zip(self.__fields, values):
setattr(self, attr, obj)
def to_dict(self):
fields = ((k, getattr(self, k)) for k in self.__fields)
return collections.OrderedDict(fields)
# Compared to custom metaclasses, composition is much simpler
class ConfusedRecord(InheritableRecord, SimpleRecord):
"""Odd record type, only included to demonstrate composition"""
# to_dict is inherited from InheritableRecord
def to_simple_dict(self):
return SimpleRecord.to_dict(self)
Perhaps it would sweeten the deal if the PEP also provided types.OrderedMeta and types.OrderedClass, such that inheriting from types.OrderedClass and defining init_class became the one-obvious-way to do order dependent class bodies? (I checked, we can make types depend on collections without a circular dependency. We would need to fix the increasingly inaccurate docstring, though)
Cheers, Nick.
-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
- Previous message: [Python-Dev] Submitting PEP 422 (Simple class initialization hook) for pronouncement
- Next message: [Python-Dev] Submitting PEP 422 (Simple class initialization hook) for pronouncement
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]