简体   繁体   中英

dynamic inheritance in Python through a decorator

I found this post where a function is used to inherit from a class:

def get_my_code(base):
    class MyCode(base):
        def initialize(self):
          ...
    return MyCode
my_code = get_my_code(ParentA)

I would like to do something similar, but with a decorator, something like:

@decorator(base)
class MyClass(base):
   ...

Is this possible?

UPDATE

Say you have a class Analysis that is used throughout your code. Then you realize that you want to use a wrapper class Transient that is just a time loop on top of the analysis class. If in the code I replace the analysis class, but Transient(Analysis) everything breaks because an analysis class is expected, and thus all its attributes. The problem is that I can't just get to define class Transient(Analysis) in this way because there are plenty of analysis classes. I thought the best way to do this would be to have some sort of dynamic inheritance. Right now I use aggregation to redirect the functionality to the analysis class inside transient.

A class decorator actually gets the class already built - and instantiated (as a class object). It can perform changes on it's dict, and even wrap its methods with other decorators.

However, it means the class already has its bases set - and these can't be ordinarily changed. That implies you have to, in some ay rebuild the class inside the decorator code.

However, if the class'methods make use of parameterless super or __class__ cell variable, those are already set in the member functions (that in Python 3 are the same as unbound methods) you can't just create a new class and set those methods as members on the new one.

So, there might be a way, but it will be non-trivial. And as I pointed out in the comment above, I d like to understand what you'd like to be able to achieve with this, since one could just put the base class on the class declaration itself, instead of using it on the decorator configuration.

I've crafted a function that, as described above, creates a new class, "clonning" the original and can re-build all methods that use __class__ or super : it returns the new class which is functionally identical to the orignal one, but with the bases exchanged. If used in a decorator as requested (decorator code included), it will simply change the class bases. It can't handle decorated methods (other than classmethod and staticmethod), and don't take care of naming details - such as qualnames or repr for the methods.

from types import FunctionType

def change_bases(cls, bases, metaclass=type):
    class Changeling(*bases, metaclass=metaclass):
        def breeder(self):
            __class__  #noQA

    cell = Changeling.breeder.__closure__
    del Changeling.breeder

    Changeling.__name__ = cls.__name__

    for attr_name, attr_value in cls.__dict__.items():
        if isinstance(attr_value, (FunctionType, classmethod, staticmethod)):
            if isinstance(attr_value, staticmethod):
                func = getattr(cls, attr_name)
            elif isinstance(attr_value, classmethod):
                func = attr_value.__func__
            else:
                func = attr_value
            # TODO: check if func is wrapped in decorators and recreate inner function.
            # Although reaplying arbitrary decorators is not actually possible -
            # it is possible to have a "prepare_for_changeling" innermost decorator
            # which could be made to point to the new function.
            if func.__closure__ and func.__closure__[0].cell_contents is cls:
                franken_func = FunctionType(
                    func.__code__,
                    func.__globals__,
                    func.__name__,
                    func.__defaults__,
                    cell
                )
                if isinstance(attr_value, staticmethod):
                    func = staticmethod(franken_func)
                elif isinstance(attr_value, classmethod):
                    func = classmethod(franken_func)
                else:
                    func = franken_func
                setattr(Changeling, attr_name, func)
                continue
        setattr(Changeling, attr_name, attr_value)

    return Changeling


def decorator(bases):
    if not isinstance(base, tuple):
        bases = (bases,)
    def stage2(cls):
        return change_bases(cls, bases)
    return stage2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM