[Python-Dev] PEP487: Simpler customization of class creation

Nick Coghlan ncoghlan at gmail.com
Thu Jul 28 09:53:45 EDT 2016


On 28 July 2016 at 23:12, Joao S. O. Bueno <jsbueno at python.org.br> wrote:
>  Although I know it is not straightforward to implement (as the
> "metaclass" parameter is not passed to the metaclass __new__ or
> __init__), wouldn't it make sense to make it be passed to
> __init_subclass__ just like all other keywords?  (the default
> __init_subclass__ then could swallow it, if it reaches there).
>
> I am putting the question now, because it is a matter of "now" or
> "never" - I can see it can does make sense if it is not passed down.

It would complicate the implementation, and has the potential to be
confusing (since the explicit metaclass hint and the actual metaclass
aren't guaranteed to be the same), so I don't think it makes sense to
pass it down. I'll make sure we note it in the documentation for the
new protocol method, though.

> Anyway, do you have any remarks on the first issue I raised? About
> __init_subclass__ being called in the class it is defined in, not just
> on it's descendant subclasses?

That's already covered in the PEP:
https://www.python.org/dev/peps/pep-0487/#calling-the-hook-on-the-class-itself

We want to make it easy for mixin classes to use __init_subclass__ to
define "required attributes" on subclasses, and that's straightforward
with the current definition:

    class MyMixin:
        def __init_subclass__(cls, **kwargs):
            super().__init_subclass__(**kwargs)
            if not hasattr(cls, "mixin_required_attribute"):
                raise TypeError(f"Subclasses of {__class__} must
define a 'mixin_required_attribute' attribute")

If you actually do want the init_subclass__ method to also run on the
"base" class, then you'll need to tweak the class hierarchy a bit to
look like:

    class _PrivateInitBase:
        def __init_subclass__(cls, **kwargs):
            super().__init_subclass__(**kwargs)
            ...

    def MyOriginalClass(_PrivateInitBase):
        ...

(You don't want to call __init_subclass__ from a class decorator, as
that would call any parent __init_subclass__ implementations a second
time)

By contrast, when we had the default the other way around, opting
*out* of self-application required boilerplate inside of
__init_subclass__ to special case the situation where "cls is
__class__":

    class MyMixin:
        def __init_subclass__(cls, **kwargs):
            super().__init_subclass__(**kwargs)
            if cls is __class__:
                return # Don't init the base class
            if not getattr(cls, "mixin_required_attribute", None) is None:
                raise TypeError(f"Subclasses of {__class__} must
define a non-None 'mixin_required_attribute' attribute")

This raises exciting new opportunities for subtle bugs, like bailing
out *before* calling the parent __init_subclass__ method, and then
figure out that a later error from an apparently unrelated method is
because your __init_subclass__ implementation is buggy.

There's still an opportunity for bugs with the current design decision
(folks expecting __init_subclass__ to be called on the class defining
it when that isn't the case), but they should be relatively shallow
ones, and once people learn the rule that __init_subclass__ is only
called on *strict* subclasses, it's a pretty easy behaviour to
remember.

Cheers,
Nick.

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


More information about the Python-Dev mailing list