2010-07-20 14 views
7

que tienen un código como:¿Cómo puedo obtener campos en un pedido original?

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

print(dir(Ordered)) 

imprime:

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

¿Cómo puedo obtener campos en un orden original: X, Z, B, A? He visto un comportamiento similar en Django Models.

+1

métodos, propiedades, etc se almacenan como objetos dentro de los diccionarios, y los diccionarios son, por definición, no ordenada. ¿Qué estás tratando de lograr (o mejor, por qué)? –

+0

Necesita Python 3 para hacer esto sin hacks: http://stackoverflow.com/a/4460565/821378 –

Respuesta

15

Como se mencionó anteriormente, si desea mantener las cosas simples, simplemente utilice un atributo por ejemplo _ordering, que realiza un seguimiento manual de los pedidos. De lo contrario, aquí hay un enfoque de metaclase (como el que utiliza Django), que crea un atributo de ordenamiento de forma automática.

Registro de la orden original

Las clases no perder de vista el orden de los atributos. Sin embargo, puede realizar un seguimiento del orden en que se crearon las instancias de campo. Para eso, tendrás que usar tu propia clase para los campos (no int). La clase realiza un seguimiento de cuántas instancias se han realizado y cada instancia toma nota de su posición. Esta es la manera que lo haría por su ejemplo (enteros almacenamiento):

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 

Creación de un atributo ordered_items automáticamente

Ahora que sus campos tienen un número que puede ser utilizado para pedirlos, sus padres la clase necesita usar eso de alguna manera. Puedes hacer esto de varias maneras, si no recuerdo mal, Django usa Metaclasses para hacer esto, lo cual es un poco salvaje para una clase simple.

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 

El uso de este metaclase

Así que, ¿cómo se utiliza esto? En primer lugar, debe usar nuestra nueva clase MyOrderedField al definir sus atributos.Esta nueva clase se mantendrá un registro del orden en que se crearon los campos:

class Ordered(object): 
    __metaclass__ = BaseWithOrderedFields 

    x = MyOrderedField(0) 
    z = MyOrderedField(0) 
    b = MyOrderedField(0) 
    a = MyOrderedField(0) 

A continuación, se puede acceder a los campos ordenados en nuestro atributo creado automáticamente ordered_fields:

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

dude en cambiar esto a un dict ordenado o simplemente devuelva los nombres o lo que sea que necesite. Además, puede definir una clase vacía con el __metaclass__ y heredar desde allí.

¡No lo use!

Como puede ver, este enfoque es un poco complicado y probablemente no adecuado para la mayoría de las tareas o desarrolladores de Python. Si eres nuevo en Python, probablemente gastarás más tiempo y esfuerzo en desarrollar tu metaclase de lo que lo hubieras hecho si hubieras definido el orden de forma manual. Definir su propio pedido de forma manual casi siempre será el mejor enfoque. Django lo hace automáticamente porque el código complicado está oculto para el desarrollador final, y Django se usa mucho más a menudo de lo que se escribe/mantiene. Entonces, solo si está desarrollando un marco para otros desarrolladores, entonces las metaclases pueden ser útiles para usted.

+0

Desarrollo un marco para los comerciantes y su solución es lo que necesito. Deseo simplificar la declaración de parámetros de entrada y salida para los indicadores, y realmente uso instancias como campos. Con "int" he simplificado demasiado un ejemplo))) ¡Gracias por la mejor respuesta! :) – DenisKolodin

+1

¡ADVERTENCIA! Esta versión de __new__ no obtiene campos de bases. ¡Por lo tanto, las subclases pierden los campos ordenados por los padres! Añádalos si es necesario. – DenisKolodin

1

No puede seguir el orden de la adición de las variables de clase. Estos atributos (así como los atributos de los objetos) se almacenan internamente como un diccionario, que está optimizado para búsquedas rápidas y no admite el pedido.

Se puede ver este hecho:

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

Si desea que sus atributos en un orden particular, usted podría agregar otro campo, que contiene la siguiente información:

class B(object): 
    x = 0 
    y = 0 
    z = 0 
    a = 0 
    _ordering = ['x', 'y', 'z', 'a'] 

print B._ordering 
# => ['x', 'y', 'z', 'a'] 

Nota al margen: En Python 2.7 y 3.2 diccionarios ordenados serán introducidos como parte de la biblioteca estándar.

+0

No está mal, pero no se ajusta al principio DRY. – DenisKolodin

1

Las metaclases de modelo y forma de Django funcionan junto con los descriptores de campo para mantener el orden original. No hay forma de hacer lo que pide sin saltar por un lote de aros. Vea el código fuente de Django si todavía está interesado.

5

Estaba 80% listo con esta respuesta cuando Will publicó la suya, pero decidí publicar de todos modos para que el esfuerzo no se desperdicie (nuestras respuestas básicamente describen lo mismo).

Así es como lo hace Django. He elegido mantener la misma nomenclatura, metodología y estructuras de datos que Django, por lo que esta respuesta también puede ser útil para las personas que intentan comprender cómo se ordenan los nombres de los campos en 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 

Ahora vamos a definir algunos modelos de ejemplo:

class Model1(Model): 
    a = Field() 
    b = Field() 
    c = Field() 
    z = Field() 

class Model2(Model): 
    c = Field() 
    z = Field() 
    b = Field() 
    a = Field() 

Y vamos a probar ellos:

>>>> [f.name for f in Model1()._meta['fields']] 
['a', 'b', 'c', 'z'] 
>>>> [f.name for f in Model2()._meta['fields']] 
['c', 'z', 'b', 'a'] 

Espero que esto ayude a aclarar cualquier cosa que ya no estaba claro en la respuesta de Will.

+0

Su solución debe actualizarse a python 3.x, donde 'cmp()' parece desaparecer. Defina '__lt __()' en lugar de '__cmp __()'. Aparte de eso, ¡gracias! –

+0

Y la definición de metaclases debería hacerse así: 'class Model (metaclass = ModelBase)'. –

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 
1

¡Por ahora solo use Python 3.6!

class OrderedClass(): 
    x = 0 
    z = 0 
    a = 0 
    b = 0 

print(list(OrderedClass.__dict__)) 

Esto me da salida:

['__module__', 'x', 'z', 'a', 'b', '__dict__', '__weakref__', '__doc__']

+1

Eso es porque PEP 520 – DenisKolodin

Cuestiones relacionadas