如何获取Python中类属性的定义顺序?
我想定义一些轻量级的类,用来表示数据结构。很多数据结构中,数据的顺序是很重要的。所以如果我这样定义:
class User(DataStructure):
username = StringValue()
password = StringValue()
age = IntegerValue()
我是在暗示这是一个数据结构,其中用户名的字符串在最前面,接着是密码的字符串,最后是用户的年龄(以整数形式表示)。
如果你对Python有点了解,你会知道上面的这个类,User
,是从type
继承而来的对象。就像Python中的大多数对象一样,它会有一个__dict__
。而这就是我遇到的问题。这个__dict__
是一个哈希表,所以类属性在__dict__
中的顺序和它们定义的顺序没有任何关系。
有没有办法让我知道实际的定义顺序呢?我在这里问这个问题,是因为我不想用一些不太靠谱的方法...
哦,对了,我想要的是从上面的定义中得到这个:['username', 'password', 'age']
5 个回答
有一种方法比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__
并进行扩展,而不是将其设置为空列表,其实并不太难。
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 版本中可行。
在Python中,这个问题并没有得到很好的支持。Django使用了一种叫做元类的东西来处理这个问题。你可以看看这个问题:Django是怎么知道渲染表单字段的顺序的?
(总结一下:可以看看 django.forms.forms.DeclarativeFieldsMetaclass
、django.forms.forms.get_declared_fields
以及 creation_counter
是如何在 django.forms.fields.Field
中使用的。)