Python __call__() 这是隐式类方法吗?

Python __call__() is this an implicit classmethod?

我想在 python 中实现单例模式,我喜欢 http://www.python-course.eu/python3_metaclasses.php 中描述的模式。

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class SingletonClass(metaclass=Singleton):
    pass
class RegularClass():
    pass
x = SingletonClass()
y = SingletonClass()
print(x == y)
x = RegularClass()
y = RegularClass()
print(x == y) 

代码运行完美。但是,__call__() 没有 self,它也没有 @classmethod@staticmethod 声明。

但是,在 Python 数据模型 https://docs.python.org/3/reference/datamodel.html#object.__call__ 中,__call__() 方法的参数中有一个 self。

如果我通过 self 或声明为 @staticmethod@classmethod,代码将不起作用。

谁能解释一下 __call__() 方法背后的语法逻辑。

将方法的第一个参数命名为 clsself 只是一种约定__call__方法确实有一个self参数,只是这里是named cls。那是因为对于 metaclass,该方法绑定到一个 class 对象,名称反映了这一点。

相同的约定适用于 @classmethod 方法;第一个参数总是 class,由于 classmethod 对象的绑定方式的性质,因此将第一个参数命名为 cls.

是有意义的

但是您可以随意为第一个参数命名。使 class 方法或常规方法或元类型上的方法起作用的不是名称。使用 selfcls 所做的只是 记录这是什么类型的对象,使其他开发人员更容易在脑海中跟踪正在发生的事情。

所以不,这不是隐式的 class 方法。第一个参数没有绑定到 Singleton metaclass 对象,它绑定到被调用的 class 对象。这是有道理的,因为 class 对象是 Singleton 元类型的一个实例。

如果您想深入了解 binding 的工作原理(导致传入第一个参数的过程,无论名称如何),您可以阅读 Descriptor HOWTO。 TLDR:函数、propertyclassmethodstaticmethod 对象都是 描述符 ,并且每当您将它们作为支持对象的属性访问时,例如一个实例或一个 class,它们是绑定的,通常会导致 不同的 对象作为结果返回,在调用时将绑定对象传递给实际函数。

马丁的回答说明了一切。我正在添加这个,所以也许不同的措辞可以为不同的人带来更多的启发:

Python 调用() 这是一个隐含的class方法吗?

没有。但是所有 "metaclass" 方法对于使用该元 class 的 class 都是隐式的 "class methods"。

当我们考虑到 classes 只是元 class 的实例时,这是隐含的。从语言的角度来看,class 的行为几乎与任何其他实例完全一样 - 与 class 的任何交互都会触发对象中的 dunder (__magic__) 方法 - 比如使用“+、-、*、/”运算符,或用[ ]检索索引,或用( )调用它们,触发其class上的相应方法。那通常是type

并且,正如在另一个答案中所说的那样,Python 不关心您在方法的第一个参数上使用的名称。对于 metaclasses,在那里使用 cls 是有意义的,因为 "instances" 方法处理的是 metaclasses。因为将 metaclass' __new__ 方法的第一个参数命名为 metacls 是有道理的(就像下面的例子一样)。因为新的 "cls" 是我们在调用 type.__new__ 之后得到的对象——纯 Python 中唯一可能的调用实际上会创建一个 class 对象。

class Meta(type):
    def __new__(metacls, name, bases, namespace):
         ...
         cls = super().__new__(metacls, name, bases, namespace)
         ...
         return cls

(现在,还是正题:__new__ 的特例 隐式静态方法的(即使是普通的 classes 不打算成为 metaclasses) - Python 专门添加第一个参数,使用与它不同的机制完成常规 classmethods。这就是为什么上面的 super().__new__ 调用需要将 metacls 作为第一个参数)