[Python-ideas] PEP 560 (second post)

Nick Coghlan ncoghlan at gmail.com
Thu Sep 28 02:27:09 EDT 2017


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

The name of "__subclass_base__" is still the part of this proposal
that bothers me the most. I do know what it means, but "the class to
use when this is listed as a base class for a subclass" is a genuinely
awkward noun phrase.

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"?

Then the MRO calculation process would be:

* generate "resolved_bases" from "orig_bases" (if any of them define
"__mro_entry__")
* use the existing MRO calculation process with resolved_bases as the
input instead of orig_bases

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.

You can see the implications of that question most clearly when
looking at the dynamic type creation API in the types module, where we
offer both:

    # All inclusive with callback
    types.new_class(name, bases, kwds, exec_body)

    # Separate preparation phase
    mcl, ns, updated_kwds = types.prepare_class(name, bases, kwds)
    exec_body(ns)
    mcl(name, bases, ns, **updated_kwds)

If we expect the metaclass to receive an already resolved sequence of
bases, then we'll need to update the usage expectations for the latter
API to look something like:

    mcl, ns, updated_kwds = types.prepare_class(name, bases, kwds)
    resolved_bases = ns.pop("__resolved_bases__", bases)
    # We leave ns["__orig_bases__"] set
    exec_body(ns)
    mcl(name, resolved_bases, ns, **updated_kwds)

By contrast, if we decide that we're going do the full MRO resolution
twice, then every metaclass will need to be updated to resolve the
bases correctly (and make sure to use "cls.__bases__" or "cls.__mro__"
after call up to their parent metaclass to actually create the class
object).

Cheers,
Nick.

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


More information about the Python-ideas mailing list