Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-21 Thread PJ Eby
On Sat, Apr 21, 2012 at 11:30 AM, Nick Coghlan  wrote:

> On Sun, Apr 22, 2012 at 12:55 AM, PJ Eby  wrote:
> > Personally, I think __build_class__ should be explicitly exposed and
> > supported, if for no other reason than that it allows one to re-implement
> > old-style __metaclass__ support in 2.x modules that rely on it...  and I
> > have a lot of those to port.  (Which is why I also think the convenience
> API
> > for PEP 3115-compatible class creation should actually call
> __build_class__
> > itself.  That way, if it's been replaced, then the replaced semantics
> would
> > *also* apply to dynamically-created classes.)
>
> No, we already have one replaceable-per-module PITA like that (i.e.
> __import__). I don't want to see us add another one.
>

Well, it's more like replacing than adding; __metaclass__ has this job in
2.x.  PEP 3115 removed what is (IMO) an important feature: the ability for
method-level decorators to affect the class, without needing user-specified
metaclasses or class decorators.

This is important for e.g. registering methods that are generic functions,
without requiring the addition of redundant metaclass or class-decorator
statements, and it's something that's possible in 2.x using __metaclass__,
but *not* possible under PEP 3115 without hooking __build_class__.
 Replacing builtins.__build_class__ allows the restoration of __metaclass__
support at the class level, which in turn allows porting 2.x code that uses
this facility.

To try to be more concrete, here's an example of sorts:

class Foo:
@decorate(blah, fah)
def widget(self, spam):
 ...

If @decorate needs access to the 'Foo' class object, this is not possible
under PEP 3115 without adding an explicit metaclass or class decorator to
support it.  And if you are using such method-level decorators from more
than one source, you will have to combine their class decorators or
metaclasses in some way to get this to work.  Further, if somebody forgets
to add the extra metaclass(es) and/or class decorator(s), things will
quietly break.

However, under 2.x, a straightforward solution is possible (well, to me
it's straightforward) : method decorators can replace the class'
__metaclass__ and chain to the previous one, if it existed.  It's like
giving method decorators a chance to *also* be a class decorator.

Without some *other* way to do this in 3.x, I don't have much of a choice
besides replacing __build_class__ to accomplish this use case.
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-21 Thread Nick Coghlan
On Sun, Apr 22, 2012 at 12:55 AM, PJ Eby  wrote:
> (Sorry I'm so late to this discussion.)
>
> I think that it's important to take into account the fact that PEP 3115
> doesn't require namespaces to implement anything more than __setitem__ and
> __getitem__ (with the latter not even needing to do anything but raise
> KeyError).
>
> Among other things, this means that .update() is right out as a
> general-purpose solution to initializing a 3115-compatible class: you have
> to loop and set items explicitly.  So, if we're providing helper functions,
> there should be a helper that handles this common case by taking the
> keywords (or perhaps an ordered sequence of pairs) and doing the looping for
> you.
>
> Of course, once you're doing that, you might as well implement it by passing
> a closure into __build_class__...

Yeah, the "operator.build_class" in the tracker issue ended up looking
a whole lot like the signature of CPython's __build_class__. The main
difference is that the class body evaluation argument moves to the end
and becomes optional in order to bring the first two arguments in line
with those of type(). The signature ends up being effectively:

def build_class(name, bases=(), kwds={}, exec_body=None):
...

Accepting an optional callback that is given the prepared namespace as
an argument just makes a lot more sense than either exposing a
separate prepare function or using the existing __build_class__
signature directly (which was designed with the compiler in mind, not
humans).

> Personally, I think __build_class__ should be explicitly exposed and
> supported, if for no other reason than that it allows one to re-implement
> old-style __metaclass__ support in 2.x modules that rely on it...  and I
> have a lot of those to port.  (Which is why I also think the convenience API
> for PEP 3115-compatible class creation should actually call __build_class__
> itself.  That way, if it's been replaced, then the replaced semantics would
> *also* apply to dynamically-created classes.)

No, we already have one replaceable-per-module PITA like that (i.e.
__import__). I don't want to see us add another one.

> Having other convenience functions that reimplement lower-level features
> than __build_class__ (like the prepare thing) sounds like a good idea, but I
> think we should encourage common cases to just call something that keeps the
> __setitem__ issue out of the way.
>
> Thoughts?

Agreed on the use of a callback to avoid making too many assumptions
about the API provided by the prepared namespace.

Definitely *not* agreed on making __build_class__ part of the language
spec (or even officially supporting people that decide to replace it
with their own alternative in CPython).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-21 Thread PJ Eby
(Sorry I'm so late to this discussion.)

I think that it's important to take into account the fact that PEP 3115
doesn't require namespaces to implement anything more than __setitem__ and
__getitem__ (with the latter not even needing to do anything but raise
KeyError).

Among other things, this means that .update() is right out as a
general-purpose solution to initializing a 3115-compatible class: you have
to loop and set items explicitly.  So, if we're providing helper functions,
there should be a helper that handles this common case by taking the
keywords (or perhaps an ordered sequence of pairs) and doing the looping
for you.

Of course, once you're doing that, you might as well implement it by
passing a closure into __build_class__...

More below:

On Sun, Apr 15, 2012 at 7:48 AM, Nick Coghlan  wrote:

>
> Yup, I believe that was my main objection to exposing __build_class__
> directly. There's no obligation for implementations to build a
> throwaway function to evaluate a class body.
>

Thing is, though, if an implementation is dynamic enough to be capable of
supporting PEP 3115 *at all*  (not to mention standard exec/eval
semantics), it's going to have no problem mimicking __build_class__.

I mean, to implement PEP 3115 namespaces, you *have* to support exec/eval
with arbitrary namespaces.  From that, it's only the tiniest of steps to
wrapping that exec/eval in a function object to pass to __build_class__.

Really, making that function is probably the *least* of the troubles an
alternate implementation is going to have with supporting PEP 3115 (by
far).  Hell, supporting *metaclasses* is the first big hurdle an alternate
implementation has to get over, followed by the exec/eval with arbitrary
namespaces.

Personally, I think __build_class__ should be explicitly exposed and
supported, if for no other reason than that it allows one to re-implement
old-style __metaclass__ support in 2.x modules that rely on it...  and I
have a lot of those to port.  (Which is why I also think the convenience
API for PEP 3115-compatible class creation should actually call
__build_class__ itself.  That way, if it's been replaced, then the replaced
semantics would *also* apply to dynamically-created classes.)

IOW, there'd be two functions: one that's basically "call __build_class__",
and the other that's "call __build_class__ with a convenience function to
inject these values into the prepared dictionary".

Having other convenience functions that reimplement lower-level features
than __build_class__ (like the prepare thing) sounds like a good idea, but
I think we should encourage common cases to just call something that keeps
the __setitem__ issue out of the way.

Thoughts?
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-16 Thread Daniel Urban
On Mon, Apr 16, 2012 at 04:17, Nick Coghlan  wrote:
> Sure, just create a new tracker issue and assign it to me. You already
> know better than most what the _prepare() step needs to do :)

I've created http://bugs.python.org/issue14588, and attached the first
version of a patch. I can't assign it to you, but you're on the nosy
list.

Thanks,
Daniel
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-15 Thread Nick Coghlan
On Mon, Apr 16, 2012 at 5:34 AM, Daniel Urban  wrote:
> On Sun, Apr 15, 2012 at 13:48, Nick Coghlan  wrote:
>> /me pages thoughts from 12 months ago back into brain...
>
> Sorry about that, I planned to do this earlier...

No worries - good to have someone following up on it, since it had
completely dropped off my own radar :)

>> No, I think we would want to expose the created namespace directly -
>> that way people can use update(), direct assigment, exec(), eval(), or
>> whatever other mechanism they choose to handle the task of populating
>> the namespace. However, a potentially cleaner way to do that might be
>> offer use an optional callback API rather than exposing a separate
>> public prepare() function. Something like:
>>
>>    def build_class(name, bases=(), kwds=None, eval_body=None):
>>        metaclass, ns = _prepare(name, bases, kwds)
>>        if eval_body is not None:
>>            eval_body(ns)
>>        return metaclass(name, bases, ns)
>
> That seems more flexible indeed. I will try to make a patch next week,
> if that's OK.

Sure, just create a new tracker issue and assign it to me. You already
know better than most what the _prepare() step needs to do :)

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-15 Thread Daniel Urban
On Sun, Apr 15, 2012 at 13:48, Nick Coghlan  wrote:
> /me pages thoughts from 12 months ago back into brain...

