On Tue, Apr 19, 2011 at 16:10, Nick Coghlan email@example.com wrote:
In reviewing a fix for the metaclass calculation in __build_class__ , I realised that PEP 3115 poses a potential problem for the common practice of using "type(name, bases, ns)" for dynamic class creation.
Specifically, if one of the base classes has a metaclass with a significant __prepare__() method, then the current idiom will do the wrong thing (and most likely fail as a result), since "ns" will probably be an ordinary dictionary instead of whatever __prepare__() would have returned.
Initially I was going to suggest making __build_class__ part of the language definition rather than a CPython implementation detail, but then I realised that various CPython specific elements in its signature made that a bad idea.
Are you referring to the first 'func' argument? (Which is basically the body of the "class" statement, if I'm not mistaken).
Instead, I'm thinking along the lines of an "operator.prepare(metaclass, bases)" function that does the metaclass calculation dance, invoking __prepare__() and returning the result if it exists, otherwise returning an ordinary dict. Under the hood we would refactor this so that operator.prepare and __build_class__ were using a shared implementation of the functionality at the C level - it may even be advisable to expose that implementation via the C API as PyType_PrepareNamespace().
__prepare__ also needs the name and optional keyword arguments. So it probably should be something like "operator.prepare(name, bases, metaclass, **kw)". But this way it would need almost the same arguments as __build_class__(func, name, *bases, metaclass=None, **kwds).
The correct idiom for dynamic type creation in a PEP 3115 world would then be:
from operator import prepare cls = type(name, bases, prepare(type, bases))
When creating a dynamic type, we may want to do it with a non-empty namespace. Maybe like this (with the extra arguments mentioned above):
from operator import prepare ns = prepare(name, bases, type, **kwargs) ns.update(my_ns) # add the attributes we want cls = type(name, bases, ns)
What about an "operator.build_class(name, bases, ns, **kw)" function? It would work like this:
def build_class(name, bases, ns, **kw): metaclass = kw.pop('metaclass', type) pns = prepare(name, bases, metaclass, **kw) pns.update(ns) return metaclass(name, bases, pns)
(Where 'prepare' is the same as above). This way we wouldn't even need to make 'prepare' public, and the new way to create a dynamic type would be:
from operator import build_class cls = build_class(name, bases, ns, **my_kwargs)