2010-12-16 93 views
34

我写一个元类读取类的属性,并将它们存储在列表中,但我想要的清单(cls.columns)尊重声明的顺序(即:mycol2mycol3zutcoolmenfina在我示例):如何以与声明相同的顺序读取类属性?

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

这里是我中庸之道developped变通方法:

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

首先,我认为“你必须在每个课程后重置creation_counter”,然后我意识到你根本不知道,假设你只关心内部命令。它实际上工作。 :) – 2010-12-16 11:14:45

+0

它如何在并行线程中工作?我认为这段代码不是线程安全的。 – pylover 2014-12-11 03:56:12

-1

我想你应该能够使一个类,你如果要使用Python 2.x的然后用ordered-dict

+1

不错的主意。你有没有测试过它? – pylover 2014-12-11 03:56:49

+0

我试过了,但事实证明它并没有按照“定义的顺序” – 2016-12-08 11:45:28

6

取代其__dict__你需要像Lennart提出的那样的黑客。如果你使用的是Python 3.x,那么请阅读PEP 3115,因为它包含了一个你想要的东西。只需修改示例以仅查看Column()实例:

# 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__',从type类生'__qualname__'。要摆脱他们,您可以在以下方式(其他城市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  

它将从MyClass的只给出ATTRS:

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