2016-10-25 6 views
0

В классе Python, я хотел бы автоматически назначать переменные-члены быть такими же, как аргументы функции в __init__, как это:Использование метакласса автоматически назначать переменные-члены класса

class Foo(object): 
    def __init__(self, arg1, arg2 = 1): 
     self.arg1 = arg1 
     self.arg2 = arg2 
  • Я бы как явно указать имена аргументов в init функция ради ясности кода.
  • Я не хочу использовать декораторы по той же причине.

Можно ли достичь этого с помощью пользовательского метакласса?

+0

честно, если вы просто хотите присвоить атрибуты, непосредственно переданные в '__init__', тогда вы можете просто использовать' collections.namedtuple'. –

+0

Зачем вам это нужно? В зависимости от конкретного варианта использования может быть довольно много разных (и более или менее подходящих) решений. –

+0

У меня есть длинный список аргументов для моих классов (скажем, около 10), и я не хочу явно вводить их один за другим. Позже в классе я использовал этот аргумент для дальнейших вычислений. – motam79

ответ

1

Во-первых, отказ от ответственности. Создание и инициализация объектов Python может быть сложной и очень динамичной. Это означает, что может возникнуть трудность придумать решение, которое работает для угловых случаев. Более того, решения, как правило, используют некоторую более темную магию, и поэтому, когда они неизбежно do ошибаются, их можно отлаживать сложно.

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

class Car: 

    def __init__(self, tires, engine): 
     self.tires = tires 
     self.engine = engine 

class Tire: 

    def __init__(self, radius, winter=False): 
     self.radius = radius 
     self.winter = winter 

class Engine: 

    def __init__(self, big=True, loud=True): 
     self.big = big 
     self.loud = loud 

в отличие от

class Car: 

    def __init__(self, tire_radius, winter_tires=False, 
       engine_big=True, engine_loud=True): 
     self.tire_radius = tire_radius 
     self.winter_tires winter_tires 
     self.engine_big = engine_big 
     self.engine_loud = engine_loud 

Все, что сказал, вот это решение. Я не использовал это в своем собственном коде, поэтому он не «проверен на битву». Но это, по крайней мере, похоже, работает в простом случае. Используйте на свой риск.

Во-первых, здесь не нужны метаклассы, и мы можем использовать простой декоратор по методу __init__. Я думаю, что это более читаемо, так или иначе, так как ясно, что мы только модифицируем поведение __init__, а не что-то более глубокое в создании класса.

import inspect 
import functools 

def unpack(__init__): 
    sig = inspect.signature(__init__) 

    @functools.wraps(__init__) 
    def __init_wrapped__(self, *args, **kwargs): 
     bound = sig.bind(self, *args, **kwargs) 
     bound.apply_defaults() 

     # first entry is the instance, should not be set 
     # discard it, taking only the rest 
     attrs = list(bound.arguments.items())[1:] 

     for attr, value in attrs: 
      setattr(self, attr, value) 

     return __init__(self, *args, **kwargs) 

    return __init_wrapped__ 

Этот декоратор использует inspect модуля для получения подписи методы __init__. Затем мы просто перебираем атрибуты и используем setattr для их назначения.

При использовании, он выглядит следующим образом:

class Foo(object): 

    @unpack 
    def __init__(self, a, b=88): 
     print('This still runs!') 

так что

>>> foo = Foo(42) 
This still runs! 
>>> foo.a 
42 
>>> foo.b 
88 

Я не уверен, что каждый инструмент самоанализа будет видеть правильную подпись украшенной __init__. В частности, я не уверен, что Sphinx сделает «правильную вещь». Но по крайней мере функция inspect модуля signature вернет подпись завернутой функции, которая может быть протестирована.

Если вы действительно хотите получить решение для метакласса, оно достаточно просто (но менее читаемо и более волшебное, ИМО).Вам нужно только написать класс завод, который применяет unpack декоратора:

def unpackmeta(clsname, bases, dct): 
    dct['__init__'] = unpack(dct['__init__']) 
    return type(clsname, bases, dct) 

class Foo(metaclass=unpackmeta): 

    def __init__(self, a, b=88): 
     print('This still runs!') 

выход будет таким же, как в примере выше.

+0

Спасибо, Есть ли эквивалент «inspect.signature» в python 2.7? – motam79

+0

Да, 'getargspec', но это не эквивалент. Тебе придется немного поиграть. Я оставлю это упражнение для читателя. – jme

 Смежные вопросы

  • Нет связанных вопросов^_^