[Python-ideas] PEP 560 (second post)

Nick Coghlan ncoghlan at gmail.com
Fri Sep 29 02:57:47 EDT 2017


On 29 September 2017 at 08:04, Ivan Levkivskyi <levkivskyi at gmail.com> wrote:
> On 28 September 2017 at 08:27, Nick Coghlan <ncoghlan at gmail.com> wrote:
>>
>> On 27 September 2017 at 19:28, Ivan Levkivskyi <levkivskyi at gmail.com>
>> wrote:
>> > If an object that is not a class object appears in the bases of a class
>> > definition, the ``__subclass_base__`` is searched on it. If found,
>> > it is called with the original tuple of bases as an argument. If the
>> > result
>> > of the call is not ``None``, then it is substituted instead of this
>> > object.
>> > Otherwise (if the result is ``None``), the base is just removed. This is
>> > necessary to avoid inconsistent MRO errors, that are currently prevented
>> > by
>> > manipulations in ``GenericMeta.__new__``. After creating the class,
>> > the original bases are saved in ``__orig_bases__`` (currently this is
>> > also
>> > done by the metaclass).
>>
>> How would you feel about calling it "__mro_entry__", as a mnemonic for
>> "the substitute entry to use instead of this object when calculating a
>> subclass MRO"?
>
> I don't have any preferences for the name, __mro_entry__ sounds equally OK
> to me.

I'd propose changing it then, as searching for "Python mro entry" is
likely to get people to the right place faster than searching for
"Python subclass base".

>> I think the other thing that needs to be clarified is whether or not
>> the actual metaclass can expect to receive an already-resolved
>> sequence of MRO entries as its list of bases, or if it will need to
>> repeat the base resolution process executed while figuring out the
>> metaclass.
>>
>
> There are three points for discussion here:
>
> 1) It is necessary to make the bases resolution soon, before the metaclass
> is calculated. This is why I do this at the beginning of __build_class__ in
> the
> reference implementation.

Indeed.

> 2) Do we need to update type.__new__ to be able to accept non-classes as
> bases?
> I think no. One might be a bit surprised that
>
>     class C(Iterable[int]):
>         pass
>
> works, but
>
>     type('C', (Iterable[int],), {})
>
> fails with a metaclass conflict, but I think it is natural that static
> typing and dynamic
> class creation should not be used together. I propose to update
> ``type.__new__`` to just give
> a better error message explaining this.

+1 from me, since that avoids ever resolving the list of bases twice.

> 3) Do we need to update types.new_class and types.prepare_class?
> Here I am not sure. These functions are rather utility functions and are
> designed to
> mimic in Python what __build_class__ does in C. I think we might add
> types._update_bases
> that does the same as its C counterpart. Then we can update types.new_class
> and types.prepare_class
> like you proposed, this will preserve their current API while
> types.new_class will match behaviour of __build_class__

Your suggestion for `types.__new__` gave me a different idea: what if
`types.prepare_class` *also* just raised an error when given a
non-class as a nominal base class?

Then if we added `types.resolve_bases` as a public API, a full
reimplementation of `types.new_class` would now look like:

    resolved_bases = types.resolve_bases(bases)
    mcl, ns, updated_kwds = types.prepare_class(name, resolved_bases, kwds)
    exec_body(ns)
    ns["__orig_bases__"] = bases
    mcl(name, resolved_bases, ns, **updated_kwds)

That way, `types.new_class` would transparently switch to the new
behaviour, while clients of any other dynamic type creation API could
do their own base class resolution, even if the type creation API they
were using didn't implicitly support MRO entry resolution.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list