从元类访问构造函数的参数
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__
方法来自动插入此行为。由于这种行为更易于跟踪、维护和整体上仅使用普通继承即可与其他类结合,因此根本不需要元类。
【讨论】:
以上是关于从元类访问构造函数的参数的主要内容,如果未能解决你的问题,请参考以下文章