Python 3中的有序类

4 投票
1 回答
1937 浏览
提问于 2025-04-17 06:57

我正在尝试使用一种“有序类”,这个概念在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 个回答

5

首先,为什么 __prepare__ 是一个类方法呢?它的定义没有使用元类,这只是个约定吗?

正如评论中提到的,当 __prepare__ 被调用时,类本身还没有被实例化。这意味着,如果它是一个普通的方法(第一个参数是 self),那么在这个调用中就没有值可以作为 self

这也很合理,因为 __prepare__ 的作用是返回一个类似字典的对象,用来替代普通字典解析类体,所以它根本不依赖于类的创建。

如果像评论中提到的,它是一个 staticmethod(意味着它不会获取 metacls 作为第一个参数),那么 __prepare__ 就无法访问元类的其他方法——这就限制了它的功能。

而且,是的,就像 self 一样,这里的 metacls 其实也是个约定——一个普通的 Python 标识符。(注意,当类方法在普通类中使用时,第一个参数通常用 cls 表示,而不是 metacls。)

其次,当我尝试这段代码时,__module__ 最终出现在 MyClass.member_names 中,排在 method1method2 之前,这似乎与评论中说的 method1 是第一个元素相矛盾。为什么这个特殊属性会出现在列表中,而其他的却没有?还有没有其他可能让我惊讶的属性(除了如果类有文档字符串的 __doc__ 和我明确定义的属性)?

这可能是因为类的 __module__ 特殊成员是在考虑 __prepare__ 方法时想出来的。(我没有通过谷歌搜索定义 __module__ 的 PEP 来验证,但我敢打赌是这样的。)

而且,不,未来的 Python 版本没有保证不会添加一些新的魔法属性到类中,这些属性可能会在你的显式类成员之前出现在字典中。

但使用元类时,你完全可以掌控——你可以调整你的类似字典的对象(代码中的 member_table),比如不计算任何以 "__" 开头的属性,或者甚至根本不把它们添加到最终的类实例中(这样做可能会导致你定义的类在某些 Python 特性下无法正常工作)。

最后,这个实现没有从基类中获取成员名称。如果我想实现这个功能,修改 __prepare__ 这样做有什么问题吗?

从阅读中来看,我没有发现你提议的实现有什么问题,但当然你需要测试一下。

更新(2013-06-30):这个话题在 Python 开发者列表中正在积极讨论,看起来从 Python 3.4 开始,所有类默认都会有顺序,不再需要使用元类或仅仅为了排序而使用 __prepare__

撰写回答