Подтвердить что ты не робот

Как вы можете установить атрибуты класса из переменных аргументов (kwargs) в python

Предположим, у меня есть класс с конструктором (или другой функцией), который принимает переменное число аргументов и затем устанавливает их как атрибуты класса условно.

Я мог бы установить их вручную, но кажется, что переменные параметры достаточно распространены в python, поэтому для этого должна быть общая идиома. Но я не уверен, как это сделать динамически.

У меня есть пример использования 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])
4b9b3361

Ответ 1

Вы можете обновить атрибут __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)

вы можете отфильтровать ключи заранее (используйте iteritems вместо items если вы все еще используете Python 2.x).

Ответ 2

Вы можете использовать метод setattr():

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

Существует аналогичный getattr() для получения атрибутов.

Ответ 3

Большинство ответов здесь не охватывают хороший способ инициализации всех разрешенных атрибутов только одним значением по умолчанию. Итак, чтобы добавить к ответам, данным @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)

Ответ 4

Я предлагаю вариант ответа 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().

Ответ 5

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, но под обложками действительно происходит то же самое.

Ответ 6

Следующие решения 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))

Ответ 7

этот самый простой с помощью 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

Ответ 8

Их может быть лучшим решением, но для меня приходит в голову следующее:

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}

Ответ 9

Еще один вариант, основанный на превосходных ответах mmj и fqxp. Что если мы хотим

  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 для инкапсуляции "защищенных" атрибутов с помощью методов получения и установки, и если мы хотим иметь возможность установить эти свойства с помощью нашего конструктора, мы можем захотеть расширить список 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 и выше.

Ответ 10

Я подозреваю, что в большинстве случаев было бы лучше использовать именованные 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)