如何按声明的顺序读取类属性?

Eri*_*ric 41 python metaclass class

我写一个元类读取类的属性,并将它们存储在列表中,但我想要的清单(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)
Run Code Online (Sandbox Code Playgroud)

结果:

[('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>)]
Run Code Online (Sandbox Code Playgroud)

这不Column()符合Listing类的属性的声明顺序.如果我classDict直接使用,它也无济于事.

我该怎么办?

Tho*_*erl 38

在当前版本的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']
Run Code Online (Sandbox Code Playgroud)

但是,这种方法对现有的类没有帮助,你需要使用内省.


Eri*_*ric 14

以下是我开发的解决方法:

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
Run Code Online (Sandbox Code Playgroud)


Con*_*tor 11

对于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__']
Run Code Online (Sandbox Code Playgroud)


Dun*_*can 6

如果您使用的是Python 2.x,那么您将需要一个像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
Run Code Online (Sandbox Code Playgroud)


Nik*_*iko 6

1)由于类定义中的Python 3.6属性具有名称在源中出现的相同顺序.此订单现在保留在新类的__dict__属性中(https://docs.python.org/3.6/whatsnew/3.6.html#whatsnew36-pep520):

class Column:
    pass

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

print(MyClass.__dict__.keys())
Run Code Online (Sandbox Code Playgroud)

您将看到这样的输出(MyClass.__dict__可能像OrderedDict一样使用):

dict_keys(['__module__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a', '__dict__', '__weakref__', '__doc__'])
Run Code Online (Sandbox Code Playgroud)

注意__xxx__python添加的额外 字段,您可能需要忽略它们.

2)对于以前的Python 3.x版本,您可以使用基于@Duncan答案的解决方案,但更简单.我们使用这个事实,该__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()
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用__fields__属性按所需顺序访问属性:

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

需要注意的是会有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    
Run Code Online (Sandbox Code Playgroud)

它只会给MyClass的玩家:

['mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a']
Run Code Online (Sandbox Code Playgroud)

3)这个anwser只能在python3.x中使用,因为__prepare__python2.7中没有定义