2010-11-18 7 views
6

Voglio definire classi di peso leggero che dovrebbero rappresentare strutture di dati. Come nel caso di molte strutture di dati, l'ordine dei dati è importante. Quindi, se vado avanti e definire questo:Come ottengo l'ordine di definizione degli attributi di classe in Python?

class User(DataStructure): 
    username = StringValue() 
    password = StringValue() 
    age = IntegerValue() 

Io sto dicendo che questa è una struttura dati in cui una stringa con il nome utente viene prima, seguito da una stringa con la password, e, infine, l'età dell'utente come un numero intero

Se hai familiarità con Python, saprai che la classe precedente, User, è un oggetto che eredita da type. Sarà, come la maggior parte degli altri oggetti in Python, avere uno __dict__. E qui, nel mio problema. Questo __dict__ è una mappa hash, quindi l'ordine degli attributi di classe nello __dict__ non è in alcun modo correlato al loro ordine di definizione.

Esiste un modo per determinare l'ordine di definizione effettivo? Mi sto chiedendo qui prima di andare con uno dei metodi meno sane di mente mi viene in mente ...

Oh, e Giusto per essere chiari, quello che voglio è un modo per ottenere questo fuori della definizione di cui sopra: ['username', 'password', 'age']

+1

Per avere un riferimento, la mia soluzione "meno sana di mente": Avere un contatore che sale ogni volta che una classe '* Value' viene creata un'istanza e impostare il nuovo valore a tale istanza , quindi ordina l'elenco di attributi con questo valore. Questo sarebbe fatto usando una meta-classe. – Blixt

+0

L'uso di una metaclass sulla falsariga di ciò che hai descritto qui è l'unica cosa che mi è venuta in mente. –

+0

beh, il contatore è il modo in cui è stato fatto dal team di Django, quindi sarei abbastanza sicuro che questo è il modo migliore per farlo. –

risposta

2

Questo è qualcosa che non è supportato affatto in Python. Django ha utilizzato metaclassi per affrontarlo. Vedere questa domanda: How does Django Know the Order to Render Form Fields?

(Riassunto:. Guarda django.forms.forms.DeclarativeFieldsMetaclass, django.forms.forms.get_declared_fields e come creation_counter viene utilizzato in django.forms.fields.Field)

+0

Sembra che Django abbia fatto ciò che avevo intenzione di fare. Guarderò il loro codice e indagherò anche sulla risposta di Tobu, a prima vista sembra più corretto. – Blixt

+0

Le risposte di Tobu e Don sarebbero grandiose da implementare, ma non sono supportate in nessuna delle versioni di Python 2.x. Quindi per ora, la soluzione Django è la scelta migliore per me. Grazie! – Blixt

1

Si potrebbe utilizzare __slots__ per ordinare in modo esplicito gli attributi. Se ogni classe che si eredita da definisce, c'è un guadagno di prestazioni bonus sulla memoria e l'accesso agli attributi.

Modifica: il modo più affidabile sarebbe definire __prepare__ in una metaclasse. I documenti di riferimento lo hanno, complete with an example of storing attributes in an OrderedDictionary.

Non è possibile avere implicitamente l'ordine in Python 2 senza la tecnica di classi di campo di Django, perché Python non tiene traccia di tali informazioni; viene perso quando viene chiamato un metaclasse.

+2

Si noti che '__prepare__' è disponibile solo in python 3. – aaronasterling

+0

Scusate, ho dimenticato di dire che voglio che funzioni con Python 2.7. __prepare__ sembra essere molto nuovo. Inoltre, conosci un'implementazione che può assegnare __slots__ in modo dinamico? Ho provato un po 'e non riuscivo a farlo funzionare in 2.7. – Blixt

+0

No, stavo pensando "l'esplicito è meglio che implicito". Metti '__slots__' nell'origine e impone che' __dict__' non sia definito o ignori gli attributi non slot. – Tobu

2

Un modo che sarebbe stato più generale di approccio di Django, __slots__e disponibile in Python 2 sarebbe questo metaclasse:

