On 27 September 2017 at 19:28, Ivan Levkivskyi email@example.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).