Давайте пройдемся по (исправленному) определению Singleton
и классу, определенному с его помощью. Я заменяю использование cls
на Singleton
, где поиск все равно пропускается.
class Singleton(type):
_instances = {}
# Each of the following functions use cls instead of self
# to emphasize that although they are instance methods of
# Singleton, they are also *class* methods of a class defined
# with Singleton
def __call__(cls, *args, **kwargs):
if cls not in Singleton._instances:
Singleton._instances[cls] = super().__call__(*args, **kwargs)
return Singleton._instances[cls]
def clear(cls):
try:
del Singleton._instances[cls]
except KeyError:
continue
class MySing(metaclass=Singleton):
pass
s1 = MySing() # First call: actually creates a new instance
s2 = MySing() # Second call: returns the cached instance
assert s1 is s2 # Yup, they are the same
MySing.clear() # Throw away the cached instance
s3 = MySing() # Third call: no cached instance, so create one
assert s1 is not s3 # Yup, s3 is a distinct new instance
Во-первых, _instances
- это атрибут класса метакласса, предназначенный для сопоставления класса с уникальным экземпляром этого класса.
__call__
- это метод экземпляра метакласса; его цель состоит в том, чтобы сделать экземпляры метакласса (то есть классов) вызываемыми. cls
здесь определяется класс, не метакласс. Поэтому каждый раз, когда вы звоните MyClass()
, это преобразуется в Singleton.__call__(MyClass)
.
clear
также является методом экземпляра метакласса, что означает, что он также принимает в качестве аргумента экземпляр метакласса (то есть опять-таки класс), а не экземпляр класса, определенного в метаклассе. MyClass.clear()
совпадает с Singleton.clear(MyClass)
. (Это также означает, что вы можете, но, вероятно, не следует для ясности, писать s1.clear()
.)
Идентификация методов экземпляра метакласса с помощью «классических» методов класса также объясняет, почему вам нужно использовать __call__
в мета-классе, где вы должны использовать __new__
в обычном классе: __new__
в специальном случае метод класса без необходимости украшать его как таковой. Для метакласса немного сложно определить метод instance для его экземпляров, поэтому мы просто используем __call__
(поскольку type.__call__
мало что делает, если вообще что-то, кроме вызова правильный __new__
метод).