42

在 python 中,我们如何在没有预定义类的情况下创建一个新对象,然后为它动态添加属性?

例子:

dynamic_object = Dynamic()
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"

最好的方法是什么?

编辑因为很多人在评论中建议我可能不需要这个。

问题是我有一个序列化对象属性的函数。出于这个原因,由于某些构造函数的限制,我不想创建预期类的对象,而是创建一个类似的对象,比如模拟,添加我需要的任何“自定义”属性,然后将其反馈给功能。

4

7 回答 7

46

只需定义您自己的类即可:

class Expando(object):
    pass

ex = Expando()
ex.foo = 17
ex.bar = "Hello"
于 2012-12-23T23:44:46.377 回答
23

如果您从@Martijn 的答案中采用元分类方法,@Ned 的答案可以改写得更短(尽管它显然不那么可读,但做同样的事情)。

obj = type('Expando', (object,), {})()
obj.foo = 71
obj.bar = 'World'

或者只是,它与上面使用dict参数的作用相同:

obj = type('Expando', (object,), {'foo': 71, 'bar': 'World'})()

对于 Python 3,bases不需要将对象传递给参数(请参阅type 文档)。

但是对于简单的情况,实例化没有任何好处,所以可以这样做:

ns = type('Expando', (object,), {'foo': 71, 'bar': 'World'})

同时,我个人更喜欢一个简单且易读的临时测试配置案例的普通类(即没有实例化):

class ns:
    foo = 71
    bar = 'World'

更新

在 Python 3.3+ 中,正是OP 所要求的,types.SimpleNamespace. 只是:

一个简单的object子类,提供对其命名空间的属性访问,以及有意义的 repr。

与 不同objectSimpleNamespace您可以添加和删除属性。如果SimpleNamespace使用关键字参数初始化对象,则这些参数会直接添加到底层命名空间。

import types
obj = types.SimpleNamespace()
obj.a = 123
print(obj.a) # 123
print(repr(obj)) # namespace(a=123)

但是,在 Python 2 和 Python 3 的 stdlib 中argparse.Namespace,都有相同的目的:

用于存储属性的简单对象。

通过属性名称和值实现相等,并提供简单的字符串表示。

import argparse
obj = argparse.Namespace()
obj.a = 123
print(obj.a) # 123 
print(repr(obj)) # Namespace(a=123)

请注意,两者都可以使用关键字参数进行初始化:

types.SimpleNamespace(a = 'foo',b = 123)
argparse.Namespace(a = 'foo',b = 123)
于 2017-04-10T15:38:39.123 回答
22

使用一个对象来保存值并不是最 Pythonic 的编程风格。这在没有良好关联容器的编程语言中很常见,但在 Python 中,您可以使用字典:

my_dict = {} # empty dict instance

my_dict["foo"] = "bar"
my_dict["num"] = 42

您还可以使用“字典文字”来一次定义字典的内容:

my_dict = {"foo":"bar", "num":42}

或者,如果您的键都是合法标识符(如果您计划将它们作为属性名称,它们将是),您可以使用dict带有关键字参数的构造函数作为键值对:

my_dict = dict(foo="bar", num=42) # note, no quotation marks needed around keys

填写字典实际上是 Python 在您使用对象时在幕后所做的事情,例如在 Ned Batchelder 的回答中。他的ex对象的属性存储在字典中ex.__dict__,最终应该等于dict直接创建的等价物。

除非ex.foo绝对需要属性语法(例如 ),否则您也可以完全跳过该对象并直接使用字典。

于 2012-12-24T01:46:45.407 回答
5

使用collections.namedtuple()类工厂为您的返回值创建一个自定义类:

from collections import namedtuple
return namedtuple('Expando', ('dynamic_property_a', 'dynamic_property_b'))('abc', 'abcdefg')

返回的值既可以用作元组,也可以通过属性访问:

print retval[0]                  # prints 'abc'
print retval.dynamic_property_b  # prints 'abcdefg'  
于 2012-12-24T09:45:06.497 回答
3

我发现的一种方法也是创建一个 lambda。它可能有副作用,并带有一些不需要的属性。只是为了兴趣发帖。

dynamic_object = lambda:expando
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"
于 2012-12-23T23:41:47.167 回答
1

我首先定义一个字典,因为它很容易定义。然后我用namedtuple它来将它转换为一个对象:

from collections import namedtuple

def dict_to_obj(dict):
    return namedtuple("ObjectName", dict.keys())(*dict.values())

my_dict = {
    'name': 'The mighty object',
    'description': 'Yep! Thats me',
    'prop3': 1234
}
my_obj =  dict_to_obj(my_dict)
于 2021-04-20T22:03:26.020 回答
0

Ned Batchelder 的回答是最好的。我只是想在这里记录一个稍微不同的答案,它避免使用class关键字(如果出于指导性原因,关闭演示等有用)

只需定义您自己的类即可:

def Expando():
    def inst():
        None
    return inst

ex = Expando()
ex.foo = 17
ex.bar = "Hello"
于 2021-08-11T00:12:00.690 回答