class OrderedTypeMeta(type): 
    def __new__(mcls, clsname, bases, clsdict): 
     attrs = clsdict.get('_attrs_', []) 
     attrnames = [] 
     for name, value in attrs: 
      clsdict[name] = value 
      attrnames.append(name) 
     clsdict['_attrs_'] = attrnames 
     return super(OrderedTypeMeta, mcls).__new__(mcls, clsname, bases, clsdict) 


class User(DataStructure): 
    __metaclass__ = OrderedTypeMeta 
    _attrs_ = (('name', StringValue()), 
       ('password', StringValue()), 
       ('age', IntegerValue())) 

io dico che è più generale di modo di Django, perché non è necessario il attributi per essere istanze di una particolare classe, qualsiasi valore farà. È anche più generale di __slots__ perché sarà ancora possibile assegnare attributi alle istanze delle classi (anche se potrebbe non essere necessario: in tal caso, preferirei __slots__). In python3, preferirei __prepare__.

Lo svantaggio principale di questo, a parte il fatto che è un po 'brutto, è che non funzionerà con l'ereditarietà. Non sarebbe troppo difficile ottenere il __attrs__ dalle classi base ed estenderlo invece di impostarlo su una lista vuota.

+1

È una soluzione logicamente elegante, ma l'unica ragione per cui sto cercando un modo per ottenere l'ordine di definizione è che non avrei reso la sintassi degli struct defs più complessa. Quindi probabilmente andrò con la soluzione Django, dato che in realtà ho una particolare classe base per le mie strutture. Grazie comunque! :) – Blixt

5

Python 2.7 e 3.x definiscono uno OrderedDict nel modulo di raccolta. Credo che utilizzi un elenco collegato per mantenere l'ordine di inserimento dei suoi articoli. Aggiunge metodi iterabili ai metodi di mappatura mutabili standard.

Si potrebbe definire un metaclasse che utilizza un OrderedDict piuttosto che uno standard non ordinata dict come spazio dei nomi __dict__ per le classi strutture dati. Se dai al tuo metaclasse un metodo speciale __prepare__() puoi farlo. Non ho provato questo, ma in base ai documenti e 'possibile:

da Python 3.1 Lingua Rif sezione 3.3.3 Data Model - Personalizzare la creazione della classe:

If the metaclass has a __prepare__() attribute (usually implemented as a class 
or static method), it is called before the class body is evaluated with the 
name of the class and a tuple of its bases for arguments. It should return an 
object that supports the mapping interface that will be used to store the 
namespace of the class. The default is a plain dictionary. This could be used, 
for example, to keep track of the order that class attributes are declared in 
by returning an ordered dictionary. 

Purtroppo la sezione equivalente 3.4.3 nel Python 2.7 Language Ref non fa menzione di essere in grado di sostituire uno spazio dei nomi di classe e di non menzionare il metodo __prepare__(). Quindi questo può essere possibile solo in Python versione 3.

0

Quanto segue, consentitemi di acquisire l'ordine degli attributi che sono metodi o di tipo struct. Non l'ho provato con i tipi built-in. È quindi possibile costruire un girello intorno bambini

 
class ordered_type(type): 
    __ordernate__ = 0 
    def __new__(meta, name, baseclasses, dct): 
     new = super(Type, meta).__new__(meta, name, baseclasses, dct) 
     new.__ordernate__ = Type.__ordernate__ 
     Type.__ordernate__ += 1   
     def key(x): 
      from inspect import ismethod 
      if ismethod(x): 
       return x.im_func.__code__.co_firstlineno 
      elif isinstance(x, type): 
       return x.__ordernate__ + 1000000 
     new.__children__ = sorted(
      [getattr(new, x) for x in dir(new) if not x.startswith('__')], 
      key=key) 

class struct(object): 
    __metaclass__ = ordered_type 

#use case 
class A(struct): 
    '''Description of class A 
    ''' 
    def name(self): 
     '''The name of instance of A 
     ''' 
    class B(struct): 
     '''Description of class B 
     ''' 
     def name(self): 
      '''The name of instance of B 
      ''' 
Problemi correlati