代码之家  ›  专栏  ›  技术社区  ›  DenisKolodin

如何按原始顺序获取字段?

  •  7
  • DenisKolodin  · 技术社区  · 15 年前

    我有这样一个代码:

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

    它打印:

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

    如何按原始顺序获取字段:x、z、b、a? 我在姜戈模特身上也看到过类似的行为。

    6 回复  |  直到 9 年前
        1
  •  15
  •   Will Hardy    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
    

    创建一个 ordered_items 自动设置属性

    既然您的字段有一个可以用来排序的数字,那么您的父类就需要以某种方式使用它。如果我没记错的话,你可以用多种方法来实现,Django使用元类来实现这一点,这对于一个简单的类来说有点疯狂。

    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)]
    

    请随意将此更改为有序的dict,或者只返回名称或您需要的任何内容。此外,还可以使用 __metaclass__ 从那里继承。

    不要用这个!

    如您所见,这种方法有点过于复杂,可能不适合大多数任务或Python开发人员。如果您对Python比较陌生,那么开发元类所花费的时间和精力可能会比手工定义排序所花费的时间和精力要多。手工定义自己的订单几乎总是最好的方法。Django会自动执行,因为复杂的代码对最终开发人员来说是隐藏的,而且Django的使用频率远高于它本身的编写/维护频率。因此,只有当您为其他开发人员开发框架时,元类才可能对您有用。

        2
  •  5
  •   Aram Dulyan    15 年前

    我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']
    

    希望这有助于澄清在威尔的回答中还不清楚的事情。

        3
  •  3
  •   Bhushan    13 年前
    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
    
        4
  •  1
  •   Ignacio Vazquez-Abrams    15 年前

    Django的模型和表单元类与字段描述符一起维护原始顺序。如果不跳过一个 许多 箍。如果您仍然感兴趣,请参阅django源代码。

        5
  •  1
  •   miku    15 年前

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

    你可以看到这个事实:

    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']
    

    旁注:在Python2.7和3.2中,有序字典将作为标准库的一部分引入。

        6
  •  1
  •   Artem Selivanov    9 年前

    现在只需使用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__']