인스턴스 변수를 자동으로 초기화 하시겠습니까?


89

다음과 같은 파이썬 클래스가 있습니다.

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

뒤에 :

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

C ++의 초기화 목록과 같이 이러한 인스턴스 변수를 자동 초기화하는 방법이 있습니까? 많은 중복 코드를 절약 할 수 있습니다.


1
autoassign활성 상태 레시피에 대한 토론 및 대체 autoargs구현을 참조하십시오. Python에서 자동 속성 할당을 수행하는 가장 좋은 방법은 무엇이며 좋은 생각입니까? -스택 오버플로
nealmcb

답변:


104

데코레이터를 사용할 수 있습니다.

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'

5
이것은 작동하고 질문에 대답하여 투표했습니다. 하지만 Ferdidand 베이어 답을 유지 : "노골적인 암시보다 낫다"
루카스 가브리엘 산체스

14
+1 내 문제를 해결 한 훌륭한 답변입니다. 그러나 그것은 언어의 핵심 기능이어야하지 않습니까? PEP를 작성할 가치가 있다고 생각하십니까?
Adam Matan

3
이것은 정말 좋은 대답입니다. 이것은 내 도구 상자에 곧바로 들어갔습니다.
Michael van der Westhuizen

3
@NadiaAlramli 코드에 작은 버그가 있다고 생각합니다. 여기를보세요 gist.github.com/pmav99/137dbf4681be9a58de74 (original.py)
pmav99 2013

2
현재 예제에는 버그가 있으며 서명에 기본 인수가 포함되어 있지 않으면 작동하지 않습니다. 이름과 기본값이 없음이 아닌지 확인하는 검사를 포함해야합니다. 예 : 이름 및 기본값 인 경우 :

36

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

이것은 특히 당신의 수업이 정말로 큰 가치의 가방 일 때 적절합니다.


2
"이것은 특히 당신의 수업이 정말로 큰 가치의 가방 일 때 적절합니다." 이러한 시나리오에서 다음을 수행 할 수도 있습니다. https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends
Big Sharpie

34

Python 3.7+의 경우 원하는 작업을 수행하는 매우 비단뱀적이고 유지 관리 가능한 방법 인 Data Class를 사용할 수 있습니다 .

자동으로 초기화되는 인스턴스 변수 인 클래스에 대한 필드 를 정의 할 수 있습니다 .

다음과 같이 보일 것입니다.

@dataclass
class Process:
    PID: int
    PPID: int
    cmd: str
    ...

__init__방법은 이미 클래스에있을 것입니다.

여기에 있습니다 힌트를 입력이 필요합니다 내가 사용하는 이유입니다, int그리고 str예에서. 필드 유형을 모르는 경우 typing모듈 에서 Any를 사용할 수 있습니다 .

데이터 클래스는 제안 된 솔루션에 비해 많은 장점이 있습니다.

  • 그것은는 명시 적으로 모든 필드는 파이썬의 선을 존중하고 읽기 쉽고 유지 보수하게하는, 볼 수 있습니다. 그것을 사용하는 **kwargs.
  • 메서드 를 가질 수 있습니다 . 다른 클래스와 마찬가지로.
  • 그것은 방법을 __init__사용하여 자동으로 넘어갈 수 있습니다 __post_init__.

편집 : NamedTuples 사용을 피하는 이유

일부는 namedtuple이 경우에 의 사용을 제안 하지만 namedtuple에는 Python 클래스와 다른 동작이 있습니다. 처음에는 분명하지 않으며 잘 알려져 있어야합니다.

1. NamedTuples는 불변입니다.

불변성은 유용 할 수 있지만 인스턴스에 원하는 것이 아닐 수도 있습니다. 당신이 인수를 사용하는 경우 DataClasses도 어떻게 든 불변 수 있습니다 frozen=True@dataclass장식을.

2. NamedTuples __eq__는 Tuple처럼 동작합니다.

파이썬에서, SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)입니다 True! __eq__비교에 사용되는 NamedTuple 의 함수는 해당 클래스 또는 필드의 이름이 아닌 비교 된 인스턴스에서 해당 값의 값과 위치 만 고려합니다.


