Динамическое наследование в Python через декоратор
Я нашел этот пост, где функция используется для наследования от класса:
def get_my_code(base):
class MyCode(base):
def initialize(self):
...
return MyCode
my_code = get_my_code(ParentA)
Я хотел бы сделать что-то подобное, но с декоратором, что-то вроде:
@decorator(base)
class MyClass(base):
...
Это возможно?
ОБНОВИТЬ
Скажи, у тебя есть класс Analysis
это используется во всем вашем коде. Затем вы понимаете, что хотите использовать класс-оболочку Transient
это просто временная петля поверх класса анализа. Если в коде я заменяю класс анализа, но Transient(Analysis)
все ломается, потому что ожидается анализ класса и, следовательно, все его атрибуты. Проблема в том, что я не могу просто определить class Transient(Analysis)
таким образом, потому что есть много классов анализа. Я думал, что лучший способ сделать это - иметь какое-то динамическое наследование. Прямо сейчас я использую агрегацию, чтобы перенаправить функциональность в класс анализа внутри переходного процесса.
1 ответ
Декоратор класса фактически получает класс, уже созданный и созданный (как объект класса). Он может вносить изменения в свою информацию и даже обернуть свои методы другими декораторами.
Тем не менее, это означает, что класс уже имеет свои установленные базы - и они не могут быть изменены обычно. Это подразумевает, что вы должны каким-то образом перестроить класс внутри кода декоратора.
Однако, если методы класса используют без параметров super
или же __class__
переменная ячейки, они уже установлены в функциях-членах (которые в Python 3 совпадают с несвязанными методами), вы не можете просто создать новый класс и установить эти методы в качестве членов нового.
Таким образом, может быть способ, но он будет нетривиальным. И, как я указал в комментарии выше, я хотел бы понять, чего бы вы хотели достичь, поскольку можно просто поставить base
class в самом объявлении класса, вместо того, чтобы использовать его в конфигурации декоратора.
Я создал функцию, которая, как описано выше, создает новый класс, "клонирует" оригинал и может пересобрать все методы, которые используют __class__
или же super
: возвращает новый класс, функционально идентичный классу оригинала, но с замененными базами. Если используется в декораторе по запросу (включая код декоратора), он просто изменит основы классов. Он не может обрабатывать декорированные методы (кроме classmethod и staticmethod) и не заботится о деталях именования - таких как квалификационные имена или repr
для методов.
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