파이썬에서 변수 인수 (kwargs)에서 클래스 속성을 설정하는 방법


119

가변 개수의 인수를 취한 다음 조건부로 클래스 속성으로 설정하는 생성자 (또는 다른 함수)가있는 클래스가 있다고 가정합니다.

수동으로 설정할 수는 있지만 파이썬에서는 변수 매개 변수가 충분히 일반적이므로이를 수행하는 공통 관용구가 있어야합니다. 그러나 이것을 동적으로 수행하는 방법을 모르겠습니다.

eval을 사용하는 예가 있지만 거의 안전하지 않습니다. 이 작업을 수행하는 적절한 방법을 알고 싶습니다. 아마도 람다로?

class Foo:
    def setAllManually(self, a=None, b=None, c=None):
        if a!=None: 
            self.a = a
        if b!=None:
            self.b = b
        if c!=None:
            self.c = c
    def setAllWithEval(self, **kwargs):
        for key in **kwargs:
            if kwargs[param] != None
                eval("self." + key + "=" + kwargs[param])

이 질문은 비슷해 보입니다. stackoverflow.com/questions/3884612/… stackoverflow.com/questions/356718/… stackoverflow.com/questions/1446555/… 그래서 내가 원하는 것은 아마도 이것이-self .__ dict__ 인 것 같습니다. [key] = kwargs [key]
fijiaaron 2011

질문과 실제로 관련이 없지만 PEP8 에서 기존 Python 스타일에 대한 몇 가지 힌트 를 확인하고 싶을 수 있습니다 .
Thomas Orozco

attrs라는 환상적인 라이브러리가 있습니다. 간단히 pip install attrs, 클래스를 장식 @attr.s하고 args를 a = attr.ib(); b = attr.ib()etc 로 설정 하십시오 . 여기에서 더 많은 것을 읽으 십시오 .
Adam Barnes

여기에 뭔가 빠졌나요? self.x = kwargs.get'x '] 당신은 발신자의 오타에 자신을 열어야합니다. 당신은 추가 문자로 클라이언트를 만들어야합니다. instance = Class (** {}) 당신이 농구를 통해 뛰어 내리지 않는다면 self.x = kwargs.get'x '] 평범함, 어쨌든 나중에 당신을 물지 않을까요? 즉, self.x 대신에 self .__ dict __ [ 'x']로 끝날 것입니다. 맞습니까? 또는 getattr () self보다 타이핑이 더 많습니다.
JGFMK

답변:


148

__dict__키워드 인수를 사용하여 속성 (사전 형식으로 클래스 속성을 나타냄)을 업데이트 할 수 있습니다 .

class Bar(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

그러면 다음을 수행 할 수 있습니다.

>>> bar = Bar(a=1, b=2)
>>> bar.a
1

그리고 다음과 같이 :

allowed_keys = {'a', 'b', 'c'}
self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)

키를 미리 필터링 할 수 있습니다 ( Python 2.x를 사용하는 iteritems대신 items사용).


2
self.__dict__.update(locals())위치 인수도 복사 하는 데 사용하면 더 좋습니다 .
Giancarlo Sportelli

2
나는 당신이 요즘 필요하다고 생각합니다 .. kwargs.iteritems () 대신 kwargs [0] .items ()-(작성할 때 Python 3.6.5를 사용하고 있습니다)
JGFMK

@JGFMK 왜 kwargs[0]대신에 kwargs? kwargs정수 키도 가질 수 있습니까 ? 나는 그들이 문자열이어야한다고 확신합니다.
wjandrea

146

다음 setattr()방법을 사용할 수 있습니다 .

class Foo:
  def setAllWithKwArgs(self, **kwargs):
    for key, value in kwargs.items():
      setattr(self, key, value)

getattr()속성을 검색 하는 유사한 방법 이 있습니다 .


@larsks 감사하지만 사전 키만 풀 수있는 방법에 대한 아이디어가 있습니까? stackoverflow.com/questions/41792761/…
JinSnow

사용해야 .getattr()합니까? 또는 Foo.key? 를 사용하여 속성에 액세스 할 수 있습니다 .
Stevoisiak

