如何以原始订单获取字段?

时间:2010-07-20 08:17:15

标签: python django introspection

我有一个代码:

class Ordered(object):
    x = 0
    z = 0
    b = 0
    a = 0

print(dir(Ordered))

打印:

[ ......., a, b, x, z]

如何以原始顺序获取字段:x,z,b,a? 我在Django Models中看到了类似的行为。

6 个答案:

答案 0 :(得分:15)

如上所述,如果您想保持简单,只需使用例如_ordering属性,该属性可手动跟踪排序。否则,这是一个元类方法(就像Django使用的那样),它会自动创建一个排序属性。

录制原始订购

类不跟踪属性的顺序。但是,您可以跟踪字段实例的创建顺序。为此,您必须将自己的类用于字段(而不是int)。该类记录已经制作了多少个实例,每个实例都记录了它的位置。以下是如何为您的示例(存储整数)执行此操作:

class MyOrderedField(int):
  creation_counter = 0

  def __init__(self, val):
    # Set the instance's counter, to keep track of ordering
    self.creation_counter = MyOrderedField.creation_counter
    # Increment the class's counter for future instances
    MyOrderedField.creation_counter += 1

自动创建ordered_items属性

现在您的字段中有一个可用于对其进行排序的数字,您的父类需要以某种方式使用它。你可以用各种方式做到这一点,如果我没记错的话,Django使用Metaclasses来做这个,这对于一个简单的类来说有点疯狂。

class BaseWithOrderedFields(type):
  """ Metaclass, which provides an attribute "ordered_fields", being an ordered
      list of class attributes that have a "creation_counter" attribute. """

  def __new__(cls, name, bases, attrs):
    new_class = super(BaseWithOrderedFields, cls).__new__(cls, name, bases, attrs)
    # Add an attribute to access ordered, orderable fields
    new_class._ordered_items = [(name, attrs.pop(name)) for name, obj in attrs.items()
                                    if hasattr(obj, "creation_counter")]
    new_class._ordered_items.sort(key=lambda item: item[1].creation_counter)
    return new_class

使用此元类

那么,你如何使用它?首先,在定义属性时需要使用新的MyOrderedField类。这个新类将跟踪创建字段的顺序:

class Ordered(object):
  __metaclass__ = BaseWithOrderedFields

  x = MyOrderedField(0)
  z = MyOrderedField(0)
  b = MyOrderedField(0)
  a = MyOrderedField(0)

然后,您可以访问我们自动创建的属性ordered_fields中的有序字段:

>>> ordered = Ordered()
>>> ordered.ordered_fields
[('x', 0), ('z', 0), ('b', 0), ('a', 0)]

随意将其更改为有序的字典或只返回您需要的名称或任何内容。此外,您可以使用__metaclass__定义一个空类,并从那里继承。

请勿使用此功能!

正如您所看到的,这种方法有点过于复杂,可能不适合大多数任务或python开发人员。如果你是python的新手,你可能会花费更多的时间和精力来开发你的元类,而不是你手动定义订购时所花费的时间和精力。手动定义自己的订购几乎总是最好的方法。 Django会自动执行此操作,因为复杂的代码对最终开发人员是隐藏的,而Django的使用频率远高于它本身的编写/维护。因此,只有当您为其他开发人员开发框架时,元类才可能对您有用。

答案 1 :(得分:5)

当威尔发布他的时候,我已经完成了80%的答案,但我还是决定发帖,所以努力不会浪费(我们的答案基本上描述了同样的事情)。

这是Django如何做到的。我选择保留与Django相同的命名法,方法论和数据结构,因此这个答案对于试图理解字段名称如何在Django中排序的人来说也很有用。

from bisect import bisect

class Field(object):
    # A global creation counter that will contain the number of Field objects
    # created globally.
    creation_counter = 0

    def __init__(self, *args, **kwargs):
        super(Field, self).__init__(*args, **kwargs)
        # Store the creation index in the "creation_counter" of the field.
        self.creation_counter = Field.creation_counter
        # Increment the global counter.
        Field.creation_counter += 1
        # As with Django, we'll be storing the name of the model property
        # that holds this field in "name".
        self.name = None

    def __cmp__(self, other):
        # This specifies that fields should be compared based on their creation
        # counters, allowing sorted lists to be built using bisect.
        return cmp(self.creation_counter, other.creation_counter)

