2010-11-18 10 views
6

Chcę zdefiniować klasy lekkości, które mają reprezentować struktury danych. Podobnie jak w przypadku wielu struktur danych, kolejność danych jest ważna. Więc jeśli mogę śmiało określić następująco:Jak uzyskać kolejność definicji atrybutów klas w Pythonie?

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

Ja sugerując, że jest to struktura danych, gdzie łańcuch z nazwą użytkownika jest na pierwszym miejscu, a następnie ciąg z hasłem, a wreszcie wiek użytkownika jako Liczba całkowita.

Jeśli znasz Python, będziesz wiedzieć, że powyższa klasa, User, jest obiektem dziedziczącym po type. Będzie, tak jak większość innych obiektów w Pythonie, mieć __dict__. I tu leży mój problem. Ta __dict__ jest mapą skrótu, więc kolejność atrybutów klas w __dict__ nie jest w żaden sposób powiązana z ich kolejnością definicji.

Czy mogę w jakiś sposób ustalić rzeczywistą kolejność definicji? Pytam tutaj zanim pójdę z jednym z mniej rozsądnych metod mogę myśleć ...

Oh i po prostu być jasne, co chcę jest sposobem, aby to z powyższej definicji: ['username', 'password', 'age']

+1

odsyłającym moim rozwiązanie „mniej sane”: masz licznik, który idzie w górę za każdym razem, gdy klasa '* value' jest tworzony i ustawić nową wartość do tej instancji , następnie uporządkuj listę atrybutów według tej wartości. Można to zrobić za pomocą meta-klasy. – Blixt

+0

Używanie metaklasy zgodnie z tym, co tu opisałeś, jest jedyną rzeczą, która podskoczyła mi do głowy. –

+0

Cóż, licznik jest sposobem, w jaki zespół Django to zrobił, więc byłbym dość pewny, że to najlepszy sposób na zrobienie tego. –

Odpowiedz

2

To jest coś, co nie jest w ogóle obsługiwane w Pythonie. Django użył metaclasses, aby sobie z tym poradzić. Zobacz na to pytanie: How does Django Know the Order to Render Form Fields?

(Podsumowanie:. Spojrzeć na django.forms.forms.DeclarativeFieldsMetaclass, django.forms.forms.get_declared_fields i jak creation_counter jest używany w django.forms.fields.Field)

+0

Wygląda na to, że Django zrobił to, co planowałem. Przyjrzę się ich kodowi, a także zbadam odpowiedź Tobu, na pierwszy rzut oka wydaje się, że jest poprawniejsza. – Blixt

+0

Odpowiedzi Tobu i Dona byłyby świetne do wdrożenia, ale nie są obsługiwane w żadnej wersji Python 2.x. Na razie rozwiązanie Django jest dla mnie najlepszym wyborem. Dzięki! – Blixt

1

Można użyć __slots__ jawnie zamówić atrybuty. Jeśli każda klasa, którą odziedziczysz po zdefiniowaniu, ma dodatkowy przyrost wydajności dla pamięci i dostępu do atrybutów.

Edycja: Najbardziej niezawodnym sposobem byłoby zdefiniowanie __prepare__ w metaklasy. Dokumenty referencyjne mają to, complete with an example of storing attributes in an OrderedDictionary.

Nie można mieć zlecenia niejawnie w Pythonie 2 bez techniki klas Django, ponieważ Python po prostu nie śledzi tej informacji; zostaje utracony do czasu wywołania metaclass.

+2

Należy zauważyć, że '__prepare__' jest dostępny tylko w pythonie 3. – aaronasterling

+0

Przepraszam, zapomniałem powiedzieć, że chcę, aby to działało z Pythonem 2.7. __prepare__ wydaje się być bardzo nowy. Czy znasz także implementację, która może dynamicznie przypisywać __slots__? Próbowałem trochę i nie mogłem go uruchomić w 2.7. – Blixt

+0

Nie, myślałem "wyraźne jest lepsze niż ukryte". Umieść '__slots__' w źródle i wymuś, aby' __dict__' nie był zdefiniowany lub zignoruj ​​atrybuty inne niż w slocie. – Tobu

2

Jednym ze sposobów, który byłby bardziej ogólny niż podejście Django, __slots__i dostępny w Pythonie 2 byłoby to metaklasa:

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

mogę powiedzieć, że jest bardziej ogólne niż sposób Django ponieważ nie potrzebne atrybuty są instancjami danej klasy, każda wartość będzie działać. Jest również bardziej ogólny niż __slots__, ponieważ nadal będziesz mógł przypisywać atrybuty do instancji klas (choć może nie być to konieczne: w takim przypadku wolałbym __slots__). W python3 wolałbym __prepare__.

Główną wadą tego, oprócz tego, że jest to trochę brzydkie, jest to, że nie będzie działać z dziedziczeniem. Uzyskanie klasy __attrs__ poza klasami podstawowymi nie byłoby zbyt trudne, a zamiast tego ustawiono ją na pustą listę.

+1

Jest to logicznie eleganckie rozwiązanie, ale jedynym powodem, dla którego szukam sposobu na uzyskanie kolejności definicji, jest to, że nie zrobiłbym jeszcze bardziej złożonej składni definicji struct. Więc prawdopodobnie pójdę z rozwiązaniem Django, ponieważ faktycznie mam konkretną klasę bazową dla moich struktur. W każdym razie dzięki! :) – Blixt

5

Python 2.7 i 3.x definiują OrderedDict w module kolekcji. Wydaje mi się, że korzysta z połączonej listy, aby zachować porządek reklamowy swoich produktów. Dodaje metody iterowalne do standardowych metod mapowania zmiennego.

Można zdefiniować metaclass, który używa OrderedDict zamiast standardowego nieuporządkowanego dict jako przestrzeni nazw __dict__ dla swoich klas struktur danych. Jeśli podasz metaclassowi specjalną metodę __prepare__(), możesz to zrobić. Nie próbowałem tego, ale według docs to możliwe:

Od Pythona 3.1 Język Ref sekcji 3.3.3 Data Model - Personalizacja utworzenie klasy:

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. 

Niestety odpowiednik sekcja 3.4.3 w Python 2.7 Ref nie wspomina o możliwości zastąpienia dyktatury przestrzeni klas i braku wzmianki o metodzie __prepare__(). Tak więc może to być możliwe tylko w Pythonie w wersji 3.

0

Poniższe pozwolą mi uchwycić kolejność atrybutów, które są metodami lub typu struct. Nie próbowałem go z wbudowanymi typami. Następnie można zbudować Walker wokół dzieci

 
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 
      ''' 
Powiązane problemy