【问题标题】:Ordered classes in Python 3Python 3 中的有序类
【发布时间】:2012-01-05 03:58:19
【问题描述】:

我正在尝试使用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__''method1''method2' 之前出现在MyClass.member_names,这显然与声称'method1' 是第一个元素的cmets 相矛盾。为什么这个特殊属性最终出现在列表中,而其他属性没有?还有其他可能让我感到惊讶的吗(除了 __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

【问题讨论】:

  • __prepare__ 将使用 2 个参数,但当以 MyClass.__prepare__ 调用时,它将被绑定为 MyClass 的方法。无论您是否会这样做,最好将其绑定到元类并使用第一个参数获取mcls 实例。我想你可以改成staticmethod
  • PEP 说“在评估类主体之前,将名为 __prepare__ 的属性作为函数调用”。所以它必须是类方法或静态方法,而不是实例方法,因为此时尚未调用(实例化)元类。
  • 感谢 eryksun 和 yak,但我不确定我是否完全理解 - 我想我对 classmethod 的确切作用有点天真。我知道如果__prepare__ 未声明为classmethod,它会接收namebases 作为参数,而如果它是classmethod,它也会接收类-但classmethod 有什么其他效果在这种情况下?另外,从元类继承的方法没有出现在dir(MyClass) 中是否有原因?
  • 正如 yak 所解释的,__prepare__ 被查找为属性,而不是方法。如果省略 classmethod 装饰器,则还需要省略 metacls 参数。除了添加 metacls 参数之外,classmethod 装饰器没有其他作用。请注意,OrderedClassMyClass 的元类,而不是基类,因此 MyClass 不继承自 OrderedClass。 __module__ 属性可能很特殊,因为它必须在创建类时添加,并且不能从对象继承。您可以在 member_table.__setitem__ 方法中将其过滤掉。

标签: python python-3.x metaclass prepare


【解决方案1】:

首先,__prepare__ 是一个类方法有什么原因吗?定义不使用 metacls - 这只是一个约定吗?

正如 cmets 中所指出的,当调用 __prepare__ 时,类本身还没有被实例化。这意味着,如果它是一个普通方法(以self 作为第一个参数) - 在此调用中将没有任何值成为self

这是有道理的,因为__prepare__ 所做的是返回一个类似字典的对象,而不是在类体的解析中使用普通字典 - 所以它根本不依赖于正在创建的类。

如果如 cmets 中所述,它是 staticmethod(意味着它不会获得 metacls 第一个参数),那么 __prepare__ 将无法访问元类的任何其他方法——这是不必要的限制。

是的,就像selfmetacls 在这种情况下只是一个约定——一个普通的 Python 标识符。 (注意,在普通类中使用classmethods时,第一个参数通常用cls表示,而不是metacls。)

其次,当我尝试这段代码时,__module__method1method2 之前出现在 MyClass.member_names 中,这显然与声称 method1 是第一个元素的 cmets 相矛盾。为什么这个特殊属性最终出现在列表中,而其他属性没有?还有其他可能让我感到惊讶的吗(除了 __doc__ 如果该类有文档字符串,以及我明确定义的任何内容)?

可能是因为在考虑元类的__prepare__ 方法之后,才考虑了类的__module__ 特殊成员。 (我无法通过谷歌搜索定义 __module__ 的 PEP 来验证,但我敢打赌就是这种情况。)

不,不能保证未来版本的 Python 不会为类添加更多神奇属性,这些属性可能会出现在字典中显式类成员之前。

但是使用元类,您可以完全掌控 - 您可以像对象一样调整您的 dict (member_table 在您的代码中)不计算以“__”开头的任何属性 - 例如。甚至根本不将 then 添加到最终类实例中(以这种方式定义的类可能无法使用某些 Python 功能)。

最后,这个实现不会从基类中检索 member_names。如果我想实现这一点,对__prepare__的以下更改有什么问题吗?

通过阅读,我认为您提出的实现没有任何问题,但当然,您必须对其进行测试。

更新(2013-06-30):Python 开发者列表中正在积极讨论这个主题,看起来从 Python 3.4 开始,所有类都将默认排序,无需使用元类或__prepare__ 仅用于订购

【讨论】:

  • 底层数据结构是什么?一个有序的字典?
猜你喜欢
  • 1970-01-01
  • 2015-09-10
  • 2017-09-21
  • 2021-12-30
  • 2017-05-22
  • 2020-11-13
  • 1970-01-01
  • 2021-01-25
相关资源
最近更新 更多