16

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?

alexis
  • 48,685
  • 16
  • 101
  • 161
  • 5
    Why wouldn't you want to subclass `bool`? You could create `10` subclasses to represent each possible boolean state. – jamylak Apr 17 '13 at 09:51
  • 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). – alexis Apr 17 '13 at 13:05

1 Answers1

23

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
alexis
  • 48,685
  • 16
  • 101
  • 161
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 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'`? – alexis Jun 02 '13 at 13:49
  • @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. – Martijn Pieters Jun 02 '13 at 13:52
  • Got it, right, it's in a metaclass, not the final class itself. Thanks! – alexis Jun 02 '13 at 13:54
  • 1
    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. – Bachsau Jan 12 '18 at 16:49
  • 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. – Christian Tismer May 10 '18 at 11:16