Sorry about that, I planned to do this earlier...

> On Sun, Apr 15, 2012 at 7:36 PM, Daniel Urban  wrote:
>> On Tue, Apr 19, 2011 at 16:10, Nick Coghlan  wrote:
>>> 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).
>
> Yup, I believe that was my main objection to exposing __build_class__
> directly. There's no obligation for implementations to build a
> throwaway function to evaluate a class body.
>
>> __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).
>
> True.
>
>>> 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))
>>>
>>> Thoughts?
>>
>> 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)
>
> No, I think we would want to expose the created namespace directly -
> that way people can use update(), direct assigment, exec(), eval(), or
> whatever other mechanism they choose to handle the task of populating
> the namespace. However, a potentially cleaner way to do that might be
> offer use an optional callback API rather than exposing a separate
> public prepare() function. Something like:
>
>    def build_class(name, bases=(), kwds=None, eval_body=None):
>        metaclass, ns = _prepare(name, bases, kwds)
>        if eval_body is not None:
>            eval_body(ns)
>        return metaclass(name, bases, ns)

That seems more flexible indeed. I will try to make a patch next week,
if that's OK.


Daniel
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-15 Thread Nick Coghlan
/me pages thoughts from 12 months ago back into brain...

On Sun, Apr 15, 2012 at 7:36 PM, Daniel Urban  wrote:
> On Tue, Apr 19, 2011 at 16:10, Nick Coghlan  wrote:
>> 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).

Yup, I believe that was my main objection to exposing __build_class__
directly. There's no obligation for implementations to build a
throwaway function to evaluate a class body.

> __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).

True.

>> 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))
>>
>> Thoughts?
>
> 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)

No, I think we would want to expose the created namespace directly -
that way people can use update(), direct assigment, exec(), eval(), or
whatever other mechanism they choose to handle the task of populating
the namespace. However, a potentially cleaner way to do that might be
offer use an optional callback API rather than exposing a separate
public prepare() function. Something like:

def build_class(name, bases=(), kwds=None, eval_body=None):
metaclass, ns = _prepare(name, bases, kwds)
if eval_body is not None:
eval_body(ns)
return metaclass(name, bases, ns)

Cheers,
Nick.

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] Providing a mechanism for PEP 3115 compliant dynamic class creation

2012-04-15 Thread Daniel Urban
On Tue, Apr 19, 2011 at 16:10, Nick Coghlan  wrote:
> In reviewing a fix for the metaclass calculation in __build_class__
> [1], 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))
>
> Thoughts?

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)


Daniel
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com