Декоратор __init__, обрабатывающий пустые аргументы - PullRequest
1 голос
/ 03 августа 2020

Допустим, у меня есть класс как таковой:

class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = b

Моя цель - создать decorator, который обрабатывает заполнение init аргументов, если они не существуют, то есть:

class Test:
    @autoinit
    def __init__(self, a, b):
        self.a = a
        self.b = b

, где @autoinit определяется как таковое:

class autoinit:
    def __init__(self, data = {"a": "test_a", "b": "test_b"}):
        self.data = data
    def __call__(self, func):
        decorator = self
        def wrapper(*args, **kwargs):
            print(decorator.data)
            func(self, **decorator.data)
            print(decorator.data)
        return wrapper

Таким образом, атрибуты Test будут автоматически присвоены test_a, test_b соответственно.

Идеальное использование быть таким:

test = Test(a="test_z", b="test_x")
test.a == "test_z"
test.b == "test_x"

# however,

test = Test()
test.a == "test_a"
test.b == "test_b"

# but also,

test = Test(a="test_z")
test.a == "test_z"
test.b == "test_b"

У меня всегда будут совпадающие аргументы в классе Test с ключами в словаре data.

Возможно ли это? Какая самая чистая реализация?

Обновление:

Предполагаемое использование - во многих независимых классах. Например, предположим, что у меня есть глобальная конфигурация как таковая:

config = {
    "resourceA": {"a": "test_a", "b": "test_b"},
    "resourceB": {"name": "foo", "value": "bar"}
}

Цель состоит в том, чтобы декоратор @autoinit(resource="resourceA") использовал **config[resource] для заполнения всех __init__ значений для данного класса.

1 Ответ

3 голосов
/ 03 августа 2020

Вот как я бы написал это:

def autoinit(**kwargs):
    if not kwargs:
        kwargs = {"a": "test_a", "b": "test_b"}  # some default

    def wrapper(f):
        def wrapped(*args, **overrides):
            kwargs.update(overrides)  # update kwargs with overrides
            return f(*args, **kwargs)
        return wrapped
    return wrapper

Это позволяет реализовать класс, как описано в вашем вопросе:

class Test:
    @autoinit()
    def __init__(self, a, b):
        self.a = a
        self.b = b

t = Test()
assert t.a = 'test_a'
assert t.b = 'test_b'

t2 = Test(a='test_z')
assert t2.a = 'test_z'
assert t2.b = 'test_b'

С учетом всего сказанного, вместо этого рассмотрите возможность использования миксина, который учит ваш класс, как читать из самой конфигурации.

from abc import ABC
class ConfigurationDefault(ABC):
    @classmethod
    def with_config_defaults(cls, config, **kwargs):
        new_kwargs = {**config, **kwargs}
        return cls(**new_kwargs)

class Test(ConfigurationDefault):
    def __init__(self, a, b):
        self.a = a
        self.b = b

config = {'resources': {'a': 'test_a', 'b': 'test_b'}}
t = Test.with_config_defaults(config['resources'])
t2 = Test.with_config_defaults(config['resources'], a='test_z')
...