Mailing List Archive

Make __mro_entries__ mandatory for non-types
Currently the class can inherit from arbitrary objects, not only types.
If the object was not designed for this you can get a mysterious
mistake, e g.:

>>> class A(1): ...
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() takes at most 2 arguments (3 given)

If the object's constructor is compatible with type constructor by
accident you can an unexpected result. To prevent this we usually add an
explicit __mro_entries__() method which raises an exception.

I propose to make __mro_entries__() mandatory if a base is not a type
instance (with a deprecation period).

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/QDFG65U5ILB7LISC7UL2WFGQIAL2DMUV/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
On Sat, Mar 05, 2022 at 11:27:44AM +0200, Serhiy Storchaka wrote:

> Currently the class can inherit from arbitrary objects, not only types.

Is that intentionally supported?

I know that metaclasses do not have to be actual classes, they can be
any callable with the correct signature, but I didn't know that classes
can inherit from non-classes.

--
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/K44D3H2L2B4KNKMYURZHL5OOSOGDOAY6/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
On Sat, Mar 05, 2022 at 04:42:55PM -0000, Jason Madden wrote:

> zope.interface relies on this behaviour.

The example you give shows that Interface is a class. It merely has a
metaclass which is not `type`. (I presume that is what's going on
behind the scenes.)

I'm asking about the example that Serhiy shows, where a class inherits
from something which is not a class at all. In his example, the base is
1, although it gives a TypeError. I'm asking if that sort of thing is
suppposed to work, and if so, how?


--
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/E2IWXPFY32R6JS22XXFORMNQTI4S6AOK/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
Steven D'Aprano wrote:
> On Sat, Mar 05, 2022 at 11:27:44AM +0200, Serhiy Storchaka wrote:
> > Currently the class can inherit from arbitrary objects, not only types.
> > Is that intentionally supported?
> I know that metaclasses do not have to be actual classes, they can be
> any callable with the correct signature, but I didn't know that classes
> can inherit from non-classes.

zope.interface relies on this behaviour.

py> from zope.interface import Interface
py> class IFoo(Interface):
... def a_method():
... """Does stuff"""
...
py> type(Interface)
<class 'zope.interface.interface.InterfaceClass'>
py> type(IFoo)
<class 'zope.interface.interface.InterfaceClass'>
py> isinstance(IFoo, type)
False
py> isinstance(Interface, type)
False
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EOEFTHD4POWRCT7FGBE5WT6IZGZNG2LX/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
Good to hear from you, Serhiy. I hope you and your family are safe.

On Sat, Mar 5, 2022 at 1:29 AM Serhiy Storchaka <storchaka@gmail.com> wrote:

> Currently the class can inherit from arbitrary objects, not only types.
> If the object was not designed for this you can get a mysterious
> mistake, e g.:
>
> >>> class A(1): ...
> ...
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: int() takes at most 2 arguments (3 given)
>
> If the object's constructor is compatible with type constructor by
> accident you can an unexpected result. To prevent this we usually add an
> explicit __mro_entries__() method which raises an exception.
>
> I propose to make __mro_entries__() mandatory if a base is not a type
> instance (with a deprecation period).
>

Funny, I was just thinking about this (reviewing Brett's draft for the next
installment of his desugaring Python blog).

How common is it that people get confused by an error message like from
`class A(1)`? I'm not sure that it's common enough to weigh down the
esoteric use cases (inheriting from non-class objects) with more API.

To me the weirder exception around this is that if a base class *is* a
class object, even if it has a __mro_entries__ method that isn't called. If
we added a __mro_entries__ to type (returning (self,)) it might make more
sense to insist on bases always having a __mro_entries__ method.

Then again I find the name __mro_entries__ somewhat poorly chosen, since it
really is a hook to override the list of explicit bases (i.e., (B, C) for
class A(B, C)), not the MRO (which would be at least (A, B, C, object), and
more if A and/or B have other superclasses).

