简单一句话,当一个类实现__call__方法时,这个类的实例就会变成可调用对象。

直接上测试代码

class ClassA:

    def __call__(self, *args, **kwargs):
print('call ClassA instance') if __name__ == '__main__':
# ClassA实现了__call__方法
a = ClassA()
'''
这个时候,ClassA的实例a,就变成可调用对象
调用a(),输出call ClassA instance,说明是调用了
__call__函数
'''
a()
# 其实a()等同于a.__call__(),它本质上就是后者的缩写
a.__call__()
# 判断是否可调用,输出True
print(callable(a))

注意,是这个类的实例变成可调用对象,类的实例变成可调用对象,类的实例变成可调用对象,而不是改变这个类的实例化行为。

那么,如果要改变一个类的被实例化行为呢?

当然要用上黑魔法元类了,因为类本身就是元类的实例,当我们在元类中定义__call__的函数时,会改变类的实例化行为(或者说被调用的行为?感觉类和函数的界限有些模糊了)。

利用元类和__call__,可以在不使用工厂函数的情况,轻松实现单例模式,同时保持不错的可读性。

(以下代码来自《Python Cookbook》,进行部分修改,同时注释部分为个人理解)。

完整源码:https://github.com/blackmatrix7/python-learning/blob/master/class_/singleton.py

先定义一个名为Singleton的元类,实现如下

class Singleton(type):

    def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs) # __call__ 是对于类实例有效,比如说Spam类,是type类的实例
def __call__(cls, *args, **kwargs):
print('Singleton __call__ running')
if cls.__instance is None:
'''
元类定义__call__方法,可以抢在类运行 __new__ 和 __init__ 之前执行,
也就是创建单例模式的前提,在类实例化前拦截掉。
type的__call__实际上是调用了type的__new__和__init__
'''
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
else:
return cls.__instance

使用元类创建类Spam

class Spam(metaclass=Singleton):

    def __new__(cls):
print('Spam __new__ running')
return super().__new__(cls) def __init__(self):
print('Spam __init__ running')

解释下上面的代码,在元类Singleton的__init__函数中,给类增加了一个叫__instance的类属性。

需要注意的是,__init__的第一个参数是cls,其实等同于我们平时在类中定义__init__的self,因为对于元类来说,类是它的实例。

之所以写成cls,是便于理解 cls.__instance = None 是给类属性 __instance 赋值为 None。

接着在元类Singleton中重写__call__方法,__call__会抢在类(元类的实例)执行__new__和__init__之前执行,也就为拦截类的实例化提供了可能。

在元类Singleton的__call__方法对类属性__instance进行判断,如果__instance为None,说明类还未进行实例化,那么调用元类的父类(元类是type的子类)type的__call__方法,同时赋值给 cls.__instance。如果 cls.__instance 不为None,说明类已经进行过实例化,直接返回之前存储在类属性cls.__instance 中的类实例,即实现单例模式。

测试代码:

if __name__ == '__main__':
a = Spam()
b = Spam()
print(a is b)
c = Spam()
print(a is c)

执行结果:

Singleton __call__ running
Spam __new__ running
Spam __init__ running
Singleton __call__ running
True
Singleton __call__ running
True

从运行结果上可以看出,每次尝试实例化Spam时,会被__call__函数拦截,所以会打印出:Singleton __call__ running

接着判断实例是否存在,在第一次运行时,实例不存在,创建类实例,并赋值给类属性__instance。

后续的几次尝试实例化Spam,因为Spam已经有实例存在,不在创建实例,实现了单例模式。

一个错误的例子

如果,我们把Spam的__new__改成下面这样,不返回任何结果,会有什么问题??

class Spam(metaclass=Singleton):

    def __new__(cls):
print('Spam __new__ running') def __init__(self):
print('Spam __init__ running')

还是执行之前的测试代码,得到下面的运行结果

Singleton __call__ running
Spam __new__ running
Singleton __call__ running
Spam __new__ running
True
Singleton __call__ running
Spam __new__ running
True

初看似乎没有什么问题,print(a is b) 和 print(a is c)都返回了True。

细心的话,会发现上面的输出结果有个很奇怪的问题,__init__方法是从未被执行!!!

对于此,官方文档是如此说明的

If __new__() does not return an instance of cls, then the new instance’s __init__() method will not be invoked.

当__new__函数没有返回这个类的实例时,__init__函数不会被调用。上面的示例函数,只是打印了文字,没有返回任何结果,所以不会执行__init__。

其实也容易理解,__init__需要类实例self参数,而__new__没有返回一个类实例,这样的话__init__自然无法运行。

但是,还有个问题,为什么每次Spam的__new__方法都会被运行??

因为 __new__ 不返回任何结果,那么__init__方法不会执行,实例从头到尾都没有被创建过(cls.__instance永远是None)。

在 执行 a = Spam() b = Spam() c = Spam()的过程中,每次cls.__instance为None,就调用type.__call__试图创建实例。

