如何在python中从变量参数(kwargs)设置类属性


119

假设我有一个带有构造函数(或其他函数)的类,该类接受可变数量的参数,然后有条件地将其设置为类属性。

我可以手动设置它们,但是似乎变量参数在python中足够普遍,因此应该有一个共同的习惯用法。但是我不确定如何动态地执行此操作。

我有一个使用eval的示例,但这并不安全。我想知道正确的方法-也许用lambda吗?

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/...所以它看起来像我想的是,也许this--自.__ dict__ [key] = kwargs [key]
fijiaaron

与您的问题并不完全相关,但是您可能需要检查PEP8以获取有关常规Python样式的一些提示。
Thomas Orozco

有一个很棒的图书馆,叫做attrs。简单地pip install attrs,用修饰您的类@attr.s,并将args设置为a = attr.ib(); b = attr.ib()etc。在此处了解更多信息
亚当·巴恩斯

我在这里想念什么吗?您仍然需要做self.x = kwargs.get'x']您可以接受来自呼叫者的错别字。您必须使用额外的字符来创建客户端instance = Class(** {})如果您不跳圈self.x = kwargs.get'x']平凡,反正以后还会咬你吗?即,而不是self.x,您将以self .__ dict __ ['x']结尾。或getattr()键入多于self。
JGFMK

Answers:


146

您可以__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 '18

2
我认为您现在需要这个。.kwargs [0] .items()而不是kwargs.iteritems()-(我在撰写本文时使用的是Python 3.6.5)
JGFMK

@JGFMK为什么kwargs[0]不只是kwargs?可kwargs即使有一个整数的关键?我很确定它们必须是字符串。
wjandrea

145

您可以使用以下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方法存在的事实。如果要为named属性不可用时提供默认值,它也很有用。
larsk's

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. 通过以下任一方式将kwarg限制为预定义的属性
    • 默默地拒绝无效的参数,可选地,
    • 引发错误。

“直接”是指避免使用多余的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()(基于“ __”的存在排除在外),以及
  • 在from 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

这是通过幼虫最容易的

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.