How does python prevent a class from being subclassed? [duplicate]
Asked Answered
I

1

18

I came across the following in the python docs:

bool([x])

Convert a value to a Boolean, using the standard truth testing procedure. If x is false or omitted, this returns False; otherwise it returns True. bool is also a class, which is a subclass of int. Class bool cannot be subclassed further. Its only instances are False and True.

I've never in my life wanted to subclass bool, but naturally I immediately tried it, and sure enough:

>>> class Bool(bool):
    pass

Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    class Bool(bool):
TypeError: Error when calling the metaclass bases
    type 'bool' is not an acceptable base type

So, the question: How is this done? And can I apply the same technique (or a different one) to mark my own classes as final, i.e., to keep them from being subclassed?

Interdependent answered 17/4, 2013 at 9:36 Comment(2)
Why wouldn't you want to subclass bool? You could create 10 subclasses to represent each possible boolean state.Altheta
Thanks @Martijn, that does look like a closely related question. It didn't come up during my SO search (I should have thought to add "final" to the search terms).Interdependent
M
25

The bool type is defined in C, and its tp_flags slot deliberately does not include the Py_TPFLAGS_BASETYPE flag.

C types need to mark themselves explicitly as subclassable.

To do this for custom Python classes, use a metaclass:

class Final(type):
    def __new__(cls, name, bases, classdict):
        for b in bases:
            if isinstance(b, Final):
                raise TypeError("type '{0}' is not an acceptable base type".format(b.__name__))
        return type.__new__(cls, name, bases, dict(classdict))

class Foo:
    __metaclass__ = Final

class Bar(Foo):
    pass

gives:

>>> class Bar(Foo):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __new__
TypeError: type 'Foo' is not an acceptable base type
Merwyn answered 17/4, 2013 at 9:42 Comment(5)
On further reflection, I have a question: Is there any purpose to the check isinstance(b, Final)? I mean, this __new__ will only ever be called by a class that inherits from Final, so why not just raise immediately if cls != 'Final'?Interdependent
@alexis: Yes, because you need to name which baseclass it is you tried to inherit from. Bar does not subclass Final, it subclasses Foo, which is why the isinstance(b, Final) test returns True for that class. If there are more base classes used, you want to tell the end user which one of those bases is not inheritable from.Merwyn
Got it, right, it's in a metaclass, not the final class itself. Thanks!Interdependent
It should be noted that this is only to be used for the sake of understanding how Python works. Noone should ever build things like that in a real program.Samara
Thanks @MartijnPieters! I actually had the problem in PySide2 when converting it to heaptypes (I'm working on PEP 384 support). I could not remove the Py_TPFLAGS_BASETYPE flag and modified instead the metatype to do the above check in C.Carolynncarolynne

© 2022 - 2024 — McMap. All rights reserved.