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

Guido van Rossum guido at python.org
Sun Jul 3 19:27:34 EDT 2016

On Sat, Jul 2, 2016 at 10:50 AM, Martin Teichmann
<lkb.teichmann at gmail.com> wrote:
> Hi list,
> so this is the next round for PEP 487. During the last round, most of
> the comments were in the direction that a two step approach for
> integrating into Python, first in pure Python, later in C, was not a
> great idea and everything should be in C directly. So I implemented it
> in C, put it onto the issue tracker here:
> http://bugs.python.org/issue27366, and also modified the PEP
> accordingly.

Thanks! Reviewing inline below.

> For those who had not been in the discussion, PEP 487 proposes to add
> two hooks, __init_subclass__ which is a classmethod called whenever a
> class is subclassed, and __set_owner__, a hook in descriptors which
> gets called once the class the descriptor is part of is created.
> While implementing PEP 487 I realized that there is and oddity in the
> type base class: type.__init__ forbids to use keyword arguments, even
> for the usual three arguments it has (name, base and dict), while
> type.__new__ allows for keyword arguments. As I plan to forward any
> keyword arguments to the new __init_subclass__, I stumbled over that.
> As I write in the PEP, I think it would be a good idea to forbid using
> keyword arguments for type.__new__ as well. But if people think this
> would be to big of a change, it would be possible to do it
> differently.

This is an area of exceeding subtlety (and also not very well
documented/specified, probably). I'd worry that changing anything here
might break some code. When a metaclass overrides neither __init__ nor
__new__, keyword args will not work because type.__init__ forbids
them. However when a metaclass overrides them and calls them using
super(), it's quite possible that someone ended up calling
super().__init__() with three positional args but super().__new__()
with keyword args, since the call sites are distinct (in the overrides
for __init__ and __new__ respectively).

What's your argument for changing this, apart from a desire for more regularity?

> Hoping for good comments
> Greetings
> Martin
> The PEP follows:
> PEP: 487
> Title: Simpler customisation of class creation
> Version: $Revision$
> Last-Modified: $Date$
> Author: Martin Teichmann <lkb.teichmann at gmail.com>,
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 27-Feb-2015
> Python-Version: 3.6
> Post-History: 27-Feb-2015, 5-Feb-2016, 24-Jun-2016, 2-Jul-2016
> Replaces: 422
> Abstract
> ========
> Currently, customising class creation requires the use of a custom metaclass.
> This custom metaclass then persists for the entire lifecycle of the class,
> creating the potential for spurious metaclass conflicts.
> This PEP proposes to instead support a wide range of customisation
> scenarios through a new ``__init_subclass__`` hook in the class body,
> and a hook to initialize attributes.
> The new mechanism should be easier to understand and use than
> implementing a custom metaclass, and thus should provide a gentler
> introduction to the full power Python's metaclass machinery.
> Background
> ==========
> Metaclasses are a powerful tool to customize class creation. They have,
> however, the problem that there is no automatic way to combine metaclasses.
> If one wants to use two metaclasses for a class, a new metaclass combining
> those two needs to be created, typically manually.
> This need often occurs as a surprise to a user: inheriting from two base
> classes coming from two different libraries suddenly raises the necessity
> to manually create a combined metaclass, where typically one is not
> interested in those details about the libraries at all. This becomes
> even worse if one library starts to make use of a metaclass which it
> has not done before. While the library itself continues to work perfectly,
> suddenly every code combining those classes with classes from another library
> fails.
> Proposal
> ========
> While there are many possible ways to use a metaclass, the vast majority
> of use cases falls into just three categories: some initialization code
> running after class creation, the initalization of descriptors and
> keeping the order in which class attributes were defined.
> The first two categories can easily be achieved by having simple hooks
> into the class creation:
> 1. An ``__init_subclass__`` hook that initializes
>    all subclasses of a given class.
> 2. upon class creation, a ``__set_owner__`` hook is called on all the
>    attribute (descriptors) defined in the class, and
> The third category is the topic of another PEP 520.
> As an example, the first use case looks as follows::
>    >>> class SpamBase:
>    ...    # this is implicitly a @classmethod
>    ...    def __init_subclass__(cls, **kwargs):
>    ...        cls.class_args = kwargs
>    ...        super().__init_subclass__(cls, **kwargs)
>    >>> class Spam(SpamBase, a=1, b="b"):
>    ...    pass
>    >>> Spam.class_args
>    {'a': 1, 'b': 'b'}
> The base class ``object`` contains an empty ``__init_subclass__``
> method which serves as an endpoint for cooperative multiple inheritance.
> Note that this method has no keyword arguments, meaning that all
> methods which are more specialized have to process all keyword
> arguments.

I'm confused. In the above example it would seem that the keyword args
{'a': 1, 'b': 2} are passed right on to super9).__init_subclass__().
Do you mean that it ignores all keyword args? Or that it has no
positional args? (Both of which would be consistent with the example.)

> This general proposal is not a new idea (it was first suggested for
> inclusion in the language definition `more than 10 years ago`_, and a
> similar mechanism has long been supported by `Zope's ExtensionClass`_),
> but the situation has changed sufficiently in recent years that
> the idea is worth reconsidering for inclusion.

Can you state exactly at which point during class initialization
__init_class__() is called? (Surely by now, having implemented it, you
know exactly where. :-)

[This is as far as I got reviewing when the weekend activities
interrupted me. In the light of ongoing discussion I'm posting this
now -- I'll continue later.]

--Guido van Rossum (python.org/~guido)

More information about the Python-Dev mailing list