Emulating Final classes in Python
ethan at stoneleaf.us
Wed Jan 18 17:02:47 EST 2017
On 01/18/2017 08:24 AM, Ethan Furman wrote:
> On 01/17/2017 11:05 PM, Steven D'Aprano wrote:
>> I've given a metaclass that disallows subclassing:
>> class MyClass(MyParent, metaclass=FinalMeta):
>> Ethan took that one step further by giving a class you inherit from to disallow
>> class MyClass(MyParent, Final):
>> Could we solve this problem with a decorator?
>> class MyClass(MyParent):
>> Without needing to add any special magic to MyParent or MyClass, apart from the
>> decorator, can we make MyClass final? That would (in principle) allow us to
>> make a subclass, and *then* set the class final so that no more subclasses
>> could be made.
>> I thought that the decorator could simply set the class' metaclass:
>> def final(cls):
>> if cls.__class__ is type:
>> cls.__class__ = Meta
>> return cls
>> raise TypeErrror('Possible metaclass conflict')
>> but that's disallowed. Any ideas?
> You still need to have the FinalMeta type and Final class available, but to use a
> decorator you'll need to scavenge the bits from the old class to make a new class
> of the same name and return that:
> def final(cls):
> new_cls = Meta(cls.__name__, (Final, )+cls.__bases__, dict(cls.__dict__))
> return new_cls
> Not sure if more is needed to handle __slots__, but this should get us started.
One problem with the above is existing instances won't be modified to inherit from the updated class. I am unsure if that is solvable before 3.6, but in 3.6 one can use the new __init_subclass__ to avoid a Final base class, a FinalMeta type, and just update the existing class:
def init_subclass(cls, **kwargs):
raise Exception('Final class cannot be subclassed')
cls.__init_subclass__ = classmethod(init_subclass)
This can be used as a decorator at class creation time, or at any later date to lock down a class. The downside is it's less obvious that the class is final... meaning there are no clues in the MRO.
More information about the Python-list