Я разрабатываю коллекцию функций преобразования классов, чтобы обернуть и внедрить методы в заданные базовые классы (классическое наследование просто не обеспечивает необходимую гибкость и API-простоту).
Подход, который я использую взятие дает мне результаты, которые я хочу, за исключением того, как он обрабатывает __name__
, __qualname__
и __module__
. По крайней мере, не жертвуя некоторой простотой API.
Рассмотрим следующий модуль, содержащий функцию-оболочку:
# scrap/wrappers.py
def add_methods(cls, new_cls_name=None, new_module_name=None, **funcs):
"""Add methods to a class."""
new_cls_name = new_cls_name or f'New{cls.__name__}'
new_cls = type(new_cls_name, (cls,), {})
for func_name, func in funcs.items():
setattr(new_cls, func_name, func)
if new_module_name:
new_cls.__module__ = new_module_name
return new_cls
И еще один модуль, содержащий класс:
# scrap/sources.py
class Foo: ...
Я хотел бы иметь возможность сказать, в каком-то модуле "scrap / creation.py":
# scrap/usage.py
from scrap.wrappers import add_methods
from scrap.sources import Foo
bar = lambda self, x: x + 10
NewFoo = add_methods(Foo, bar=bar)
так, чтобы имя моего NewFoo
класса было "NewFoo"
, а модуля было "scrap.usage"
.
Вместо этого мне нужно заплатить за это с уменьшением простоты:
Давайте попробуем это:
>>> from scrap.wrappers import add_methods
>>> from scrap.sources import Foo
>>> bar = lambda self, x: x + 10
Следующее дает мне имя и модуль, который я ожидаю , но мне нужно указать их явно.
>>> FooBar_1 = add_methods(Foo, new_cls_name='FooBar_1', new_module_name=__name__, bar=bar)
>>> FooBar_1
<class '__main__.FooBar_1'>
Если new_module_name
не указан явно, кажется, что новый класс происходит от add_methods.__module__
, что технически верно, но не очень полезно.
>>> FooBar_2 = add_methods(Foo, new_cls_name='FooBar_2', bar=bar)
>>> FooBar_2
<class 'scrap.wrappers.FooBar_2'>
Если мы не укажем new_cls_name
, будет выбрано значение по умолчанию. и не уверен, что случится что-то плохое в пространстве имен ...
>>> FooBar_3 = add_methods(Foo, bar=bar)
>>> FooBar_4 = add_methods(Foo, bar=bar)
>>> FooBar_3
<class 'scrap.wrappers.NewFoo'>
>>> FooBar_4
<class 'scrap.wrappers.NewFoo'>
>>> assert (FooBar_3.__module__, FooBar_3.__qualname__) == (FooBar_4.__module__, FooBar_4.__qualname__) # yet...
>>> assert FooBar_3 != FooBar_4