2010-12-16 2 views
34

Я пишу метакласс, который читает атрибуты класса и хранить их в списке, но я хочу, чтобы список (cls.columns) соблюдать порядок декларирования (то есть: mycol2, mycol3, zut, cool, menfin, a в моем пример):Как читать атрибуты класса в том же порядке, что и объявлено?

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) 

Результат:

[('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>)] 

Это не уважает порядок декларации о Column() атрибутов для Listing класса. Если я использую classDict напрямую, это тоже не помогает.

Как я могу продолжить?

+3

Я не думаю, что вы можете получить их в порядке без какого-то анализа исходного уровня. В любом случае заказ должен быть в основном неактуальным. «Dict» хэши по ключу, поэтому вы не видите его в порядке – Robert 2010-12-16 10:13:42

+0

На самом деле очень конструктивный вопрос. спасибо – pylover 2014-12-11 03:58:29

+0

, вы можете взглянуть на виджет tosca 2, чтобы найти, как это сделать – pylover 2014-12-11 04:04:23

ответ

12

Вот обходной путь я 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 
-1

Я думаю, вы должны быть в состоянии сделать класс, где вместо ее __dict__ с ordered-dict

6

Если вы используете Python 2.x, то вам понадобится взломать, например, тот, который предлагает Леннарт. Если вы используете Python 3.x, тогда прочитайте PEP 3115, так как в нем содержится пример, который делает то, что вы хотите. Просто измените пример только посмотреть на вашу колонку() экземпляров:

# 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

В текущей версии Python, сохраняется порядок класса. См. PEP520.

В более старых версиях языка (3.5 и ниже, но не 2.x) вы можете предоставить метакласс, который использует пространство имен классов OrderedDict.

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

Этот подход не поможет вам с существующими классами, однако, где вы должны будете использовать самоанализ.

5

Основано на решении от @Duncan, но проще (для python3 ТОЛЬКО). Мы используем тот факт, что метод __prepare__ возвращает OrderDict вместо простого dict - поэтому все атрибуты, собранные до вызова __new__, будут заказаны.

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

Теперь вы можете использовать атрибут __fields__ для доступа атрибутов в требуемом порядке:

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

Обратите внимание, что там будет ATTRS '__module__', '__qualname__', рожденные от type класса. Чтобы избавиться от них, вы можете фильтровать имена следующим образом (изменение 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  

это даст только AttrS от MyClass:

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

, пожалуйста, помните ваш ответ только работоспособным в Python3.x, потому что нет Подготовить определение в python2.7

9

Для python 3.6 это стало поведением по умолчанию. См 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

ответ, который исключает методы:

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) 
Смежные вопросы