--
--Guido van Rossum (python.org/~guido)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
Yes, it's supposed to work. There's tons of special code to support it, and
documentation (e.g.
https://docs.python.org/3/reference/datamodel.html?highlight=__mro_entries__#resolving-mro-entries
).

Now, I think it was an attempt at getting too fancy, but we can't remove
the functionality without deprecation etc. -- __mro_entries__ was
introduced in Python 3.7 by PEP 560 to support it (but the support itself
is much older).

On Sat, Mar 5, 2022 at 9:50 AM Steven D'Aprano <steve@pearwood.info> wrote:

> On Sat, Mar 05, 2022 at 04:42:55PM -0000, Jason Madden wrote:
>
> > zope.interface relies on this behaviour.
>
> The example you give shows that Interface is a class. It merely has a
> metaclass which is not `type`. (I presume that is what's going on
> behind the scenes.)
>
> I'm asking about the example that Serhiy shows, where a class inherits
> from something which is not a class at all. In his example, the base is
> 1, although it gives a TypeError. I'm asking if that sort of thing is
> suppposed to work, and if so, how?
>
>
> --
> Steve
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/E2IWXPFY32R6JS22XXFORMNQTI4S6AOK/
> Code of Conduct: http://python.org/psf/codeofconduct/
>


--
--Guido van Rossum (python.org/~guido)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
Re: Make __mro_entries__ mandatory for non-types [ In reply to ]
Steven D'Aprano wrote:
> On Sat, Mar 05, 2022 at 04:42:55PM -0000, Jason Madden wrote:
> > zope.interface relies on this behaviour.
> > The example you give shows that Interface is a class. It merely has a
> metaclass which is not `type`. (I presume that is what's going on
> behind the scenes.)

I don't think that's *quite* correct. `Interface` is an ordinary object, an instance of `InterfaceClass`. `InterfaceClass` despite the name, is not actually a metaclass — `type` isn't anywhere in the MRO. The original example is similar in that 1 is an instance of `int` and `int` isn't a metaclass either.

(<class 'zope.interface.interface.InterfaceClass'>,
<class 'zope.interface.interface.InterfaceClass'>,
<class 'zope.interface.interface.InterfaceBase'>,
<class 'zope.interface.interface.NameAndModuleComparisonMixin'>,
<class 'zope.interface.interface.Specification'>,
<class 'zope.interface.interface.SpecificationBase'>,
<class 'zope.interface.interface.Element'>,
<class 'object'>)

The simplified version of what zope.interface is doing is this:

... def __init__(self, name, bases, ns):
... pass
... pass
<__main__.SomethingBase object at 0x100c81910>
False

Contrast with a true metaclass:

... pass
... pass
<class '__main__.Meta'>
(<class '__main__.Meta'>, <class 'type'>, <class 'object'>)

> I'm asking about the example that Serhiy shows, where a class inherits
> from something which is not a class at all. In his example, the base is
> 1, although it gives a TypeError. I'm asking if that sort of thing is
> suppposed to work, and if so, how?

Python accepts anything callable with the right number of arguments as a metaclass. If you specify it explicitly, it can be just a function:

py> def Meta(*ignored):
... return 42
...
py> class X(metaclass=Meta):
... pass
...
py> X
42

If you don't specify it explicitly, Python uses the type of the first base as the metaclass (ignoring conflicts and derived metaclasses) and calls that. In neither case does it have to be, or return, a class/type. We can actually write a class statement using a number as a base if we define an appropriate `__new__` method:

py> class MyInt(int):
... def __new__(cls, *args):
... if len(args) == 1:
... return int.__new__(cls, *args)
... return int.__new__(cls, 42)
py> one = MyInt(1)
py> one
1
py> isinstance(one, int)
True
py> isinstance(one, type)
False
py> class A(one):
... pass
...
py> A
42
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/2WV6GXDQLRA4PYQHZLEKZUIAOH3EYKNF/
Code of Conduct: http://python.org/psf/codeofconduct/