【问题标题】:How to read class attributes in the same order as declared?如何按照声明的顺序读取类属性?
【发布时间】:2011-05-26 11:06:56
【问题描述】:

我正在编写一个读取类属性并将它们存储在列表中的元类,但我希望列表(cls.columns)尊重声明顺序(即:mycol2mycol3zutcool, 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>)]

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

我该如何继续?

【问题讨论】:

  • 如果没有某种源代码级别的分析,我认为您无法按顺序排列它们。在任何情况下,顺序都应该是无关紧要的。 dict 按键散列,这就是为什么您没有按顺序看到它的原因
  • 完全是一个非常有建设性的问题。谢谢
  • 您可以查看 tosca 小部件 2,了解如何操作

标签: python class metaclass


【解决方案1】:

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

你会看到这样的输出(MyClass.__dict__ 可以像 OrderedDict 一样使用):

dict_keys(['__module__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a', '__dict__', '__weakref__', '__doc__'])

注意python添加的额外__xxx__字段,你可能需要忽略它们。

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

现在您可以使用属性__fields__ 按要求的顺序访问属性:

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

请注意,'__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    

它只会给出来自 MyClass 的属性:

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

3) 这个anwser只适用于python3.x,因为python2.7中没有__prepare__的定义

【讨论】:

    【解决方案2】:

    在当前版本的 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']
    

    这种方法不能帮助您处理现有的类,但是,您需要使用自省。

    【讨论】:

    • 关于如何使用和学习introspection的任何想法或建议? (或者更好的是,在这种情况下如何应用它。)
    • 还应该注意的是,即使在 P3.6 中,当使用外部类来帮助定义 Something 类项时,顺序也不总是保留。
    【解决方案3】:

    排除方法的答案:

    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)
    

    【讨论】:

    • 这也适用于imported Scrapy items,当他们被分配到一个带有some_item = Field()的类中时。
    【解决方案4】:

    对于 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__']
    

    【讨论】:

      【解决方案5】:

      如果您使用的是 Python 2.x,那么您将需要一个像 Lennart 建议的那样的 hack。如果您使用的是 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
      

      【讨论】:

        【解决方案6】:

        我猜你应该可以创建一个类,用ordered-dict 替换它的__dict__

        【讨论】:

        • 好主意。你测试了吗?
        • 我试过了,结果不在defined order
        【解决方案7】:

        这是我刚刚开发的解决方法:

        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
        

        【讨论】:

        • 首先我认为“你必须在每节课后重置creation_counter”,然后我意识到你根本不关心,假设你只关心内部顺序。它确实有效。 :)
        • 它如何在并行线程中工作?我认为这段代码不是线程安全的。
        • 对于 Python3.6+,请参阅@Conchylicultor 答案,这可能基于该语言的较新的 dict 顺序插入保证。你可以使用vars(cls) 而不是更丑的cls.__dict__
        猜你喜欢
        • 2013-05-07
        • 2023-03-07
        • 2015-06-17
        • 1970-01-01
        • 2015-01-10
        • 2015-07-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多