【问题标题】:How do I get the definition order of class attributes in Python?如何获取 Python 中类属性的定义顺序?
【发布时间】:2011-05-12 08:50:18
【问题描述】:

我想定义应该表示数据结构的轻量级类。与许多数据结构一样,数据的顺序很重要。所以如果我继续定义这个:

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

我的意思是这是一个数据结构,其中首先是一个带有用户名的字符串,然后是一个带有密码的字符串,最后是用户的年龄作为整数。

如果你熟悉 Python,就会知道上面的类 User 是一个继承自 type 的对象。就像 Python 中的大多数其他对象一样,它将具有__dict__。这就是我的问题。这个__dict__ 是一个哈希映射,所以__dict__ 中的类属性的顺序与它们的定义顺序没有任何关系。

有什么办法可以找出实际的定义顺序吗?在我使用我能想到的不太理智的方法之一之前,我在这里问...

哦,要明确一点,我想要的是一种摆脱上述定义的方法:['username', 'password', 'age']

【问题讨论】:

  • 作为参考,我的“不太理智”的解决方案:每次实例化 *Value 类时都有一个计数器上升,并将新值设置为该实例,然后按此排序属性列表价值。这将使用元类来完成。
  • 按照您在此处描述的方式使用元类是我唯一想到的事情。
  • 嗯,计数器是 Django 团队的做法,所以我相当肯定这是最好的做法。

标签: python oop class data-structures hash


【解决方案1】:

Python 2.7 和 3.x 在集合模块中定义了一个 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 中才有可能。

【讨论】:

    【解决方案2】:

    这在 Python 中根本没有得到很好的支持。 Django 使用元类来处理它。看到这个问题:How does Django Know the Order to Render Form Fields?

    (总结:看看django.forms.forms.DeclarativeFieldsMetaclassdjango.forms.forms.get_declared_fields以及creation_counterdjango.forms.fields.Field中是如何使用的。)

    【讨论】:

    • 听起来 Django 完成了我的计划。我会查看他们的代码并调查 Tobu 的答案,乍一看它似乎更正确。
    • Tobu 和 Don 的答案很容易实现,但在任何 Python 2.x 版本中都不支持。所以就目前而言,Django 解决方案是我的最佳选择。谢谢!
    【解决方案3】:

    一种比 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__)。在python3中,我更喜欢__prepare__

    除了有点难看之外,它的主要缺点是它不适用于继承。将__attrs__ 从基类中取出并扩展它而不是将其设置为空列表并不难。

    【讨论】:

    • 这是一个逻辑上优雅的解决方案,但我正在寻找一种获取定义顺序的方法的唯一原因是这样我就不会使 struct defs 的语法变得更加复杂。所以我可能会选择 Django 解决方案,因为我的结构实际上有一个特定的基类。不过谢谢! :)
    【解决方案4】:

    您可以使用__slots__ 来显式排序属性。如果您继承的每个类都定义了它,那么在内存和属性访问方面会有额外的性能提升。

    编辑:最可靠的方法是在元类中定义__prepare__。参考文档有它,complete with an example of storing attributes in an OrderedDictionary

    如果没有 Django 的字段类技术,您将无法在 Python 2 中获得隐式排序,因为 Python 只是不跟踪该信息;在调用元类时它会丢失。

    【讨论】:

    • 需要注意的是__prepare__只在python 3中可用。
    • 抱歉,我忘了说我希望它与 Python 2.7 一起使用。 prepare 似乎很新。另外,您知道可以动态分配 slots 的实现吗?我尝试了一点,但无法在 2.7 中使用。
    • 不,我在想“显式优于隐式”。将__slots__ 放入源中,并强制__dict__ 未定义或忽略非插槽属性。
    • 哦,感谢__prepare__ 的提醒,我会把它放在脑后直到它成熟:)
    • 我的方法是在第一次引入错误代码时将内容更新而不会出错。忘记订购新的 attr?没有适合你的课程。
    【解决方案5】:

    下面让我捕获方法或结构类型的属性的顺序。我还没有尝试过使用内置类型。然后,您可以围绕儿童

    构建一个步行者 类ordered_type(类型): __ordernate__ = 0 def __new__(meta, name, baseclasses, dct): new = super(Type, meta).__new__(meta, name, baseclasses, dct) new.__ordernate__ = 类型.__ordernate__ 类型.__ordernate__ += 1 定义键(x): 从检查导入方法 如果是方法(x): 返回 x.im_func.__code__.co_firstlineno elif isinstance(x,类型): 返回 x.__ordernate__ + 1000000 new.__children__ = 排序( [getattr(new, x) for x in dir(new) if not x.startswith('__')], 键=键) 类结构(对象): __metaclass__ = 有序类型 #用例 A类(结构): '''A类描述 ''' 定义名称(自我): '''A的实例名称 ''' B类(结构): '''B类描述 ''' 定义名称(自我): '''B实例的名称 '''

    【讨论】:

      猜你喜欢
      • 2013-05-07
      • 2019-05-20
      • 1970-01-01
      • 1970-01-01
      • 2015-09-01
      • 2011-01-12
      • 2023-03-07
      • 1970-01-01
      • 2015-06-17
      相关资源
      最近更新 更多