Emulating Final classes in Python

Ethan Furman ethan at stoneleaf.us
Tue Jan 17 14:14:13 EST 2017


On 01/16/2017 11:32 PM, Steven D'Aprano wrote:
> On Tuesday 17 January 2017 18:05, Steven D'Aprano wrote:
>
>> I wish to emulate a "final" class using Python, similar to bool:
>
> I may have a solution: here's a singleton (so more like None than bools) where
> instantiating the class returns the singleton, and subclassing the class fails:
>
> class DoneMeta(type):
>      _final = None
>      def __new__(meta, name, bases, ns):
>          if meta._final is None:
>              meta._final = cls = super().__new__(meta, name, bases, ns)
>              return cls
>          elif meta._final in bases:  # Not sure this check is needed.
>              raise TypeError('base class is final and cannot be subclassed')

This will make DoneMeta a one-shot, meaning you'll have to make more DoneMeta's if you need more than one unsubclassable class.

> class DoneType(metaclass=DoneMeta):
>      __slots__ = ()
>      _instance = None
>      def __new__(cls):
>          if cls._instance is None:
>              cls._instance = inst = super().__new__(cls)
>              return inst
>          return cls._instance
>      def __repr__(self):
>          return '<DONE>'

And this has to do with single instances, which is not what you asked about.

Here's some sample code that creates a Final class; any class that subclasses from it cannot be further subclassed:

-- 8< ------------------------------------------------------------

Final = None

class FinalMeta(type):

     def __new__(metacls, cls, bases, clsdict):
         print('-' * 50)
         print('class: ', cls)
         print('bases: ', bases)
         if Final is not None:
             for base in bases:
                 if base is not Final and  issubclass(base, Final):
                     print('should raise')
         print('-' * 50)
         return type.__new__(metacls, cls, bases, clsdict)

class Final(metaclass=FinalMeta):
     pass

class One(Final):
     pass

class Two(One):
     pass

class Three(Two):
     pass

class Ten(Final):
     pass

-- 8< ------------------------------------------------------------

Change the "should raise" to a raise, remove the other print()s, and away you go.

Should work in any Python 3.

--
~Ethan~


More information about the Python-list mailing list