Как я могу получить поля в оригинальном порядке? - PullRequest
7 голосов
/ 20 июля 2010

У меня есть такой код:

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

print(dir(Ordered))

он печатает:

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

Как получить поля в исходном порядке: x, z, b, a?Я видел подобное поведение в моделях Django.

Ответы [ 6 ]

15 голосов
/ 20 июля 2010

Как уже упоминалось выше, если вы хотите сохранить простоту, просто используйте, например, атрибут _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)]

Не стесняйтесь сменить это на заказанный диктат или просто вернуть имена или все, что вам нужно.Кроме того, вы можете определить пустой класс с помощью __metaclass__ и наследовать оттуда.

Не используйте это!

Как видите, этот подходнемного сложнее и, вероятно, не подходит для большинства задач или разработчиков Python.Если вы новичок в Python, вы, вероятно, потратите больше времени и усилий на разработку своего метакласса, чем если бы вы просто определили порядок вручную.Определение собственного заказа вручную почти всегда будет лучшим подходом.Django делает это автоматически, потому что сложный код скрыт от конечного разработчика, и Django используется гораздо чаще, чем сам написан / поддерживается.Поэтому, только если вы разрабатываете фреймворк для других разработчиков, метаклассы могут быть вам полезны.

5 голосов
/ 20 июля 2010

Я был на 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 голосов
/ 24 октября 2012
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
1 голос
/ 26 октября 2016

Пока просто используйте 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__']

1 голос
/ 20 июля 2010

Метаклассы модели и формы Джанго работают вместе с дескрипторами полей, чтобы поддерживать исходный порядок.Невозможно выполнить то, что вы просите, не перепрыгнув через лот обручей.Посмотрите исходный код Django, если вы все еще заинтересованы.

1 голос
/ 20 июля 2010

Вы не можете отслеживать порядок добавления переменных класса.Эти атрибуты (а также атрибуты объектов) хранятся внутри как словарь, который оптимизирован для быстрого поиска и не поддерживает упорядочение.

Вы можете увидеть этот факт:

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

Sidenote: В Python 2.7 и 3.2 упорядоченные словари будут представлены как часть стандартной библиотеки.

...