Programing

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

lottogame 2020. 9. 8. 21:50
반응형

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


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

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

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])

__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사용).


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

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

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


여기에서 대부분의 답변은 허용 된 모든 속성을 하나의 기본값으로 초기화하는 좋은 방법을 다루지 않습니다. 따라서 @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)

허용 된 속성 외에도 속성의 기본값 을 설정할 수 있는 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().


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

I called the class SymbolDict because it essentially is a dictionary that operates using symbols instead of strings. In other words, you do x.foo instead of x['foo'] but under the covers it's really the same thing going on.


The following solutions vars(self).update(kwargs) or self.__dict__.update(**kwargs) are not robust, because the user can enter any dictionary with no error messages. If I need to check that the user insert the following signature ('a1', 'a2', 'a3', 'a4', 'a5') the solution does not work. Moreover, the user should be able to use the object by passing the "positional parameters" or the "kay-value pairs parameters".

So I suggest the following solution by using a metaclass.

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

this one is the easiest via larsks

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

my example:

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

Their might be a better solution but what comes to mind for me is:

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}

Yet another variant based on the excellent answers by mmj and fqxp. What if we want to

  1. Avoid hardcoding a list of allowed attributes
  2. Directly and explicitly set default values for each attributes in the constructor
  3. Restrict kwargs to predefined attributes by either
    • silently rejecting invalid arguments or, alternatively,
    • raising an error.

By "directly", I mean avoiding an extraneous default_attributes dictionary.

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

Not a major breakthrough, but maybe useful to someone...

EDIT: If our class uses @property decorators to encapsulate "protected" attributes with getters and setters, and if we want to be able to set these properties with our constructor, we may want to expand the allowed_keys list with values from dir(self), as follows:

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

The above code excludes

  • any hidden variable from dir() (exclusion based on presence of "__"), and
  • any method from dir() whose name is not found in the end of an attribute name (protected or otherwise) from __dict__.keys(), thereby likely keeping only @property decorated methods.

This edit is likely only valid for Python 3 and above.


I suspect it might be better in most instances to use named args (for better self documenting code) so it might look something like this:

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

참고URL : https://stackoverflow.com/questions/8187082/how-can-you-set-class-attributes-from-variable-arguments-kwargs-in-python

반응형