问题
I'm confused about this code I got from here:
import functools
def singleton(cls):
"""Make a class a Singleton class (only one instance)"""
@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs):
if not wrapper_singleton.instance:
wrapper_singleton.instance = cls(*args, **kwargs)
return wrapper_singleton.instance
print('****')
wrapper_singleton.instance = None
return wrapper_singleton
@singleton
class TheOne:
pass
Why doesn't wrapper_singleton.instance = None
set the instance to none each time the class is instantiated? I put a print statement above this line and it only gets called once also. Thanks
>>> first_one = TheOne()
>>> another_one = TheOne()
>>> id(first_one)
140094218762280
>>> id(another_one)
140094218762280
>>> first_one is another_one
True
回答1:
Why doesn't
wrapper_singleton.instance = None
set the instance to none each time the class is instantiated?
Because that part of the code is only executed the when class is decorated.
This:
@singleton
class TheOne:
pass
is functionally equivalent to
class TheOne:
pass
TheOne = singleton(TheOne)
Both versions of the code actually return a function through the magic of functools.wraps
, that acts as if it was the wrapped callable, as @smarie excellently explains here.
TheOne = singleton(TheOne)
print(TheOne)
# <function TheOne at 0x00000000029C4400>
If you remove the @functools.wraps
decoration, you have a superficial look behind the scenes:
def singleton(cls)
#@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs): ...
TheOne = singleton(TheOne)
print(TheOne)
# <function singleton.<locals>.wrapper_singleton at 0x00000000029F4400>
So the name TheOne
is actually assigned to the inner function wrapper_singleton
of your singleton
function.
Hence when you do TheOne()
, you don't instantiate the class directly, you call wrapper_singleton
which does that for you.
This means, that the function singleton
is only called when you decorate the class or do that manually via TheOne = singleton(TheOne)
. It defines wrapper_singleton
, creates an additional attribute instance
on it (so that if not wrapper_singleton.instance
doesn't raise an AttributeError) and then returns it under the name TheOne
.
You can break the singleton by decorating the class again.
class TheOne:
def __init__(self, arg):
self.arg = arg
TheOne = singleton(TheOne)
t1 = TheOne(42)
print(t1.arg, id(t1))
# 42 43808640
# Since this time around TheOne already is wrapper_singleton, wrapped by functools.wraps,
# You have to access your class object through the __wrapped__ attribute
TheOne = singleton(TheOne.__wrapped__)
t2 = TheOne(21)
print(t2.arg, id(t2))
# 21 43808920
来源:https://stackoverflow.com/questions/55155620/clarification-on-why-decorator-only-called-once