Python: модуль обернутого класса - PullRequest
0 голосов
/ 27 апреля 2020

Я разрабатываю коллекцию функций преобразования классов, чтобы обернуть и внедрить методы в заданные базовые классы (классическое наследование просто не обеспечивает необходимую гибкость и 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
...