如何在 python 2 中为元类替换 __prepare__

Posted

技术标签:

【中文标题】如何在 python 2 中为元类替换 __prepare__【英文标题】:How to replace __prepare__ for metaclass in python 2 【发布时间】:2017-05-04 21:57:16 【问题描述】:

我需要将一些代码从 python 3 转换为 python 2。我有一个元类,其中 __prepare__ 方法在类 dict 中设置了一个函数。我尝试转换为 __new__ 方法,但无法设置 SET_DEFAULTS 函数。这可能吗?

我在初始化时有一个NameError: name 'SET_DEFAULTS'

class UazeMessageMeta (type):

    @staticmethod
    def __prepare__(name, bases, **kwargs):
        d = 
        for b in bases: 
            if 'DEFAULT_VALUES' in dir(b):
                d.update(b.DEFAULT_VALUES)
        return  
            'SET_DEFAULTS' : lambda **kwargs : d.update(kwargs),
            'DEFAULT_VALUES' : d
        

class UazeMessage (bytearray):
    """
    A basic message (header only).  This class also provides the
    base behavior for all messages.
    """
    # gp test py27 -----------------
    __metaclass__ = UazeMessageMeta 

    # ------------

    priority    = MessageField(0,  1,  Priority)
    sequence    = MessageField(1,  7,  FieldType.UNSIGNED)
    readWrite   = MessageField(8,  1,  ReadWriteFlag)
    ack         = MessageField(9,  2,  Ack)
    channel     = MessageField(11, 2,  FieldType.UNSIGNED)
    category    = MessageField(13, 3,  Category)
    item        = MessageField(16, 8,  FieldType.UNSIGNED)

    DEFAULT_SIZE = 3

    def __init__(self, init=0, setdefaults=None, **kwargs):
        # If init is still or None, initialize the size of the message
        # using the default size provided in the class.
        if init == None or init == 0: 
            init = type(self).DEFAULT_SIZE
        super(UrmpMessage,self).__init__(init)
        # Set any default or provided fields.
        initval = 
        if (isinstance(init, int) and setdefaults != False) or \
           (setdefaults == True):
            initval = dict(self.DEFAULT_VALUES)
        initval.update(kwargs)
        for key, value in initval.items():
            setattr(self, key, value)

class ResetBase (UazeMessage):
    """Reset response/request structure."""

    resetType = MessageField(24, 8, ResetType)

    SET_DEFAULTS(
        category  = Category.OPERATION,
        resetType = ResetType.SOFT,
        item      = 0)

    DEFAULT_SIZE = 4

【问题讨论】:

请查看您帖子中的缩进;这在 Python 中很重要。 我已经更新了,不便之处见谅 那里似乎至少缺少一个类。请给minimal reproducible example。 【参考方案1】:

通常,您不能这样做。 __prepare__ 的引入是 Python3 的根本变化,它允许自定义解析类体本身的命名空间。

我相信这样做的主要动机是提供一种方法来用 OrderedDict 替换类体内的本地命名空间,以便类初始化(在元类 __new____init__ 方法中)可以受益于类体内方法和属性的声明顺序。应该考虑的是,从 Python 3.6(本周发布最终版本)开始,类主体中默认使用有序字典,并且不再需要元类。

__prepare__ 机制比这更灵活,并且在更简单的使用中,它允许使用预定值简单地预先填充类主体字典。这就是你的项目所做的。

但是,由于这段代码不需要特殊的字典类,而只是预先填充了一个普通的字典,你只需要编写一个普通的函数,将字典和基类作为参数,并填写该字典根据__prepare__ 方法中的现有代码。然后,在类体的开头调用该函数,将locals() 调用返回的字典作为参数传递。就是这样:类体命名空间可以用同样的方式预填充。

def prepare(bases, dct):
    for base in bases:
        dct["special_attribute"] = 
        if "special_attribute" in base.__dict__:
            dct["special_attribute" ].update(base.__dict__["special_attribute"])
     ...

class MyClass(bytearray):
    prepare((bytearray,), locals())
    ...

说了这么多,我真的建议您尽可能不要在此时将项目反向移植到 Python2 - 它只会使您的代码库复杂化 - 并放弃以一致的方式使用新功能(例如,提示上面而不是__prepare__)

【讨论】:

感谢您的解释 - 这是我正在寻找的。我将尝试使项目 python 2 和 3 兼容 - 但在涉及元类时可能会很棘手 应该注意的是,在 Python 3.6 中排序的默认字典对于 Python 3.6+ yet 不一定是正确的。官方声明这是一个简洁的功能,将来不一定会得到支持。换句话说,程序员当心。 虽然字面量字典的默认顺序仍在讨论中,但类主体内的顺序是一个单独的问题(即使在实现级别它们是混合的)。属性的类主体声明中的顺序在 PEP 520 中定义,现在是语言规范的一部分。

以上是关于如何在 python 2 中为元类替换 __prepare__的主要内容,如果未能解决你的问题,请参考以下文章

Python __super 黑魔法失败了

如何在元类的帮助下将python方法变成setter?

Python-元类 单例

Python 元类中的继承如何工作?

Cython 元类 .pxd:我应该如何实现 `__eq__()`?

python面向对象高级:反射魔法方法元类