2010-12-16 7 views
34

Ich schreibe eine Metaklasse, die Klassenattribute liest und speichert sie in einer Liste, aber ich möchte die Liste (cls.columns), um die Erklärung zu respektieren (dh: mycol2, mycol3, zut, cool, menfin, a in meinem Beispiel):Wie können Klassenattribute in der angegebenen Reihenfolge gelesen werden?

import inspect 
import pprint 

class Column(object): 
    pass 

class ListingMeta(type): 
    def __new__(meta, classname, bases, classDict): 
     cls = type.__new__(meta, classname, bases, classDict) 
     cls.columns = inspect.getmembers(cls, lambda o: isinstance(o, Column)) 
     cls.nb_columns = len(cls.columns) 
     return cls 

class Listing(object): 
    __metaclass__ = ListingMeta 
    mycol2 = Column() 
    mycol3 = Column() 
    zut = Column() 
    cool = Column() 
    menfin = Column() 
    a = Column() 

pprint.pprint(Listing.columns) 

Ergebnis:

[('a', <__main__.Column object at 0xb7449d2c>), 
('cool', <__main__.Column object at 0xb7449aac>), 
('menfin', <__main__.Column object at 0xb7449a8c>), 
('mycol2', <__main__.Column object at 0xb73a3b4c>), 
('mycol3', <__main__.Column object at 0xb744914c>), 
('zut', <__main__.Column object at 0xb74490cc>)] 

dieser Hinsicht nicht die Deklaration der Reihenfolge der Column() Attribute für Listing Klasse. Wenn ich classDict direkt benutze, hilft es auch nicht.

Wie kann ich fortfahren? Hier

+3

ich Sie sie ohne irgendeine Art von Source-Level-Analyse, um bekommen nicht denken. In jedem Fall soll die Reihenfolge weitgehend irrelevant sein. Die 'dict' Hashes per Schlüssel, weshalb Sie es nicht in der Reihenfolge sehen – Robert

+0

Überhaupt eine sehr konstruktive Frage. danke – pylover

+0

kannst du auf tosca widget 2 schauen, um herauszufinden, wie das geht – pylover

Antwort

12

ist die Abhilfe, die ich entwickelt Juste:

import inspect 

class Column(object): 
    creation_counter = 0 
    def __init__(self): 
     self.creation_order = Column.creation_counter 
     Column.creation_counter+=1 

class ListingMeta(type): 
    def __new__(meta, classname, bases, classDict): 
     cls = type.__new__(meta, classname, bases, classDict) 
     cls.columns = sorted(inspect.getmembers(cls,lambda o:isinstance(o,Column)),key=lambda i:i[1].creation_order) 
     cls.nb_columns = len(cls.columns) 
     return cls 

class Listing(object): 
    __metaclass__ = ListingMeta 
    mycol2 = Column() 
    mycol3 = Column() 
    zut = Column() 
    cool = Column() 
    menfin = Column() 
    a = Column() 


for colname,col in Listing.columns: 
    print colname,'=>',col.creation_order 
+0

Zuerst dachte ich "Du musst den creation_counter nach jeder Klasse zurücksetzen", und dann habe ich gemerkt, dass du gar nicht tust, vorausgesetzt, dir ist nur egal die interne Reihenfolge. Es funktioniert tatsächlich. :) –

+0

Wie funktioniert es in parallelen Threads? Ich denke, dass dieser Code nicht threadsicher ist. – pylover

-1

Ich denke, sollten Sie in der Lage sein, eine Klasse zu machen, wo man seine __dict__ mit einem ordered-dict

+1

Schöne Idee. hast du es getestet? – pylover

+0

Ich versuchte es, aber es stellte sich heraus, dass es nicht in "definierter Reihenfolge" war –

6

ersetzen Wenn Sie Python verwenden 2.x dann Sie brauchen einen Hack, wie ihn Lennart vorschlägt. Wenn Sie Python 3.x verwenden, dann lesen Sie PEP 3115 als enthält ein Beispiel, das was Sie wollen. Ändern Sie das Beispiel, um nur Ihre Column() - Instanzen zu sehen:

# The custom dictionary 
class member_table(dict): 
    def __init__(self): 
     self.member_names = [] 

    def __setitem__(self, key, value): 
     # if the key is not already defined, add to the 
     # list of keys. 
     if key not in self: 
      self.member_names.append(key) 

     # Call superclass 
     dict.__setitem__(self, key, value) 

