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

119

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

Я мог бы установить их вручную, но кажется, что параметры переменных достаточно распространены в 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])
Fijiaaron
источник
Похоже, эти вопросы похожи: stackoverflow.com/questions/3884612/… stackoverflow.com/questions/356718/… stackoverflow.com/questions/1446555/… так что похоже, что я хочу, может быть, это - self .__ dict__ [key] = kwargs [key]
fijiaaron
На самом деле это не имеет отношения к вашему вопросу, но вы можете проверить PEP8, чтобы получить несколько советов по традиционному стилю Python.
Thomas Orozco
Для этого есть фантастическая библиотека attrs. просто pip install attrsукрасьте свой класс @attr.sи установите аргументы как a = attr.ib(); b = attr.ib()и т. д. Подробнее здесь .
Адам Барнс
Я что-то упустил? Вам все еще нужно сделать self.x = kwargs.get'x '] Вы открываете себя для опечаток от вызывающего. Вам нужно создать клиента с дополнительными символами instance = Class (** {}) Если вы не прыгаете через обручи с обыденность self.x = kwargs.get'x '], разве она не укусит тебя позже? т.е. вместо self.x вы получите self .__ dict __ ['x'], верно? Или getattr () Либо больше набора текста, чем self.
JGFMK

Ответы:

149

Вы можете обновить __dict__атрибут (который представляет атрибуты класса в форме словаря) с помощью ключевого слова arguments:

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

fqxp
источник
2
Еще лучше, если вы используете self.__dict__.update(locals())для копирования также позиционные аргументы.
Джанкарло Спортелли
2
Я думаю, вам это понадобится сейчас ... kwargs [0] .items () вместо kwargs.iteritems () - (Я использую 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
источник
@larsks, спасибо, но есть идеи, как можно распаковать только ключ словаря? stackoverflow.com/questions/41792761/…
JinSnow 02
Вам нужно использовать .getattr()? Или вы можете получить доступ к атрибутам с помощью Foo.key?
Stevoisiak
@StevenVascellaro, вы, конечно, можете просто использовать Foo.attrname. Думаю, я просто указывал на то, что getattrметод существует. Это также полезно, если вы хотите указать значение по умолчанию, когда именованный атрибут недоступен.
larsks
3
В чем разница с принятым ответом? . Каковы их плюсы и минусы?
Эдуардо Пиньятелли
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. Хорошая точка зрения!
Кайрол
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().

MMJ
источник
1
Это наиболее гибкий ответ, обобщающий другие подходы в этой ветке. Он устанавливает атрибуты, допускает значения по умолчанию и добавляет только разрешенные имена атрибутов. Прекрасно работает с python 3.x, как показано здесь.
squarespiral
7

Еще один вариант, основанный на отличных ответах 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(), имя которого не находится в конце имени атрибута (защищенного или иного) from __dict__.keys(), таким образом, вероятно, сохраняются только методы, украшенные @property.

Это изменение, вероятно, действительно только для Python 3 и выше.

billjoie
источник
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'), решение не сработает. Более того, пользователь должен иметь возможность использовать объект, передав «позиционные параметры» или «параметры пары значений и значений».

Поэтому я предлагаю следующее решение с использованием метакласса.

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))
antonjs
источник
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

это самый простой через ларск

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
Олег_Корнилов
источник
не мог бы кто-нибудь объяснить, что такое kwargs.items ()?
Oleg_Kornilov
kwargs- словарь аргументов ключевого слова и items()метод, возвращающий копию списка (key, value)пар словаря .
harryscholes
-1

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

class Foo:
    def setAll(a=None, b=None, c=None):
        for key, value in (a, b, c):
            if (value != None):
                settattr(self, key, value)
Fijiaaron
источник
Эта итерация не работает:for key, value in (a, b, c)
rerx