Python 3中的有序类
我正在尝试使用一种“有序类”,这个概念在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
我有几个地方不太明白。首先,为什么__prepare__
是一个classmethod
?它的定义没有使用metacls
,这只是个约定吗?
其次,当我运行这段代码时,发现'__module__'
出现在MyClass.member_names
中,排在'method1'
和'method2'
之前,这似乎和注释中说的'method1'
是第一个元素相矛盾。为什么这个特殊属性会出现在列表中,而其他属性却没有?还有没有其他属性会让我感到惊讶的(除了如果类有文档字符串的__doc__
,以及我自己明确定义的属性)?
最后,这个实现没有从基类中获取member_names
。如果我想实现这个功能,以下对__prepare__
的修改是否有问题(除了它没有检查重复项)?
@classmethod
def __prepare__(metacls, name, bases):
prep_dict = member_table()
for base in bases:
try:
prep_dict.member_names.extend(base.member_names)
except AttributeError:
pass
return prep_dict
1 个回答
首先,为什么
__prepare__
是一个类方法呢?它的定义没有使用元类,这只是个约定吗?
正如评论中提到的,当 __prepare__
被调用时,类本身还没有被实例化。这意味着,如果它是一个普通的方法(第一个参数是 self
),那么在这个调用中就没有值可以作为 self
。
这也很合理,因为 __prepare__
的作用是返回一个类似字典的对象,用来替代普通字典解析类体,所以它根本不依赖于类的创建。
如果像评论中提到的,它是一个 staticmethod
(意味着它不会获取 metacls
作为第一个参数),那么 __prepare__
就无法访问元类的其他方法——这就限制了它的功能。
而且,是的,就像 self
一样,这里的 metacls
其实也是个约定——一个普通的 Python 标识符。(注意,当类方法在普通类中使用时,第一个参数通常用 cls
表示,而不是 metacls
。)
其次,当我尝试这段代码时,
__module__
最终出现在 MyClass.member_names 中,排在method1
和method2
之前,这似乎与评论中说的method1
是第一个元素相矛盾。为什么这个特殊属性会出现在列表中,而其他的却没有?还有没有其他可能让我惊讶的属性(除了如果类有文档字符串的__doc__
和我明确定义的属性)?
这可能是因为类的 __module__
特殊成员是在考虑 __prepare__
方法时想出来的。(我没有通过谷歌搜索定义 __module__
的 PEP 来验证,但我敢打赌是这样的。)
而且,不,未来的 Python 版本没有保证不会添加一些新的魔法属性到类中,这些属性可能会在你的显式类成员之前出现在字典中。
但使用元类时,你完全可以掌控——你可以调整你的类似字典的对象(代码中的 member_table
),比如不计算任何以 "__" 开头的属性,或者甚至根本不把它们添加到最终的类实例中(这样做可能会导致你定义的类在某些 Python 特性下无法正常工作)。
最后,这个实现没有从基类中获取成员名称。如果我想实现这个功能,修改
__prepare__
这样做有什么问题吗?
从阅读中来看,我没有发现你提议的实现有什么问题,但当然你需要测试一下。
更新(2013-06-30):这个话题在 Python 开发者列表中正在积极讨论,看起来从 Python 3.4 开始,所有类默认都会有顺序,不再需要使用元类或仅仅为了排序而使用 __prepare__
。