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

Den*_*din 7 python django introspection

我有一个代码:

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

print(dir(Ordered))
Run Code Online (Sandbox Code Playgroud)

它打印:

[ ......., a, b, x, z]
Run Code Online (Sandbox Code Playgroud)

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

Wil*_*rdy 15

如上所述,如果您想保持简单,只需使用eg _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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

使用这个元类

那么,你怎么用呢?首先,MyOrderedField在定义属性时需要使用我们的新类.这个新类将跟踪创建字段的顺序:

class Ordered(object):
  __metaclass__ = BaseWithOrderedFields

  x = MyOrderedField(0)
  z = MyOrderedField(0)
  b = MyOrderedField(0)
  a = MyOrderedField(0)
Run Code Online (Sandbox Code Playgroud)

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

>>> ordered = Ordered()
>>> ordered.ordered_fields
[('x', 0), ('z', 0), ('b', 0), ('a', 0)]
Run Code Online (Sandbox Code Playgroud)

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

不要用这个!

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


Ara*_*yan 5

当Will发布他的时候,我已经完成了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
Run Code Online (Sandbox Code Playgroud)

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

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

class Model2(Model):
    c = Field()
    z = Field()
    b = Field()
    a = Field()
Run Code Online (Sandbox Code Playgroud)

让我们测试一下:

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

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