Как я могу получить поля в первоначальном заказе?

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

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

print(dir(Ordered))

это печатает:

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

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

7
задан miku 20 July 2010 в 08:21
поделиться

4 ответа

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

Автоматическое создание атрибута orders_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)

Затем вы можете получить доступ к упорядоченным полям в нашем автоматически созданном атрибуте Order_fields :

>>> ordered = Ordered()
>>> ordered.ordered_fields
[('x', 0), ('z', 0), ('b', 0), ('a', 0)]

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

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

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

15
ответ дан 6 December 2019 в 06:48
поделиться

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

1
ответ дан 6 December 2019 в 06:48
поделиться

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

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

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 упорядоченные словари будут представлены как часть стандартной библиотеки.

1
ответ дан 6 December 2019 в 06:48
поделиться

Я был на 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']

Надеюсь, это поможет прояснить все, что не было понятно в ответе Уилла.

5
ответ дан 6 December 2019 в 06:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: