9 Stimmen

Wie kann ich Felder in einer ursprünglichen Reihenfolge erhalten?

Ich habe einen Code wie:

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

print(dir(Ordered))

druckt es:

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

Wie kann ich Felder in der ursprünglichen Reihenfolge erhalten: x, z, b, a? Ich habe ein ähnliches Verhalten in Django Models gesehen.

1 Stimmen

Methoden, Eigenschaften usw. werden als Wörterbücher in Objekten gespeichert, und Wörterbücher sind per Definition ungeordnet. Was wollen Sie erreichen (oder besser, warum)?

0 Stimmen

Sie benötigen Python 3, um dies ohne Hacks zu tun: stackoverflow.com/a/4460565/821378

15voto

Will Hardy Punkte 14049

Wie bereits erwähnt, können Sie, wenn Sie es einfach halten wollen, einfach ein eg verwenden. _ordering Attribut, das die Reihenfolge manuell festlegt. Andernfalls ist hier ein Metaklassen-Ansatz (wie der, den Django verwendet), der ein Ordnungsattribut automatisch erstellt.

Aufzeichnung der ursprünglichen Bestellung

Die Klassen behalten die Reihenfolge der Attribute nicht im Auge. Sie können jedoch verfolgen, in welcher Reihenfolge die Feldinstanzen erstellt wurden. Dazu müssen Sie eine eigene Klasse für Felder (nicht int) verwenden. Die Klasse merkt sich, wie viele Instanzen bereits erstellt wurden, und jede Instanz merkt sich ihre Position. So würden Sie es für Ihr Beispiel (Speicherung von Integern) machen:

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

Erstellen einer ordered_items Attribut automatisch

Nun, da Ihre Felder eine Nummer haben, die verwendet werden kann, um sie zu ordnen, muss Ihre übergeordnete Klasse das irgendwie verwenden. Sie können dies auf verschiedene Arten tun. Wenn ich mich richtig erinnere, verwendet Django dazu Metaklassen, was für eine einfache Klasse ein bisschen wild ist.

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

Verwendung dieser Metaklasse

Und wie benutzt man das? Zunächst müssen Sie unsere neue MyOrderedField Klasse, wenn Sie Ihre Attribute definieren. Diese neue Klasse merkt sich die Reihenfolge, in der die Felder erstellt wurden:

class Ordered(object):
  __metaclass__ = BaseWithOrderedFields

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

Dann können Sie auf die bestellten Felder in unserem automatisch erstellten Attribut ordered_fields :

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

Es steht Ihnen frei, dies in ein geordnetes Diktat zu ändern oder nur die Namen zurückzugeben oder was immer Sie benötigen. Zusätzlich können Sie eine leere Klasse mit der Option __metaclass__ und erben von dort.

Benutzen Sie das nicht!

Wie Sie sehen können, ist dieser Ansatz ein wenig zu kompliziert und wahrscheinlich nicht für die meisten Aufgaben oder Python-Entwickler geeignet. Wenn Sie ein Python-Neuling sind, werden Sie wahrscheinlich mehr Zeit und Mühe aufwenden, um Ihre Metaklasse zu entwickeln, als wenn Sie die Reihenfolge einfach manuell definiert hätten. Es ist fast immer die beste Lösung, die Reihenfolge manuell zu definieren. Django macht das automatisch, weil der komplizierte Code vor dem Endentwickler verborgen ist und Django viel öfter benutzt wird, als es selbst geschrieben/gewartet wird. Nur wenn Sie also ein Framework für andere Entwickler entwickeln, können Metaklassen für Sie nützlich sein.

0 Stimmen

Ich entwickle Frameworks für Händler und Ihre Lösung ist das, was ich brauche. Ich möchte die Deklaration von Eingabe- und Ausgabeparametern für Indikatoren vereinfachen, und ich verwende wirklich Instanzen als Felder. Mit "int" habe ich ein zu vereinfachtes Beispiel))) Ich danke Ihnen für die beste Antwort! :)

1 Stimmen

WARNUNG! Diese Version von neu erhalten keine Felder von Basen. Daher verlieren Unterklassen die bestellten Felder der Eltern! Fügen Sie sie bei Bedarf hinzu.

5voto

Aram Dulyan Punkte 2386

Ich war zu 80 % mit dieser Antwort fertig, als Will seine Antwort veröffentlichte, aber ich beschloss, sie trotzdem zu posten, damit die Mühe nicht umsonst war (unsere Antworten beschreiben im Grunde das Gleiche).

So macht es Django. Ich habe mich entschieden, die gleiche Nomenklatur, Methodik und Datenstrukturen wie Django beizubehalten, damit diese Antwort auch für Leute nützlich ist, die versuchen zu verstehen, wie Feldnamen in Django sortiert werden.

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

Lassen Sie uns nun einige Beispielmodelle definieren:

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

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

Und wir sollten sie testen:

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

Ich hoffe, dass dies zur Klärung von Fragen beiträgt, die in Wills Antwort nicht bereits deutlich wurden.

0 Stimmen

Ihre Lösung sollte auf python 3.x aktualisiert werden, wobei cmp() scheint verschwunden zu sein. Definieren Sie __lt__() statt __cmp__() . Ansonsten: Danke!

0 Stimmen

Und die Definition von Metaklassen sollte wie folgt erfolgen: class Model(metaclass=ModelBase) .

3voto

Bhushan Punkte 2100
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

1voto

miku Punkte 170688

Sie können die Reihenfolge des Hinzufügens der Klassenvariablen nicht nachvollziehen. Diese Attribute (wie auch die Attribute von Objekten) werden intern in einem Wörterbuch gespeichert, das für schnelles Nachschlagen optimiert ist und keine Reihenfolge unterstützt.

Sie können diese Tatsache sehen:

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

Wenn Sie Ihre Attribute in einer bestimmten Reihenfolge haben möchten, können Sie einfach ein weiteres Feld hinzufügen, das diese Informationen enthält:

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

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

Nebenbemerkung: In Python 2.7 und 3.2 werden Ordered Dictionaries als Teil der Standardbibliothek eingeführt.

0 Stimmen

Es ist nicht schlecht, entspricht aber nicht dem DRY-Prinzip.

1voto

Die Modell- und Formular-Metaklassen von Django arbeiten mit den Felddeskriptoren zusammen, um die ursprüngliche Reihenfolge beizubehalten. Es gibt keine Möglichkeit, das zu tun, was Sie verlangen, ohne durch ein Los von Reifen. Sehen Sie sich den Django-Quellcode an, wenn Sie noch interessiert sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X