# The metaclass 
class OrderedClass(type): 

    # The prepare function 
    @classmethod 
    def __prepare__(metacls, name, bases): # No keywords in this case 
     return member_table() 

    # The metaclass invocation 
    def __new__(cls, name, bases, classdict): 
     # Note that we replace the classdict with a regular 
     # dict before passing it to the superclass, so that we 
     # don't continue to record member names after the class 
     # has been created. 
     result = type.__new__(cls, name, bases, dict(classdict)) 
     result.member_names = classdict.member_names 
     return result 

class MyClass(metaclass=OrderedClass): 
    # method1 goes in array element 0 
    def method1(self): 
     pass 

    # method2 goes in array element 1 
    def method2(self): 
     pass 
31

In der aktuellen Version von Python wird die Klassenreihenfolge beibehalten. Details finden Sie unter PEP520. In älteren Versionen der Sprache (3.5 und darunter, aber nicht 2.x) können Sie eine Metaklasse bereitstellen, die für den Klassennamespace eine OrderedDict verwendet.

import collections 

class OrderedClassMembers(type): 
    @classmethod 
    def __prepare__(self, name, bases): 
     return collections.OrderedDict() 

    def __new__(self, name, bases, classdict): 
     classdict['__ordered__'] = [key for key in classdict.keys() 
       if key not in ('__module__', '__qualname__')] 
     return type.__new__(self, name, bases, classdict) 

class Something(metaclass=OrderedClassMembers): 
    A_CONSTANT = 1 

    def first(self): 
     ... 

    def second(self): 
     ... 

print(Something.__ordered__) 
# ['A_CONSTANT', 'first', 'second'] 

Dieser Ansatz hilft Ihnen nicht mit bestehenden Klassen, aber wo es Selbstbeobachtung verwenden müssen.

5

Basierend auf der Lösung von @Duncan, aber einfacher (für python3 nur). Wir verwenden diese Tatsache, dass __prepare__ Methode eine OrderDict anstelle von einfachen dict zurückgibt - so alle Attribute gesammelt vor __new__ Aufruf wird bestellt.

from collections import OrderedDict 

class OrderedClass(type): 
    @classmethod 
    def __prepare__(mcs, name, bases): 
     return OrderedDict() 

    def __new__(cls, name, bases, classdict): 
     result = type.__new__(cls, name, bases, dict(classdict)) 
     result.__fields__ = list(classdict.keys()) 
     return result 

class Column: 
    pass 

class MyClass(metaclass=OrderedClass): 
    mycol2 = Column() 
    mycol3 = Column() 
    zut = Column() 
    cool = Column() 
    menfin = Column() 
    a = Column() 

Jetzt können Sie Attribut __fields__ für den Zugriff auf Attribute in gewünschter Reihenfolge verwenden:

m = MyClass() 
print(m.__fields__) 
['__module__', '__qualname__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a'] 

Beachten Sie, dass wird es attrs seine '__module__', '__qualname__' von type Klasse geboren. Um loszuwerden von ihnen können Sie Namen filtern, die in folgender Weise (ändern OrderedClass.__new__):

def __new__(cls, name, bases, classdict): 
    result = type.__new__(cls, name, bases, dict(classdict)) 
    exclude = set(dir(type)) 
    result.__fields__ = list(f for f in classdict.keys() if f not in exclude) 
    return result  

es gibt nur attrs von MyClass:

['mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a'] 

bitte vergessen Sie nicht Ihre Antwort nur praktikabel in python3.x, denn es gibt keine Vorbereitung Definition in Python2.7

9

Für Python 3.6 ist dies das Standardverhalten geworden. Siehe PEP520: https://www.python.org/dev/peps/pep-0520/

class OrderPreserved: 
    a = 1 
    b = 2 
    def meth(self): pass 

print(list(OrderPreserved.__dict__.keys())) 
# ['__module__', 'a', 'b', 'meth', '__dict__', '__weakref__', '__doc__'] 
2

eine Antwort, die Methoden ausschließt:

from collections import OrderedDict 
from types import FunctionType 


class StaticOrderHelper(type): 
    # Requires python3. 
    def __prepare__(name, bases, **kwargs): 
     return OrderedDict() 

    def __new__(mcls, name, bases, namespace, **kwargs): 
     namespace['_field_order'] = [ 
       k 
       for k, v in namespace.items() 
       if not k.startswith('__') and not k.endswith('__') 
        and not isinstance(v, (FunctionType, classmethod, staticmethod)) 
     ] 
     return type.__new__(mcls, name, bases, namespace, **kwargs) 


class Person(metaclass=StaticOrderHelper): 
    first_name = 'First Name' 
    last_name = 'Last Name' 
    phone_number = '000-000' 

    @classmethod 
    def classmethods_not_included(self): 
     pass 

    @staticmethod 
    def staticmethods_not_included(self): 
     pass 

    def methods_not_included(self): 
     pass 


print(Person._field_order)