# A metaclass used by all Models
class ModelBase(type):
    def __new__(cls, name, bases, attrs):
        klass = super(ModelBase, cls).__new__(cls, name, bases, attrs)
        fields = []
        # Add all fields defined for the model into "fields".
        for key, value in attrs.items():
            if isinstance(value, Field):
                # Store the name of the model property.
                value.name = key
                # This ensures the list is sorted based on the creation order
                fields.insert(bisect(fields, value), value)
        # In Django, "_meta" is an "Options" object and contains both a
        # "local_fields" and a "many_to_many_fields" property. We'll use a
        # dictionary with a "fields" key to keep things simple.
        klass._meta = { 'fields': fields }
        return klass

class Model(object):
    __metaclass__ = ModelBase

现在让我们定义一些示例模型:

class Model1(Model):
    a = Field()
    b = Field()
    c = Field()
    z = Field()

class Model2(Model):
    c = Field()
    z = Field()
    b = Field()
    a = Field()

让我们测试一下:

>>>> [f.name for f in Model1()._meta['fields']]
['a', 'b', 'c', 'z']
>>>> [f.name for f in Model2()._meta['fields']]
['c', 'z', 'b', 'a']

希望这有助于澄清威尔答案中尚未明确的任何内容。

答案 2 :(得分:3)

class SchemaItem():
    def __init__(self,item):
        self.item = item
        time.sleep(0.1)
        self.order = datetime.now()

    def __str__(self):
        return "Item = %s, Order = %s"%(self.item, self.order)

class DefiningClass():
    B       = SchemaItem("B")
    C       = SchemaItem("C")
    A       = SchemaItem("A")
    PRODUCT = SchemaItem("PRODUCT")
    ASSSET  = SchemaItem("ASSET")
    TENOR   = SchemaItem("TENOR")

    def get_schema(self):
        self_class = self.__class__
        attributes = [x for x in dir(self_class) if x not in ["class","name","schema","values"]]
        schema     = [(attribute_name,getattr(self_class,attribute_name)) for attribute_name in attributes if isinstance(getattr(self_class,attribute_name),SchemaItem)]
        return dict(schema)

# Actual usage
ss = [(name,schema_item) for name,schema_item in s.get_schema().items()]
print "Before = %s" % ss
ss.sort(key=lambda a:a[1].order)
print "After =%s" % ss

答案 3 :(得分:1)

您无法跟踪添加类变量的顺序。这些属性(以及对象上的属性)在内部存储为字典,该字典针对快速查找进行了优化,不支持排序。

你可以看到这个事实:

class A(object):
    x = 0
    y = 0
    z = 0

A.__dict__.items()

#  [('__module__', '__main__'), 
#   ('__dict__', <attribute '__dict__' of 'A' objects>), 
#   ('y', 0), ('x', 0), ('z', 0), 
#   ('__weakref__', <attribute '__weakref__' of 'A' objects>), 
#   ('__doc__', None)]

如果您希望按特定顺序排列属性,则可以添加另一个包含此信息的字段:

class B(object):
    x = 0
    y = 0
    z = 0
    a = 0
    _ordering = ['x', 'y', 'z', 'a']

print B._ordering
# => ['x', 'y', 'z', 'a']

旁注:在python 2.7和3.2中,有序词典将作为标准库的一部分引入。

答案 4 :(得分:1)

Django的模型和表单元类与字段描述符一起工作以保持原始顺序。没有跳过很多的箍,就没有办法做你要求的。如果您仍然感兴趣,请参阅Django源代码。

答案 5 :(得分:1)

现在只使用Python 3.6!

class OrderedClass():
    x = 0
    z = 0
    a = 0
    b = 0

print(list(OrderedClass.__dict__))

这输出了我:

  

['__module__', 'x', 'z', 'a', 'b', '__dict__', '__weakref__', '__doc__']