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

时间:2011-11-18 18:18:25

标签: python

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

我可以手动设置它们,但似乎变量参数在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])

11 个答案:

答案 0 :(得分:127)

您可以使用setattr()方法:

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

有一种用于检索属性的类似getattr()方法。

答案 1 :(得分:90)

您可以使用关键字参数更新__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 :(得分:9)

此处的大多数答案都没有涵盖将所有允许的属性初始化为一个默认值的好方法。 因此,要添加@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)

答案 3 :(得分:5)

我建议使用fqxp's answer的变体,除 允许的属性 外,还可以设置 默认值 属性:

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

答案 4 :(得分: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'],但在幕后,它实际上是一样的。

答案 5 :(得分: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))

答案 6 :(得分:1)

这是最简单的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

答案 7 :(得分:0)

他们可能是一个更好的解决方案,但我想到的是:

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}

答案 8 :(得分:0)

根据mmjfqxp的出色回答,又有了另一个变体。如果我们想

  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装饰器通过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()中的任何隐藏变量(基于“ __”的存在排除在外),以及
  • dir()中的任何方法,其名称都没有在__dict__.keys()的属性名称(受保护或其他方式)的末尾找到,因此可能仅保留@property装饰的方法。

此修改可能仅对Python 3及更高版本有效。

答案 9 :(得分:0)

setattr()__dict__.update()方法都绕过属性@setter函数。我发现使它生效的唯一方法是使用exec()

exec被认为是安全隐患,但是我们没有将其与任何旧的用户输入一起使用,因此我不知道为什么会这样。如果您不同意我真的很想知道为什么,请发表评论。我不想提倡或提交不安全的代码。

我的示例主要是从先前的答案中借来的,以确保完整性,但主要区别是exec(f"self.{key} = '{value}'")

class Foo:
    def __init__(self, **kwargs):
        # Predefine attributes with default values
        self.a = 0
        self.b = 0
        self.name = " "
        

        # get a list of all predefined attributes
        allowed_keys = [attr for attr in dir(self) if not attr.startswith("_")]
        for key, value in kwargs.items():
            # Update properties, but only for keys that have been predefined 
            # (silently ignore others)
            if key in allowed_keys:
                exec(f"self.{key} = '{value}'")

    @property
    def name(self):
        return f"{self.firstname} {self.lastname}"
    
    @name.setter
    def name(self, name):
        self.firstname, self.lastname = name.split(" ", 2)

答案 10 :(得分:-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)