而每次执行到Spam的__new__方法时,会没有创建出任何实例,这样每次都不能成功创建实例。

所以,会有每次都执行Spam __new__ running的情况。

最后,因为 a、b、c 三个实例都是None,所以在做比较时,永远的True。

通过 python的 __call__ 函数与元类 实现单例模式的更多相关文章

  1. 【原创】Python 对象创建过程中元类, __new__, __call__, __init__ 的处理

    原始type: type是最原始的元类,其__call__方法是在你使用" t_class = type(classname_string, base_classes_tuple, attr ...

  2. 【python进阶】详解元类及其应用2

    前言 在上一篇文章[python进阶]详解元类及其应用1中,我们提到了关于元类的一些前置知识,介绍了类对象,动态创建类,使用type创建类,这一节我们将继续接着上文来讲~~~ 5.使⽤type创建带有 ...

  3. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  4. 深刻理解Python中的元类(metaclass)以及元类实现单例模式

    在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...

  5. Python元类(metaclass)以及元类实现单例模式

    这里将一篇写的非常好的文章基本照搬过来吧,这是一篇在Stack overflow上很热的帖子,我看http://blog.jobbole.com/21351/这篇博客对其进行了翻译. 一.理解类也是对 ...

  6. python随用随学-元类

    python中的一切都是对象 按着我的逻辑走: 首先接受一个公理,「python中的一切都是对象」.不要问为什么,吉大爷(Guido van Rossum,python之父)人当初就是这么设计的,不服 ...

  7. python面向对象( item系列,__enter__ 和__exit__,__call__方法,元类)

    python面向对象进阶(下)   item系列 __slots__方法 __next__ 和 __iter__实现迭代器  析构函数 上下文管理协议 元类一.item系列 把对象操作属性模拟成字典的 ...

  8. python 入门基础24 元类、单例模式

    内容目录: 一.元类 二.单例模式 一.元类 1 什么是元类: 源自一句话:在python中,一切皆对象,而对象都是由类实例化得到的 class OldboyTeacher: def __init__ ...

  9. Python学习_13_继承和元类

    继承 继承的含义就是子类继承父类的命名空间,子类中可以调用父类的属性和方法,由于命名空间的查找方式,当子类中定义和父类同名属性或者方法时,子类的实例调用的是子类中的属性,而不是父类,这就形成了pyth ...

随机推荐

  1. Spring Cloud 前后端分离后引起的跨域访问解决方案

    背景 Spring Cloud 微服务试点改造,目前在尝试前后端分离. 前台A应用(本机8080端口),通过网管(本机8769端口)调用后台应用B(本机8082端口).应用C发布的http服务.. A ...

  2. 一个模型中有两个外键指向同一张表时,创建迁移模型时报错:“ HINT: Add or change a related_name argument to the definition for 'AnswersModel.author' or 'AnswersModel.relay_to'.”解决方案

    class AnswersModel(models.Model): author = models.ForeignKey(FrontUserModel,null=True,related_name=' ...

  3. 如何用shell脚本取出服务器图片

    一 ,SHELL 是什么 (1)shell是一种命令行解释器. (2)是用户和Linux内核之间沟通的桥梁,属于中间件.见下图 (3)交互流程:shell接受用户输入的指令 =>将指令传达给Li ...

  4. java集合框架07——Map架构与源代码分析

    前几节我们对Collection以及Collection中的List部分进行了分析,Collection中还有个Set,因为Set是基于Map实现的,所以这里我们先分析Map,后面章节再继续学习Set ...

  5. LeetCode90:Subsets II

    Given a collection of integers that might contain duplicates, nums, return all possible subsets. Not ...

  6. 25个增强iOS应用程序性能的提示和技巧(0基础篇)

    在开发iOS应用程序时,让程序具有良好的性能是非常关键的. 这也是用户所期望的,假设你的程序执行迟钝或缓慢,会招致用户的差评.然而因为iOS设备的局限性,有时候要想获得良好的性能,是非常困难的. 在开 ...

  7. JAVA-UML

    UML(UnifiedModelingLanguage)(统一建模语言或标准建模语言) 它是模型化的软件系统开发图形语言 为软件开发所有阶段提供模型化,可视化支持 UM2.2中定义了14中图示 三种常 ...

  8. 腾讯云实力通过工信部测评,获全国范围CDN经营许可

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 近日,腾讯云提前完成了全国范围的CDN资质测评,顺利获得工信部颁发的CDN业务全国范围的经营许可证. 2017年1月份,工信部发布<关于 ...

  9. 获取本地ip

    public void GetLocalIP(string username) { List<string> strIPs = new List<string>(); Netw ...

  10. 基于.NET的弹性及瞬间错误处理库Polly

    本文基本是官方说明的翻译和总结(https://github.com/App-vNext/Polly) 什么是Polly? Polly是一款基于.NET的弹性及瞬间错误处理库, 它允许开发人员以顺畅及 ...