Как вы можете установить атрибуты класса из переменных аргументов (kwargs) в Python - PullRequest
84 голосов
/ 18 ноября 2011

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

Я мог бы установить их вручную, но кажется, что параметры переменнойв 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])

Ответы [ 10 ]

128 голосов
/ 18 ноября 2011

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

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

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

93 голосов
/ 18 ноября 2011

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

11 голосов
/ 16 ноября 2016

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

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

2 голосов
/ 26 ноября 2018

Следующие решения 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))
2 голосов
/ 18 ноября 2011
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'], но под прикрытием действительно происходит то же самое.

1 голос
/ 26 августа 2016

это самый простой из жаворонков

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
0 голосов
/ 09 января 2019

Еще один вариант, основанный на превосходных ответах 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 и выше.

0 голосов
/ 18 ноября 2011

Я подозреваю, что в большинстве случаев было бы лучше использовать именованные аргументы (для лучшего самодокументированного кода), поэтому это может выглядеть примерно так:

class Foo:
    def setAll(a=None, b=None, c=None):
        for key, value in (a, b, c):
            if (value != None):
                settattr(self, key, value)
0 голосов
/ 18 ноября 2011

Возможно, это лучшее решение, но мне на ум приходит следующее:

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}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...