如何获取Python中类属性的定义顺序?

7 投票
5 回答
4395 浏览
提问于 2025-04-16 07:18

我想定义一些轻量级的类,用来表示数据结构。很多数据结构中,数据的顺序是很重要的。所以如果我这样定义:

class User(DataStructure):
    username = StringValue()
    password = StringValue()
    age = IntegerValue()

我是在暗示这是一个数据结构,其中用户名的字符串在最前面,接着是密码的字符串,最后是用户的年龄(以整数形式表示)。

如果你对Python有点了解,你会知道上面的这个类,User,是从type继承而来的对象。就像Python中的大多数对象一样,它会有一个__dict__。而这就是我遇到的问题。这个__dict__是一个哈希表,所以类属性在__dict__中的顺序和它们定义的顺序没有任何关系。

有没有办法让我知道实际的定义顺序呢?我在这里问这个问题,是因为我不想用一些不太靠谱的方法...

哦,对了,我想要的是从上面的定义中得到这个:['username', 'password', 'age']

5 个回答

2

有一种方法比Django的做法更通用,__slots__ 也可以在Python 2中使用,那就是这种元类:

class OrderedTypeMeta(type):
    def __new__(mcls, clsname, bases, clsdict):
        attrs = clsdict.get('_attrs_', [])
        attrnames = []
        for name, value in attrs:
            clsdict[name] = value
            attrnames.append(name) 
        clsdict['_attrs_'] = attrnames
        return super(OrderedTypeMeta, mcls).__new__(mcls, clsname, bases, clsdict)


class User(DataStructure):
    __metaclass__ = OrderedTypeMeta
    _attrs_ = (('name', StringValue()),
               ('password', StringValue()),
               ('age', IntegerValue()))

我说它比Django的方法更通用,是因为你不需要属性是某个特定类的实例,任何值都可以用。而且它也比__slots__更通用,因为你仍然可以给类的实例赋值属性(虽然这可能不是必须的:在这种情况下,我会更倾向于使用__slots__)。在Python 3中,我更喜欢使用__prepare__

这种方法的主要缺点,除了看起来有点丑之外,就是它不支持继承。要从基类中提取__attrs__并进行扩展,而不是将其设置为空列表,其实并不太难。

5

Python 2.7 和 3.x 在 collections 模块中定义了一个叫 OrderedDict 的东西。我觉得它是用链表来保持插入顺序的,也就是说,它能记住你添加元素的顺序。它还增加了一些可以遍历的方法,和标准的可变映射方法相比,多了一些功能。

你可以定义一个元类,使用 OrderedDict 来代替标准的无序 dict,作为你的数据结构类的命名空间 __dict__。如果你给你的元类一个特别的 __prepare__() 方法,就可以做到这一点。我还没试过,但根据文档,这确实是可行的:

来自 Python 3.1 语言参考的第 3.3.3 节 数据模型 - 自定义类创建:

If the metaclass has a __prepare__() attribute (usually implemented as a class 
or static method), it is called before the class body is evaluated with the 
name of the class and a tuple of its bases for arguments. It should return an 
object that supports the mapping interface that will be used to store the 
namespace of the class. The default is a plain dictionary. This could be used, 
for example, to keep track of the order that class attributes are declared in 
by returning an ordered dictionary.

不过,遗憾的是,Python 2.7 语言参考的第 3.4.3 节并没有提到可以替换类的命名空间字典,也没有提到 __prepare__() 方法。所以这可能只在 Python 3 版本中可行。

4

在Python中,这个问题并没有得到很好的支持。Django使用了一种叫做元类的东西来处理这个问题。你可以看看这个问题:Django是怎么知道渲染表单字段的顺序的?

(总结一下:可以看看 django.forms.forms.DeclarativeFieldsMetaclassdjango.forms.forms.get_declared_fields 以及 creation_counter 是如何在 django.forms.fields.Field 中使用的。)

撰写回答