@StevenVascellaro 물론 사용할 수 있습니다 Foo.attrname. 그 getattr방법이 존재 한다는 사실 만 지적한 것 같습니다 . 명명 된 속성을 사용할 수없는 경우 기본값을 제공하려는 경우에도 유용합니다.
larsks

3
수락 된 답변 과 다른 점은 무엇입니까 ? . 그들의 장단점은 무엇입니까?
Eduardo Pignatelli

15

여기서 대부분의 답변은 허용되는 모든 속성을 하나의 기본값으로 초기화하는 좋은 방법을 다루지 않습니다. 따라서 @fqxp@mmj가 제공하는 답변에 추가하려면 다음을 수행하십시오 .

class Myclass:

    def __init__(self, **kwargs):
        # all those keys will be initialized as class attributes
        allowed_keys = set(['attr1','attr2','attr3'])
        # initialize all allowed keys to false
        self.__dict__.update((key, False) for key in allowed_keys)
        # and update the given keys by their given values
        self.__dict__.update((key, value) for key, value in kwargs.items() if key in allowed_keys)

나는 이것이에 대한 초기화로 인해 가장 완전한 대답이라고 생각합니다 False. 좋은 지적!
Kyrol

9

허용 된 속성 외에도 속성 에 대한 기본값 을 설정할 수 있는 fqxp의 답변 변형을 제안 합니다.

class Foo():
    def __init__(self, **kwargs):
        # define default attributes
        default_attr = dict(a=0, b=None, c=True)
        # define (additional) allowed attributes with no default value
        more_allowed_attr = ['d','e','f']
        allowed_attr = list(default_attr.keys()) + more_allowed_attr
        default_attr.update(kwargs)
        self.__dict__.update((k,v) for k,v in default_attr.items() if k in allowed_attr)

이것은 Python 3.x 코드입니다. Python 2.x iteritems()의 경우 items().


1
이것은이 스레드의 다른 접근 방식을 요약하는 가장 유연한 대답입니다. 속성을 설정하고 기본값을 허용하며 허용 된 속성 이름 만 추가합니다. 여기에 표시된대로 python 3.x에서 잘 작동합니다.
squarespiral

7

mmjfqxp 의 우수한 답변을 기반으로 한 또 다른 변형 입니다. 우리가 원한다면

  1. 허용 된 속성 목록을 하드 코딩하지 마십시오.
  2. 생성자의 각 속성에 대해 직접 적이고 명시 적으로 기본값 설정
  3. kwargs를 미리 정의 된 속성으로 제한합니다.
    • 자동으로 유효하지 않은 인수를 거부 하거나 대안,
    • 오류가 발생합니다.

"직접"이란 외부 default_attributes사전을 피하는 것을 의미 합니다.

class Bar(object):
    def __init__(self, **kwargs):

        # Predefine attributes with default values
        self.a = 0
        self.b = 0
        self.A = True
        self.B = True

        # get a list of all predefined values directly from __dict__
        allowed_keys = list(self.__dict__.keys())

        # Update __dict__ but only for keys that have been predefined 
        # (silently ignore others)
        self.__dict__.update((key, value) for key, value in kwargs.items() 
                             if key in allowed_keys)

        # To NOT silently ignore rejected keys
        rejected_keys = set(kwargs.keys()) - set(allowed_keys)
        if rejected_keys:
            raise ValueError("Invalid arguments in constructor:{}".format(rejected_keys))

큰 돌파구는 아니지만 누군가에게 유용 할 수도 있습니다 ...

편집 : 클래스가 @property데코레이터를 사용 하여 "보호 된"속성을 getter 및 setter로 캡슐화하고 생성자로 이러한 속성을 설정할 수 있도록하려면 다음과 같이 allowed_keys값으로 목록 을 확장 할 수 dir(self)있습니다.

allowed_keys = [i for i in dir(self) if "__" not in i and any([j.endswith(i) for j in self.__dict__.keys()])]

