2009-09-07 2 views
53

У меня есть класс питона, который выглядит следующим образом:Автоматически инициализировать переменные экземпляра?

class Process: 
    def __init__(self, PID, PPID, cmd, FDs, reachable, user): 

следует:

 self.PID=PID 
     self.PPID=PPID 
     self.cmd=cmd 
     ... 

Есть ли способ autoinitialize этих переменного экземпляра, как список инициализации С ++? Это избавило бы много избыточного кода.

+0

См. Также обсуждение «autoassign» activestate recipe и альтернативную реализацию «autoargs» в: [Что является лучшим способом автоматического назначения атрибутов в Python, и это хорошая идея? - Переполнение стека] (http://stackoverflow.com/questions/3652851/what-is-the-best-way-to-do-automatic-attribute-assignment-in-python-and-is-it-a) – nealmcb

ответ

66

Вы можете использовать декоратора:

from functools import wraps 
import inspect 

def initializer(func): 
    """ 
    Automatically assigns the parameters. 

    >>> class process: 
    ...  @initializer 
    ...  def __init__(self, cmd, reachable=False, user='root'): 
    ...   pass 
    >>> p = process('halt', True) 
    >>> p.cmd, p.reachable, p.user 
    ('halt', True, 'root') 
    """ 
    names, varargs, keywords, defaults = inspect.getargspec(func) 

    @wraps(func) 
    def wrapper(self, *args, **kargs): 
     for name, arg in list(zip(names[1:], args)) + list(kargs.items()): 
      setattr(self, name, arg) 

     for name, default in zip(reversed(names), reversed(defaults)): 
      if not hasattr(self, name): 
       setattr(self, name, default) 

     func(self, *args, **kargs) 

    return wrapper 

использовать его для украшения __init__ метод:

class process: 
    @initializer 
    def __init__(self, PID, PPID, cmd, FDs, reachable, user): 
     pass 

Выход:

>>> c = process(1, 2, 3, 4, 5, 6) 
>>> c.PID 
1 
>>> dir(c) 
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user' 
+1

minor nitpick - вы забыли импортировать инспектировать –

+2

Это работает и отвечает на вопрос, поэтому я проголосовал. Но я оставил Фердиданд Бейер ответом: «Явный лучше, чем неявный» –

+5

+1 Для отличного ответа, который решил мою проблему. Но разве это не основная функциональность языка? Как вы думаете, стоит ли писать PEP? –

13

Вы можете сделать это легко с помощью аргументов ключевого слова, например. как это:

>>> class D: 
    def __init__(self, **kwargs): 
     for k, v in kwargs.items(): 
      setattr(self, k, v) 

>>> D(test='d').test 
'd' 

похожая реализация для позиционных аргументов будет:

>> class C: 
    def __init__(self, *args): 
     self.t, self.d = args 


>>> C('abc', 'def').t 
'abc' 
>>> C('abc', 'def').d 
'def' 

, который мне не кажется, чтобы решить вашу проблему.

+1

Другой вариант, который мне нравится, это 'self .__ dict __. Update (** kwargs)' –

+0

Возможно также использовать locals() и поместить обычные аргументы. – mk12

22

Цитирование в Zen of Python,

Явный лучше, чем неявный.

+1

Не было бы явно указано объявление списка инициализации? –

+0

Думаю. Но я не вижу причины добавлять что-то подобное к языку. Я явно предпочитаю несколько операторов присваивания за какой-то волшебник-декоратор за кулисами. –

+20

@Ferdinand, я согласен, что было бы глупо иметь на языке что-то, что вполне может быть в stdlib, но это ДОЛЖНО быть в stdlib, потому что «красиво лучше, чем уродливое» имеет преимущество, и много повторяющихся заданий уродливый (как и любая форма повторения). –

27

Если вы используете Python 2.6 или выше, вы можете использовать collections.namedtuple:

>>> from collections import namedtuple 
>>> Process = namedtuple('Process', 'PID PPID cmd') 
>>> proc = Process(1, 2, 3) 
>>> proc.PID 
1 
>>> proc.PPID 
2 

Это уместно, особенно когда ваш класс действительно просто большой мешок ценностей.

+0

+1 для действительно опрятного инструмента. –

+2

«Это особенно подходит, когда ваш класс действительно просто большой мешок ценностей ». В таком сценарии вы также можете сделать это: [https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends](https: //docs.python.org/3.3/tutorial/classes.html # odds-and-end) –

16

Другая вещь, которую вы можете сделать:

class X(object): 
    def __init__(self, a,b,c,d): 
     vars = locals() # dict of local names 
     self.__dict__.update(vars) # __dict__ holds and object's attributes 
     del self.__dict__["self"] # don't need `self` 

Но единственное решение, которое я рекомендовал бы, к тому же просто написание его, это «сделать макрос в редакторе» ;-p

6

решение Nadia является лучше и более мощный, но я думаю, что это тоже интересно:

def constructor(*arg_names): 
    def __init__(self, *args): 
    for name, val in zip(arg_names, args): 
     self.__setattr__(name, val) 
    return __init__ 


class MyClass(object): 
    __init__ = constructor("var1", "var2", "var3") 


>>> c = MyClass("fish", "cheese", "beans") 
>>> c.var2 
"cheese" 
1

nu11ptr сделал небольшой модуль, PyInstanceVars, который включает в себя эту функцию как декоратор функции. В README модуля указано, что производительность «[...] теперь на 30-40% хуже, чем явная инициализация под CPython».

Пример использования, поднял прямо из documentation модуля:

>>> from instancevars import * 
>>> class TestMe(object): 
...  @instancevars(omit=['arg2_']) 
...  def __init__(self, _arg1, arg2_, arg3='test'): 
...    self.arg2 = arg2_ + 1 
... 
>>> testme = TestMe(1, 2) 
>>> testme._arg1 
1 
>>> testme.arg2_ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'TestMe' object has no attribute 'arg2_' 
>>> testme.arg2 
3 
>>> testme.arg3 
'test' 
2

Там не может быть необходимо инициализировать переменные, как местные жители() уже содержит значения!

класс Фиктивные (объект):

def __init__(self, a, b, default='Fred'): 
    self.params = {k:v for k,v in locals().items() if k != 'self'} 

д = пустышки (2, 3)

d.params

{ 'а': 2, 'B': 3 ' по умолчанию ': 'Fred'}

d.params [' б ']

Конечно, в классе можно было использовать self.params

+0

Это хороший и оригинальный подход, но 'd ['b']' написан на языке Python * lingua franca *, в то время как 'd.params ['b']' вызовет путаницу для считывателей кода , –

0

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

import inspect 

class AutoInit(type): 
    def __new__(meta, classname, supers, classdict): 
     classdict['__init__'] = wrapper(classdict['__init__']) 
     return type.__new__(meta, classname, supers, classdict) 

def wrapper(old_init): 
    def autoinit(*args): 
     formals = inspect.getfullargspec(old_init).args 
     for name, value in zip(formals[1:], args[1:]): 
      setattr(args[0], name, value) 
    return autoinit 
2

Как только getargspec является устаревшим, так как Python 3.5, вот решение с использованием inspect.signature:

from inspect import signature, Parameter 
import functools 


def auto_assign(func): 
    # Signature: 
    sig = signature(func) 
    for name, param in sig.parameters.items(): 
     if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): 
      raise RuntimeError('Unable to auto assign if *args or **kwargs in signature.') 
    # Wrapper: 
    @functools.wraps(func) 
    def wrapper(self, *args, **kwargs): 
     for i, (name, param) in enumerate(sig.parameters.items()): 
      # Skip 'self' param: 
      if i == 0: continue 
      # Search value in args, kwargs or defaults: 
      if i - 1 < len(args): 
       val = args[i - 1] 
      elif name in kwargs: 
       val = kwargs[name] 
      else: 
       val = param.default 
      setattr(self, name, val) 
     func(self, *args, **kwargs) 
    return wrapper 

Проверьте работу:

class Foo(object): 
    @auto_assign 
    def __init__(self, a, b, c=None, d=None, e=3): 
     pass 

f = Foo(1, 2, d="a") 
assert f.a == 1 
assert f.b == 2 
assert f.c is None 
assert f.d == "a" 
assert f.e == 3 

print("Ok") 
1

Мне нужно что-то с той же целью, но ни один из существующих ответов не охватывал все случаи, когда я испытания.   Ответ Надии был самым близким к тому, что я искал, поэтому я начал с ее кода в качестве базы.

Декоратор ниже работает со всеми действительными комбинациями аргументов:

Positional           __init__(self, a, b    ) 
Keyword            __init__(self, a=None, b=None  ) 
Positional + Keyword        __init__(self, a, b, c=None, d=None) 
Variable Positional         __init__(self, *a     ) 
Variable Positional + Keyword      __init__(self, *a, b=None   ) 
Variable Positional + Variable Keyword    __init__(self, *a, **kwargs  ) 
Positional + Variable Positional + Keyword   __init__(self, a, *b, c=None  ) 
Positional + Variable Positional + Variable Keyword __init__(self, a, *b, **kwargs ) 
Keyword Only          __init__(self, *, a=None   ) 
Positional + Keyword Only       __init__(self, a, *, b=None  ) 

Он также реализует стандартную _ -prefix конвенцию, чтобы для __init__ -Частных переменных, которые не будут назначены на экземпляры класса.


### StdLib ### 
from functools import wraps 
import inspect 


########################################################################################################################### 
#//////| Decorator |//////////////////////////////////////////////////////////////////////////////////////////////////# 
########################################################################################################################### 

def auto_assign_arguments(function): 

    @wraps(function) 
    def wrapped(self, *args, **kwargs): 
    _assign_args(self, list(args), kwargs, function) 
    function(self, *args, **kwargs) 

    return wrapped 


########################################################################################################################### 
#//////| Utils |//////////////////////////////////////////////////////////////////////////////////////////////////////# 
########################################################################################################################### 

def _assign_args(instance, args, kwargs, function): 

    def set_attribute(instance, parameter, default_arg): 
    if not(parameter.startswith("_")): 
     setattr(instance, parameter, default_arg) 

    def assign_keyword_defaults(parameters, defaults): 
    for parameter, default_arg in zip(reversed(parameters), reversed(defaults)): 
     set_attribute(instance, parameter, default_arg) 

    def assign_positional_args(parameters, args): 
    for parameter, arg in zip(parameters, args.copy()): 
     set_attribute(instance, parameter, arg) 
     args.remove(arg) 

    def assign_keyword_args(kwargs): 
    for parameter, arg in kwargs.items(): 
     set_attribute(instance, parameter, arg) 
    def assign_keyword_only_defaults(defaults): 
    return assign_keyword_args(defaults) 

    def assign_variable_args(parameter, args): 
    set_attribute(instance, parameter, args) 

    POSITIONAL_PARAMS, VARIABLE_PARAM, _, KEYWORD_DEFAULTS, _, KEYWORD_ONLY_DEFAULTS, _ = inspect.getfullargspec(function) 
    POSITIONAL_PARAMS = POSITIONAL_PARAMS[1:] # remove 'self' 

    if(KEYWORD_DEFAULTS ): assign_keyword_defaults  (parameters=POSITIONAL_PARAMS, defaults=KEYWORD_DEFAULTS) 
    if(KEYWORD_ONLY_DEFAULTS): assign_keyword_only_defaults(defaults=KEYWORD_ONLY_DEFAULTS       ) 
    if(args    ): assign_positional_args  (parameters=POSITIONAL_PARAMS, args=args    ) 
    if(kwargs    ): assign_keyword_args   (kwargs=kwargs           ) 
    if(VARIABLE_PARAM  ): assign_variable_args  (parameter=VARIABLE_PARAM,  args=args    ) 


###########################################################################################################################$#//////| Tests |//////////////////////////////////////////////////////////////////////////////////////////////////////#$###########################################################################################################################$$if __name__ == "__main__":$$#######| Positional |##################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b):$  pass$$ t = T(1, 2)$ assert (t.a == 1) and (t.b == 2)$$#######| Keyword |#####################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a="KW_DEFAULT_1", b="KW_DEFAULT_2"):$  pass$$ t = T(a="kw_arg_1", b="kw_arg_2")$ assert (t.a == "kw_arg_1") and (t.b == "kw_arg_2")$$#######| Positional + Keyword |########################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b, c="KW_DEFAULT_1", d="KW_DEFAULT_2"):$  pass$$ t = T(1, 2)$ assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "KW_DEFAULT_2")$$ t = T(1, 2, c="kw_arg_1")$ assert (t.a == 1) and (t.b == 2) and (t.c == "kw_arg_1") and (t.d == "KW_DEFAULT_2")$$ t = T(1, 2, d="kw_arg_2")$ assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "kw_arg_2")$$#######| Variable Positional |#########################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a):$  pass$$ t = T(1, 2, 3)$ assert (t.a == [1, 2, 3])$$#######| Variable Positional + Keyword |###############################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a, b="KW_DEFAULT_1"):$  pass$$ t = T(1, 2, 3)$ assert (t.a == [1, 2, 3]) and (t.b == "KW_DEFAULT_1")$$ t = T(1, 2, 3, b="kw_arg_1")$ assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1")$$#######| Variable Positional + Variable Keyword |######################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a, **kwargs):$  pass$$ t = T(1, 2, 3, b="kw_arg_1", c="kw_arg_2")$ assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1") and (t.c == "kw_arg_2")$$#######| Positional + Variable Positional + Keyword |##################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *b, c="KW_DEFAULT_1"):$  pass$$ t = T(1, 2, 3, c="kw_arg_1")$ assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1")$$#######| Positional + Variable Positional + Variable Keyword |#########################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *b, **kwargs):$  pass$$ t = T(1, 2, 3, c="kw_arg_1", d="kw_arg_2")$ assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1") and (t.d == "kw_arg_2")$$#######| Keyword Only |################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *, a="KW_DEFAULT_1"):$  pass$$ t = T(a="kw_arg_1")$ assert (t.a == "kw_arg_1")$$#######| Positional + Keyword Only |###################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *, b="KW_DEFAULT_1"):$  pass$$ t = T(1)$ assert (t.a == 1) and (t.b == "KW_DEFAULT_1")$$ t = T(1, b="kw_arg_1")$ assert (t.a == 1) and (t.b == "kw_arg_1")$$#######| Private __init__ Variables (underscored) |####################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b, _c):$  pass$$ t = T(1, 2, 3)$ assert hasattr(t, "a") and hasattr(t, "b") and not(hasattr(t, "_c")) 

Примечание:

я включал тесты, но разрушился их в последнюю строку() для краткости.   Вы можете развернуть тесты, в которых подробно описываются все возможные варианты использования, на find/replace -все всех $ символов с символом новой строки.

0

Для Python 3.3+:

from functools import wraps 
from inspect import Parameter, signature 


def instance_variables(f): 
    sig = signature(f) 
    @wraps(f) 
    def wrapper(self, *args, **kwargs): 
     values = sig.bind(self, *args, **kwargs) 
     for k, p in sig.parameters.items(): 
      if k != 'self': 
       if k in values.arguments: 
        val = values.arguments[k] 
        if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY): 
         setattr(self, k, val) 
        elif p.kind == Parameter.VAR_KEYWORD: 
         for k, v in values.arguments[k].items(): 
          setattr(self, k, v) 
       else: 
        setattr(self, k, p.default) 
    return wrapper 

class Point(object): 
    @instance_variables 
    def __init__(self, x, y, z=1, *, m='meh', **kwargs): 
     pass 

Демонстрация:

>>> p = Point('foo', 'bar', r=100, u=200) 
>>> p.x, p.y, p.z, p.m, p.r, p.u 
('foo', 'bar', 1, 'meh', 100, 200) 

Не-декоратора подход для обоих Python 2 и 3 с использованием кадров:

import inspect 


def populate_self(self): 
    frame = inspect.getouterframes(inspect.currentframe())[1][0] 
    for k, v in frame.f_locals.items(): 
     if k != 'self': 
      setattr(self, k, v) 


class Point(object): 
    def __init__(self, x, y): 
     populate_self(self) 

Демонстрация:

>>> p = Point('foo', 'bar') 
>>> p.x 
'foo' 
>>> p.y 
'bar' 
Смежные вопросы