从元类访问构造函数的参数

Posted

技术标签:

【中文标题】从元类访问构造函数的参数【英文标题】:Accessing the parameters of a constructor from a metaclass 【发布时间】:2018-06-23 13:48:20 【问题描述】:

TL;DR - 我有一个使用元类的类。 我想在初始化过程之前从元类访问对象构造函数的参数,但我找不到访问这些参数的方法。如何函数__new__?


为了练习在python中元类的使用,我想创建一个类,用作《银河系漫游指南》一书中的超级计算机“深度思考”。

我的课程的目的是存储超级计算机从用户那里获得的各种查询。 归根结底,它只会获取一些参数并存储它们。 如果给定的参数之一是数字 42 或字符串“生命、宇宙和一切的答案”,我不想创建一个新对象,而是返回一个指向现有对象的指针。 这背后的想法是这些对象将完全相同,因此当使用 is 运算符比较这两个时,结果将是正确的。

为了能够使用is 运算符并得到True 作为答案,我需要确保这些变量指向同一个对象。所以,为了返回一个指向现有对象的指针,我需要在对象的初始化过程中间进行干预。我无法检查构造函数本身的给定参数并相应地修改对象的内部变量,因为为时已晚:如果我仅将给定参数作为__init__ 函数的一部分进行检查,那么这两个对象将被分配在不同的部分内存(它们可能相等,但在使用 is 运算符时不会返回 True)。

我想过做这样的事情:

class SuperComputer(type):

    answer = 42

    def __new__(meta, name, bases, attributes):
        # Check if args contains the number "42" 
        # or has the string "The answer to life, the universe, and everything"
        # If so, just return a pointer to an existing object:
        return SuperComputer.answer

        # Else, just create the object as it is:
        return super(SuperComputer, meta).__new__(meta, name, bases, attributes)


class Query(object):
    __metaclass__ = SuperComputer

    def __init__(self, *args, **kwargs):
        self.args = args
        for key, value in kwargs.items():
            setattr(self, key, value)


def main():
    number = Query(42)
    string = Query("The answer to life, the universe, and everything")
    other = Query("Sunny", "Sunday", 123)
    num2 = Query(45)

    print number is string  # Should print True
    print other is string  # Should print False
    print number is num2  # Should print False


if __name__ == '__main__':
    main()

但我一直坚持从构造函数获取参数。 我看到__new__ 方法只有四个参数: 元类实例本身、类的名称、它的基类及其属性。 如何将参数从构造函数发送到元类? 我可以做些什么来实现我的目标?

【问题讨论】:

您试图在错误的地方进行检查。它需要在Query.__new__ 中,而不是在SuperComputer.__new__ 中。你根本不需要元类。 我不明白你为什么需要一个元类。元类__new__ 是在你定义class Query 时执行的,那时你似乎不想做任何事情。如果您想检查 Query 何时被实例化,只需在 Query.__new__ 中进行检查。 所以在这种情况下我根本不需要使用metaclass?我认为我需要这样做,因为这类似于(半)单例,因为它有时会返回相同的对象而不是创建新的对象,我看到实现这一目标的最佳方法是使用元类(***.com/a/6798042/6253504 )。您能否详细说明一下,以便我了解有什么区别以及我可以做些什么不同的事情? 使用元类远非创建单例的最佳方式。 (实际上,甚至在阅读此评论之前,我就在回答中提到了这一点) 【参考方案1】:

你不需要一个元类。

事实上,__init__ 不是 Python 中对象的“构造函数”,而是通常称为“初始化器”。 __new__ 在其他语言中更接近于“构造函数”的角色,并且它不仅适用于元类——所有类都有一个__new__ 方法。如果没有显式实现,则直接调用object.__new__

实际上,object.__new__ 在 Python 中创建了一个新对象。从纯 Python 代码中,没有其他可能的方法来创建对象:它总是会经过那里。这意味着如果您在自己的类上实现 __new__ 方法,您可以选择不创建新实例,而是返回同一类(或任何其他对象)的另一个预先存在的实例。

您只需要记住:如果__new__ 返回同一类的实例,则默认行为是在同一实例上调用__init__。否则,__init__ 不会被调用。

还值得注意的是,近年来,一些使用元类在 Python 中创建“单例”的方法变得流行——这实际上是一种过度杀伤的方法,覆盖 __new__ 也更适合创建单例。

在您的情况下,您只需要有一个包含您要跟踪的参数作为键的字典,并检查您是创建一个新实例还是在__new__ 运行时“回收”一个实例。字典可以是类属性,也可以是模块级别的全局变量 - 这是您的选择:

class Recycler:
   _instances = 
   def __new__(cls, parameter1, ...):
       if parameter1 in cls._instances:
           return cls._instances[parameter1] 
       self = super().__new__(cls) # don't pass remaining parameters to object.__new__
       _instances[parameter1] = self
       return self

如果您在__init__ 中还有任何代码,请将其移至__new__

您可以拥有一个具有这种行为的基类并拥有一个类层次结构,而无需为每个类重新实现__new__

对于元类,在实际创建使用该元类创建的类的新实例时,不会调用它的任何方法。它只会用于通过在使用该元类创建的类上装饰或创建新的__new__ 方法来自动插入此行为。由于这种行为更易于跟踪、维护和整体上仅使用普通继承即可与其他类结合,因此根本不需要元类。

【讨论】:

以上是关于从元类访问构造函数的参数的主要内容,如果未能解决你的问题,请参考以下文章

swift 元类构造方法

C++学习:4拷贝友元

从元类设置实例变量

Java 实现 - 元类

具有私有构造函数和析构函数的类对象的向量?

Java:使用类型参数访问私有构造函数