위의 코드는 다음을 제외합니다.

  • 숨겨진 변수 dir()( "__"의 존재를 기준으로 제외) 및
  • dir()의 속성 이름 (보호 또는 기타) 끝에 이름이없는 모든 메서드는 __dict__.keys()@property 데코 레이팅 된 메서드 만 유지합니다.

이 편집은 Python 3 이상에서만 유효합니다.


2
class SymbolDict(object):
  def __init__(self, **kwargs):
    for key in kwargs:
      setattr(self, key, kwargs[key])

x = SymbolDict(foo=1, bar='3')
assert x.foo == 1

SymbolDict본질적으로 문자열 대신 기호를 사용하여 작동하는 사전이기 때문에 클래스를 호출했습니다 . 다른 말로하면, 당신은 x.foo대신에 그렇게 x['foo']하지만 실제로는 똑같은 일이 진행되고 있습니다.


2

다음 솔루션 vars(self).update(kwargs)또는self.__dict__.update(**kwargs) 사용자가 오류 메시지와 함께 어떤 사전을 입력 할 수 있기 때문에, 강력한되지 않습니다. 사용자가 다음 서명 ( 'a1', 'a2', 'a3', 'a4', 'a5')을 삽입했는지 확인해야하는 경우 솔루션이 작동하지 않습니다. 또한 사용자는 "위치 매개 변수"또는 "kay-value 쌍 매개 변수"를 전달하여 객체를 사용할 수 있어야합니다.

그래서 저는 메타 클래스를 사용하여 다음과 같은 해결책을 제안합니다.

from inspect import Parameter, Signature

class StructMeta(type):
    def __new__(cls, name, bases, dict):
        clsobj = super().__new__(cls, name, bases, dict)
        sig = cls.make_signature(clsobj._fields)
        setattr(clsobj, '__signature__', sig)
        return clsobj

def make_signature(names):
    return Signature(
        Parameter(v, Parameter.POSITIONAL_OR_KEYWORD) for v in names
    )

class Structure(metaclass = StructMeta):
    _fields = []
    def __init__(self, *args, **kwargs):
        bond = self.__signature__.bind(*args, **kwargs)
        for name, val in bond.arguments.items():
            setattr(self, name, val)

if __name__ == 'main':

   class A(Structure):
      _fields = ['a1', 'a2']

   if __name__ == '__main__':
      a = A(a1 = 1, a2 = 2)
      print(vars(a))

      a = A(**{a1: 1, a2: 2})
      print(vars(a))

1

더 나은 해결책이 될 수 있지만 나에게 떠오르는 것은 다음과 같습니다.

class Test:
    def __init__(self, *args, **kwargs):
        self.args=dict(**kwargs)

    def getkwargs(self):
        print(self.args)

t=Test(a=1, b=2, c="cats")
t.getkwargs()


python Test.py 
{'a': 1, 'c': 'cats', 'b': 2}

내가 찾고있는 것은 유효성 검사를 기반으로 조건부로 속성을 설정하는 것입니다. kwargs 사용의 문제점은 어떤 속성이 허용되는지 검증 (또는 문서화)하지 않는다는 것임을 깨달았습니다
fijiaaron

예, @larsks 답변이 더 낫다는 것을 알고 있습니다. SO에서 매일 새로운 것을 배우십시오!
Tom

1

이것은 larsks 를 통해 가장 쉬운 것입니다

class Foo:
    def setAllWithKwArgs(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

내 예 :

class Foo:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

door = Foo(size='180x70', color='red chestnut', material='oak')
print(door.size) #180x70

smb가 kwargs.items ()가 무엇인지 설명 할 수 있습니까?
Oleg_Kornilov

kwargs키워드 인수의 사전이며 items()사전의 (key, value)쌍 목록 사본을 리턴하는 메소드입니다 .
harryscholes

-1

대부분의 경우 명명 된 args (더 나은 자체 문서화 코드를 위해)를 사용하는 것이 더 나을 수 있으므로 다음과 같이 보일 수 있습니다.

class Foo:
    def setAll(a=None, b=None, c=None):
        for key, value in (a, b, c):
            if (value != None):
                settattr(self, key, value)

이 반복은 작동하지 않습니다.for key, value in (a, b, c)
rerx
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.