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的使用频率远高于它本身的编写/维护.因此,只有当您为其他开发人员开发框架时,元类才可能对您有用.
当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的答案中尚未明确的任何内容.