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).
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
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
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
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__... ). 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/E2IWXPFY... 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-c...>
Steven D'Aprano wrote:
zope.interface relies on this behaviour. The example you give shows that Interface is a class. It merely has a
On Sat, Mar 05, 2022 at 04:42:55PM -0000, Jason Madden wrote: 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. py> InterfaceClass.__mro__ (<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: py> class SomethingBase: ... def __init__(self, name, bases, ns): ... pass py> Something = SomethingBase('Something', (), {}) py> class MySomething(Something): ... pass py> MySomething <__main__.SomethingBase object at 0x100c81910> py> isinstance(MySomething, type) False Contrast with a true metaclass: py> class Meta(type): ... pass py> class WithMeta(metaclass=Meta): ... pass py> type(WithMeta) <class '__main__.Meta'> py> type(WithMeta).__mro__ (<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
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-c...>
participants (4)
-
Guido van Rossum -
Jason Madden -
Serhiy Storchaka -
Steven D'Aprano