Сначала создайте декоратор для связывания.
from functools import wraps
from inspect import signature
def binds(f):
@wraps(f)
def __init__(self, *args, **kwargs):
# Update self attributes with inspected binding of arg names to values.
vars(self).update(signature(f).bind(None, *args, **kwargs).arguments)
# `self` is also an arg, but we didn't really want to save this one.
del self.self
return __init__
Вот как вы можете его использовать.
class Foo:
@binds
def __init__(self, foo, bar, *args, baz, **kwargs):
pass
И демонстрация:
>>> vars(Foo(1,2,3,4,baz=5,quux=10))
{'foo': 1, 'bar': 2, 'args': (3, 4), 'baz': 5, 'kwargs': {'quux': 10}}
Теперь осталось только заставить метакласс автоматически применять этот декоратор.
class AutoInit(type):
def __new__(cls, name, bases, namespace):
namespace['__init__'] = store_args(namespace['__init__'])
return type.__new__(cls, name, bases, namespace)
AutoInit
- это подкласс метакласса по умолчанию type
. По построению он находит метод init и заменяет его упакованной версией. type
обрабатывает все остальное.
Теперь вы можете использовать его как любой другой метакласс:
class Bar(metaclass=AutoInit):
def __init__(self, spam, eggs, *, bacon):
pass
Демонстрация:
>>> vars(Bar(1,2,bacon=3))
{'spam': 1, 'eggs': 2, 'bacon': 3}
И это поведение будет наследоваться как хорошо.
class Baz(Bar):
def __init__(self, spam, eggs, sausage, *, bacon):
super().__init__(spam, eggs, bacon=bacon)
Демонстрация (обратите внимание на 'sausage'
):
>>> vars(Baz(1,2,3,bacon=4))
{'spam': 1, 'eggs': 2, 'sausage': 3, 'bacon': 4}
Метаклассы: Глубокие маги c. Мощный, но неясный. Они вам почти никогда не нужны (если вы не уверены, предположите, что они вам не нужны). Код, который использует слишком много метаклассов, может стать очень трудным для понимания и поддержки.
В этом случае вам, вероятно, следует остановиться на декораторе. Это делает почти все, что вы хотите, не будучи таким запутанным. Явное лучше, чем неявное. Код читается больше, чем написано. Читаемость имеет значение.