我正在编写一个读取类属性并将它们存储在列表中的元类,但我希望列表(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
,也没有帮助。
我该如何继续?
在当前版本的 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']
但是,此方法对您需要使用内省的现有类没有帮助。
这是我刚刚开发的解决方法:
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
对于 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__']
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__
定义
如果您使用的是 Python 2.x,那么您将需要一种 hack,例如 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
排除方法的答案:
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)
如果您也需要将其用于注释,则可以使用此方法。 添加了注释但没有默认值的属性不会添加到 dct 中,并且顺序会被打乱。这个解决方案将解决这个问题。
class OrderedAttrs(dict):
def __new__(cls, *args, _ordered__attrs=None, **kwargs):
inst = super().__new__(cls)
inst.ordered_attrs = _ordered__attrs or []
return inst
def __setitem__(self, key, value):
if key not in self.ordered_attrs:
self.ordered_attrs.append(key)
if key == '__annotations__':
# create another instance of this class
# giving it a reference to attrs list this one has
value = type(self)(_ordered__attrs=self.ordered_attrs)
dict.__setitem__(self, key, value)
def todict(self):
if '__annotations__' in self:
self['__annotations__'] = dict(self['__annotations__'])
return dict(self)
class Meta(type):
@classmethod
def __prepare__(cls, name, bases, **kwargs):
return OrderedAttrs()
def __new__(mcs, name, bases, dct):
ordered_attrs = dct.ordered_attrs
...# do something with ordered_attrs
dct = dct.todict()
klass = type.__new__(mcs, name, bases, dct)
klass.ordered_attrs = ordered_attrs
return klass
class Class(metaclass=Meta):
a: int
b: int = 0
c = 0
print('ordered attrs: ', Class.ordered_attrs)
#ordered attrs: ['__module__', '__qualname__', '__annotations__', 'a', 'b', 'c']
我想你应该能够创建一个类,用
__dict__
替换它的
ordered-dict