Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python Singleton #103

Open
xxleyi opened this issue May 26, 2019 · 8 comments
Open

Python Singleton #103

xxleyi opened this issue May 26, 2019 · 8 comments
Labels

Comments

@xxleyi
Copy link
Owner

xxleyi commented May 26, 2019

面试经常考察的一个点,值得掌握,借此更熟练的使用 class 的种种特性

最简单易用的:Python 中的 module 是天然的 singleton 模式

# module file: single.py
class Singleton(object):
    pass
Singleton = Singleton()

# when use
from single import Singleton
@xxleyi xxleyi added the WHAT label May 26, 2019
@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

重写类中新建实例时的 __new__ 方法

# do not print "enter call"
class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        print('enter call')
        if not cls._instance:
            # 看下面这个 super 后面 __new__ 的参数,依然要带着 cls
            # 因为这里要调用元类的 __new__ 函数(静态方法),所以需要当前类作为参数
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton() # print "enter call"
b = Singleton() # print "enter call" again
a is b # True
In [7]: object.__new__
Out[7]: <function object.__new__(*args, **kwargs)>

在新建类实例时:object.__new__() takes exactly one argument (the type to instantiate)

@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

重写元类中新建实例时的 __call__ 方法,新建一个单例元类

# do not print "enter call"
class SingletonType(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        print('enter call')
        if cls not in cls._instances:
            # 这里的 super 后面的 __call__ 无需使用 cls 作为参数
            # 因为是在继承 type 中 __call__ 方法
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

# do not print "enter call" either
class Singleton(metaclass=SingletonType):
    pass

a = Singleton() # print "enter call"
b = Singleton() # print "enter call" again
a is b # True
In [6]: type.__call__
Out[6]: <slot wrapper '__call__' of 'type' objects>

值得注意的是,这里虽然是新建一种所谓的单例类型,但是实质上还是在类实例化的过程中动手脚。

一种更清晰的写法

class SingletonType(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        print('enter call')
        if cls not in SingletonType._instances:
            # 这里的 super 后面的 __call__ 无需使用 cls 作为参数
            # 因为是在继承 type 中 __call__ 方法
            SingletonType._instances[cls] = type.__call__(cls, *args, **kwargs)
        return SingletonType._instances[cls]

# do not print "enter call" either
class Singleton(metaclass=SingletonType):
    pass

a = Singleton() # print "enter call"
b = Singleton() # print "enter call" again
a is b # True

@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

还可以使用装饰器控制类的实例化

装饰器是一种面向切面编程的设计模式,Python 中有函数装饰器,也有类装饰器。通常函数装饰器便够用了。

from functools import wraps

def singleton(cls):
    instances = {}
    @wraps(cls)
    def get_instance(*args, **kwargs):
        print('enter getinstance')
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance


@singleton
class Singleton(object):
    pass

a = Singleton() # print "enter get_instance"
b = Singleton() # print "enter get_instance" again
a is b # True

type(Singleton) # function
print(Singleton) # <function Singleton at xxxxxxxx>

值得注意的是,这里在使用函数装饰器时,虽然使用了 wraps,但最终得到的 Singleton 当然还是一个函数,而且是一个闭包。

@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

值得深挖的一个点

Callable Objects

在 Python 中,不止函数是 Callable Objects,类也是 Callable Objects,实例也可以是 Callable Objects。这也是 Python 在类的实例化时无需一个 new 关键字的原因所在。

在 Python 中一切皆对象,只要对象实现了 __call__ 方法,就是 Callable Objects,就可以写成 some_object(*args, **kwargs) 的形式。

def t():
    pass

dir(t)
['__annotations__',
 '__call__', # 看见没,函数也有 __call__ 方法
 '__class__',
 '__closure__', # 这个是用来实现闭包的
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',  # 这是新增加的方法,可以在很多情况下替换 __new__
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',  # 这个名字挺有用,很适合用在通用化的日志输出中
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

要稍稍懂一点的元类

元类中有 new 方法,在创建类时执行,返回类。而 call 方法则是针对调用类生成实例这个过程。

class NewMetaType(type):
    def __new__(meta_cls, name, bases, dct):
        x = super().__new__(meta_cls, name, bases, dct)
        x.attr = 100
        return x

super().__new__(meta_cls, name, bases, dct) 中,meta_cls 这个形参未来会被传入 NewMetaType 这个新的元类。

@xxleyi
Copy link
Owner Author

xxleyi commented May 26, 2019

再往深处走

参见:理解python的类实例化 - 简书
英文原文:Understanding Python Class Instantiation

# 需要看解释器源码。。。。
# 忽略错误检查等健壮性处理,对于常规类的实例化主要有如下过程:
def __call__(obj_type, *args, **kwargs):
    obj = obj_type.__new__(*args, **kwargs)
    if obj is not None and issubclass(obj, obj_type):
        obj.__init__(*args, **kwargs)
    return obj

# __new__ 方法为对象分配了内存空间,构建它为一个“空"对象然后 __init__ 方法被调用来初始化它。
# 总的来说:
# Foo(*args, **kwargs)等价于Foo.__call__(*args, **kwargs)
# 而 Foo 是一个 type 的实例,Foo.__call__(*args, **kwargs) 实际调用的是 type.__call__(Foo, *args, **kwargs)
# type.__call__(Foo, *args, **kwargs)调用type.__new__(Foo, *args, **kwargs),然后返回一个对象。
# obj随后通过调用obj.__init__(*args, **kwargs)被初始化。
# obj被返回。

值得注意的是,尽管__new__是一个静态方法,但你不需要用@staticmethod来声明它——它是Python解释器的特例。

@xxleyi
Copy link
Owner Author

xxleyi commented May 30, 2019

彻底而邪恶的单例:源自单类的单例

# do not print "enter call"
class SingletonType(type):
    _instances = {}
    _cls = None
    def __call__(cls, *args, **kwargs):
        print('enter call')
        if cls not in cls._instances:
            # 这里的 super 后面的 __call__ 无需使用 cls 作为参数
            # 因为是在继承 type 中 __call__ 方法
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

    def __new__(meta_cls, name, bases, dct):
        assert bases == ()
        if not meta_cls._cls:
            meta_cls._cls = super().__new__(meta_cls, name, bases, dct)
        else:
            for k, v in dct.items():
                setattr(meta_cls._cls, k, v)
        return meta_cls._cls

# do not print "enter call" either
class SingletonA(metaclass=SingletonType):
    def a():
        return 'a'

class SingletonB(metaclass=SingletonType):
    def b():
        return 'b'

a = SingletonA() # print "enter call"
b = SingletonB() # print "enter call" again
a is b  # True
SingletonA is SingletonB # True
SingletonA.b() # b
SingletonB.a() # a

@xxleyi
Copy link
Owner Author

xxleyi commented Aug 8, 2019

总结

  1. 使用 super() 容易混淆,因为静态方法和元类方法还不大一样。所以可以统一使用 object. 或者 type. 的显式形式,甚至于在元类中的实例字典中,也可以使用 SingleType._instances 的形式
  2. __new____call__ 需要注意区分。 __new__ 既可以用于类的创建,也可以用于实例的创建。而 __call__ 是在创建之后的调用。
  3. __new__ 用于创建类的实例时,只接受一个参数。

@xxleyi xxleyi mentioned this issue Aug 28, 2019
18 tasks
@xxleyi xxleyi added HOW and removed WHAT labels Mar 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant