python создать метакласс, который автоматически __init__ для * args и ** kwargs - PullRequest
0 голосов
/ 17 февраля 2020

Backround

Когда я создаю класс python и в методе init я просто пишу:

self.name1 = name1
self.name2 = name2
....

для всех аргументов.

Вопрос

ТАК, как изящно написать __init__ для разных *args и **kwargs?

Я хочу написать метакласс для автоматической установки атрибутов для *args и **kwargs:

#the metaclass to be used
class LazyInit():
     # code here

И затем, когда я создаю его дочерние классы, дочерние классы автоматически __init__ для своих собственных *args и **kwargs.

class Hospital(metaclass = LazyInit):
  def __init__(self, *args,**kwargs): pass

class Basketball(metaclass = LazyInit):
  def __init__(self, *args, **kwargs): pass

a = 1
b = 2
c = 3
a_hospital = Hospital(a, b, kw1 =1, kw2 =2)
a_circle = Basketball(a, c, kw3 = 10, kw4 = 20)

Моя попытка

Я только знаю, что мы можем использовать setattr, чтобы сделать это только для **kwargs:

class LazyInit():
    def __init__(self,**kwargs):
        for k,v in kwargs.items():
            setattr(self,k,v)
LazyInit(a=1).a

1

Однако это не мета класс, который можно использовать в дочерних классах. Я должен написать этот фрагмент кода в каждом из моих дочерних классов ...

1 Ответ

0 голосов
/ 17 февраля 2020

Сначала создайте декоратор для связывания.

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. Мощный, но неясный. Они вам почти никогда не нужны (если вы не уверены, предположите, что они вам не нужны). Код, который использует слишком много метаклассов, может стать очень трудным для понимания и поддержки.

В этом случае вам, вероятно, следует остановиться на декораторе. Это делает почти все, что вы хотите, не будучи таким запутанным. Явное лучше, чем неявное. Код читается больше, чем написано. Читаемость имеет значение.

...