이것은 클래스의 목적이 데이터 저장 인 경우에만 사용해야합니다.
JC Rocamonde

또는 데이터 저장을 중심으로 개발합니다.
JC Rocamonde

3
왜 데이터 클래스가 다른 동작이 아닌 데이터를 저장하는 클래스에만 사용되어야하는지 설명 하시겠습니까? 적절한 사용 사례를 더 잘 이해하기 위해 완전히 새로운 SO 게시물을 만들 수 있습니다. 감사.
Vahid Pazirandeh

Data Classes can be thought of as "mutable namedtuples with defaults". -PEP557
aafulei

26

Zen of Python 인용 ,

명시적인 것이 암시적인 것보다 낫습니다.


10
초기화 목록 선언이 충분히 명시 적이 지 않습니까?
Adam Matan

나는 추측한다. 그러나 나는 그런 것을 언어에 추가하는 이유를 보지 못했습니다. 나는 장면 뒤에서 일종의 장식 자 마술보다 다중 할당 문을 선호합니다.
Ferdinand Beyer

29
@Ferdinand, stdlib에 완벽하게 포함될 수있는 무언가를 언어로 갖는 것은 어리석은 일이라는 데 동의하지만, "아름다운 것이 추한 것보다 낫다"가 우선하고 많은 반복적 인 할당이 추악하기 때문에 stdlib에 있어야합니다. (모든 형태의 반복이 그렇듯이).
Alex Martelli

음, 카운터 : DWIM은> DWIS
테렌스 브래넌

나는 장식이 할당 목록보다 더 아름다운 동의하지만 PyCharm은 :-(을 이해하지 못하는하여 이보다합니다
user110954

23

당신이 할 수있는 또 다른 일 :

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


1
'자신'을 삭제하는 것이 좋습니다.
michael

15

다음과 같이 키워드 인자로 쉽게 할 수 있습니다.

>>> 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'

나에게 당신의 문제를 해결하지 못하는 것 같습니다.


3
내가 좋아하는 또 다른 변형은self.__dict__.update( **kwargs )
S.Lott

locals ()를 사용하고 일반 인수를 넣을 수도 있습니다.
mk12

7

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"

5

나는 같은 목적을 위해 무언가가 필요했지만 기존 답변 중 내가 테스트 한 모든 사례를 다루지 않았습니다. Nadia의 대답은 내가 찾던 것과 가장 가깝기 때문에 그녀의 코드를 기반으로 시작했습니다.

아래 데코레이터는 모든 유효한 인수 조합에서 작동합니다.

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        )

또한 클래스 인스턴스에 할당되지 않는 -private 변수 _를 허용 하는 표준 접두사 규칙을 구현합니다 __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"))

노트 :

테스트를 포함했지만 간결성을 위해 마지막 줄 ( 58 ) 로 축소했습니다 . find/replace모든 $문자에 개행 문자를 사용하여 모든 잠재적 사용 사례를 자세히 설명하는 테스트를 확장 할 수 있습니다 .


3

locals ()에 이미 값이 포함되어 있으므로 변수를 초기화 할 필요가 없습니다!

클래스 더미 (객체) :

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

d = 더미 (2, 3)

d. 매개 변수

{ 'a': 2, 'b': 3, 'default': 'Fred'}

d.params [ 'b']

물론 클래스 내에서 self.params를 사용할 수 있습니다.


그것은 좋은 원래의 접근 방식이지만, d['b']파이썬으로 작성되어 공용어 동안 d.params['b']코드 독자 혼란의 원인이됩니다.
Adam Matan 2014 년

3

getargspecPython 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")

2

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'

1

nu11ptr이 작은 모듈했다 PyInstanceVars 함수 장식 등이 기능을 포함한다. 모듈의 README에는 " [...] 성능이 CPython의 명시 적 초기화보다 30-40 % 더 나쁘다는 내용이 있습니다.".

사용 예, 모듈 설명서 에서 바로 가져옴 :

>>> 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'

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


0

init 함수 끝에 :

for var in list(locals().keys()):
    setattr(self,var,locals()[var])

자세한 내용 setattr()여기 를 참조 하십시오


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.