我有一个代码:
class Ordered(object):
x = 0
z = 0
b = 0
a = 0
print(dir(Ordered))
打印:
[ ......., a, b, x, z]
如何以原始顺序获取字段:x,z,b,a? 我在Django Models中看到了类似的行为。
答案 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)
这是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__']