楔子

下面我们说几个魔法方法,这几个魔法方法比较特殊,因为我们不经常用。但是相信你在看完之后,能够对python的类有更深刻的理解。下面我们就来介绍一下__instancecheck____subclasscheck____subclasshook__这几个魔法方法。

__instancecheck__

__instancecheck__是专门用于isinstance函数,检测一个实例对象是否属于某个类的实例。但是注意:这个方法一定要定义在元类当中,比如isinstance(obj, A),实际上会调用type(A)__instancecheck__方法,而A是一个类,那么type(A)不就是一个元类吗?我们举个例子。

  1. class A:
  2. def __instancecheck__(self, instance):
  3. print("__instancecheck__被调用")
  4. return True
  5. print(isinstance(123, A)) # False
  6. # 上面打印了False,很正常,因为123显然不是A的实例对象。
  7. # 虽然A中定义了__instancecheck__,但是没用,因为调用的是type(A)的__instancecheck__
  8. # 于是聪明如你可能想到了
  9. print(isinstance(123, A()))
  10. """
  11. __instancecheck__被调用
  12. True
  13. """
  14. # 如果我们将A改成A()不就行了吗,这样的话会调用type(A())、也就是A的__instancecheck__
  15. # 确实如此,但这没有什么意义。
  16. # 而且事实上isinstance的第二个参数不可以是实例对象,否则报错
  17. try:
  18. isinstance(123, object())
  19. except Exception as e:
  20. # 告诉我们isinstance的第二个参数必须是一个类,或者是一个包含的多个类的元组
  21. print(e) # isinstance() arg 2 must be a type or tuple of types
  22. # 而我们上面的isinstance(123, A())之所以没有报错,就是因为我们内部定义了__instancecheck__
  23. # 如果没有定义这个魔法方法,那么也会报出同样的错误

因此__instancecheck__这个魔法方法是要定义在元类当中,尽管定义在普通的类里面也可以使用,但是没有什么意义

  1. class MyType(type):
  2. def __instancecheck__(self, instance):
  3. # 当我们调用isinstance(obj, cls)的时候
  4. # 那么这个obj就会传递到这里的instance参数,前提是cls这个类是由这里的MyType实例化得到的
  5. if hasattr(instance, "hanser"):
  6. # 如果instance内部有hanser这个属性或者方法的话,返回True
  7. return True
  8. # 否则返回False
  9. return False
  10. class A(metaclass=MyType):
  11. pass
  12. # 整型显然没有hanser这个属性或方法,所以是False
  13. print(isinstance(123, A)) # False
  14. from flask import Flask
  15. print(isinstance(Flask(__name__), A)) # False
  16. setattr(Flask, "hanser", "xxx")
  17. print(isinstance(Flask(__name__), A)) # True
  18. """
  19. 一开始Flask内部没有hanser这个属性或方法,所以isinstance(Flask(__name__), A)为False
  20. 但是我们通过setattr设置一个名为hanser的属性,所以再次执行isinstance(Flask(__name__), A),返回True
  21. """
  22. # 尽管Flask(__name__)是A的实例对象,但是Flask并不是A的子类
  23. print(issubclass(Flask, A)) # False
  24. # 之所以说这一点,是为了和后面的两个魔法方法作区分

__subclasscheck__

__subclasscheck__这个不用想,肯定是用于issubclass。这个内置函数不用我多说,接收两个类,判断一个类是不是另一个类的子类。但是这个方法同样需要定义在元类里面才有意义

  1. class MyType(type):
  2. def __subclasscheck__(self, subclass):
  3. # 当调用issubclass(cls1, cls2)的时候,cls1就会传递给这里的subclass
  4. # 但前提是cls2的元类是这里的MyType
  5. if hasattr(subclass, "hanser"):
  6. # 如果subclass内部有hanser这个属性或者方法的话,返回True
  7. return True
  8. # 否则返回False
  9. return False
  10. class A(metaclass=MyType):
  11. pass
  12. from flask import Flask
  13. print(issubclass(Flask, A)) # False
  14. setattr(Flask, "hanser", "xxx")
  15. print(issubclass(Flask, A)) # True
  16. # 原因无需再解释,和isinstance类似
  17. print(isinstance(Flask(__name__), A)) # False
  18. # 但此时Flask(__name__)不是A的实例对象

如果我们不定义在元类中,看看会怎么样

  1. class A:
  2. def __subclasscheck__(self, subclass):
  3. # 全部返回True
  4. return True
  5. # 惊了,object居然是A的实例对象的子类。
  6. print(issubclass(object, A())) # True
  7. # A的实例对象压根就不是一个类,它居然摇身一变,成为了python中万物之父的类object的父类
  8. # 究其原因就是因为A内部定义了__subclasscheck__,issubclass(object, A())的时候,会调用A的__subclasscheck__方法

无论是__instancecheck__,还是__subclasscheck__,它们都应该定义在元类里面,而不是类里面。如果定义在类里面,那么要想使这两个魔法方法生效,那么就必须使用该类的实例对象。而isinstance和issubclass的第二个参数接收的都是类(或者包含多个类的元组),我们传入实例对象理论上是会报错的,只不过生成该实例对象的类里面定义了相应的魔法方法,所以才不会报错。但即便如此,我们也不要这么做,因为这样没有什么意义。而且,如果你用的是pycharm这种智能的编辑器的话,也会给你标黄

所以我们不要传入一个实例对象,也就是不要将这两个魔法方法定义在普通的类中。而是要定义在继承自type的类中,也就是元类。

__subclasshook__

上面那两个魔法方法是属于预定义的,需要定义在元类中。但是__subclasshook__不是,它是定义在抽象基类中。

我们可以去模块collections.abc(或者直接去_collections_abc)中看一下,里面定义了大量的抽象基类。比如Iterable、Sized、Container等等一大堆

  1. class Iterable(metaclass=ABCMeta):
  2. __slots__ = ()
  3. # 如果想要继承Iterable,那么必须实现__iter__方法
  4. @abstractmethod
  5. def __iter__(self):
  6. while False:
  7. yield None
  8. @classmethod
  9. def __subclasshook__(cls, C):
  10. # 重点来了,当我们调用issubclass(cls, Iterable)的时候
  11. # 那么cls会传递给这里的C,注意这个方法是一个类方法,__subclasshook__里面cls指的是Iterable本身
  12. # 而我们在调用issubclass(cls, Iterable)的时候,cls会传给这里的C
  13. if cls is Iterable:
  14. return _check_methods(C, "__iter__")
  15. return NotImplemented
  16. class Sized(metaclass=ABCMeta):
  17. __slots__ = ()
  18. # Sized,可以使用len方法的,那么内部必须实现__len__
  19. @abstractmethod
  20. def __len__(self):
  21. return 0
  22. @classmethod
  23. def __subclasshook__(cls, C):
  24. # 和Iterable类似
  25. if cls is Sized:
  26. return _check_methods(C, "__len__")
  27. return NotImplemented
  28. class Container(metaclass=ABCMeta):
  29. __slots__ = ()
  30. # 容器,内部必须实现__contains__方法,换句话说就是可以使用in
  31. # 比如:if 1 in [1, 2, 3] 等价于 if [1, 2, 3].__contains__(1)
  32. @abstractmethod
  33. def __contains__(self, x):
  34. return False
  35. @classmethod
  36. def __subclasshook__(cls, C):
  37. if cls is Container:
  38. return _check_methods(C, "__contains__")
  39. return NotImplemented

所以关键就在于这个__subclasshook__,下面我们就可以自己实现了

  1. from abc import ABCMeta
  2. class A(metaclass=ABCMeta):
  3. @classmethod
  4. def __subclasshook__(cls, C):
  5. if hasattr(C, "hanser"):
  6. return True
  7. return False
  8. from flask import Flask
  9. setattr(Flask, "hanser", "xxx")
  10. print(isinstance(Flask(__name__), A)) # True
  11. print(issubclass(Flask, A)) # True

我们看到如果定义了__subclasshook__,那么会同时作用于isinstance和issubclass。而__instancecheck__只作用于isinstance函数,__subclasscheck__只作用于issubclass函数。

并且我们还可以进行继承

  1. from abc import ABCMeta
  2. class A(metaclass=ABCMeta):
  3. @classmethod
  4. def __subclasshook__(cls, C):
  5. if hasattr(C, "hanser"):
  6. return True
  7. return False
  8. class B(A):
  9. pass
  10. from flask import Flask
  11. setattr(Flask, "hanser", "xxx")
  12. print(isinstance(Flask(__name__), B)) # True
  13. print(issubclass(Flask, B)) # True

以上就简单的介绍了它们的用法,或者说逻辑。至于怎么在项目中使用,就看你自己的啦。

剖析isinstance的实现机制的更多相关文章

  1. 剖析Qt的事件机制原理

    版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消息循环和WinMai ...

  2. 深度剖析JDK动态代理机制

    摘要 相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象. 代理模式 使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过 ...

  3. [转][MVC] 剖析 NopCommerce 的 Theme 机制

    本文转自:http://www.cnblogs.com/coolite/archive/2012/12/28/NopTheme.html?utm_source=tuicool&utm_medi ...

  4. 剖析MapReduce 作业运行机制

    包含四个独立的实体: ·  Client Node 客户端:编写 MapReduce代码,配置作业,提交MapReduce作业. ·  JobTracker :初始化作业,分配作业,与 TaskTra ...

  5. 通过库函数API和C代码中嵌入汇编代码剖析系统调用的工作机制

    作者:吴乐 山东师范大学<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 本次实验的主要内容就是分别采用A ...

  6. 剖析ECMALL的登录机制

    在ecmall.php文件中实例化控制器类,每一个控制器类,必须继承(extends)upload\admin\app\backend.base.php文件.在继承中调用方法是谁先被继承谁的方法被先调 ...

  7. 深入剖析tomcat的类加载机制

    1JVM类加载机制 JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构.其中引导类.扩展类.系统类三个加载器是JVM内置的. 它们的作用分别是: 1)引导类加载器:使用n ...

  8. 源码剖析Linux epoll实现机制及Linux上惊群

    转载:https://blog.csdn.net/tgxallen/article/details/78086360 看源码是对一个技术认识最直接且最有效的方式了,之前用Linux Epoll做过一个 ...

  9. 【转】简易剖析Hadoop作业工作机制

    原文地址:https://www.cnblogs.com/duma/p/10666269.html 建议:结合第四版Hadoop权威指南阅读,更有利于理解 运行机制 运行一个 MR 程序主要涉及以下 ...

随机推荐

  1. CentOS8 安装 simple-scan 的方法

    CentOS8删除了很多软件包,解决的思路一般是从CentOS7或EPEL7的软件仓库中寻找,并解决依赖关系. 比如找到EPEL7中有 simple-scan 软件包,但安装时发现其又依赖 gnome ...

  2. 使用Visual Studio Code Coverage和nunit上传单元测试覆盖率和单元测试结果到SonarQube上

    SonarQube.Scanner.MSBuild.exe begin /k:"OMDCCQuotes" /d:sonar.host.url="http://myip:9 ...

  3. matlab作业之m文件的建立与使用运行

    画曲线y=xsin(ex-x),要求编写m文件,qx1.m,要求有标注 实现方法: 打开matlab,点击左上角新建 然后打开编辑器 在编辑器里输入函数语句 ctrl+s保存,这里的名字要命名成××× ...

  4. 用Blackbox Exporter to Monitor web和端口

    1.按照exporter .wget https://github.com/prometheus/blackbox_exporter/releases/download/v0.12.0/blackbo ...

  5. mysql数据库之索引与慢查询优化

    索引与慢查询优化 知识回顾:数据都是存在硬盘上的,那查询数据不可避免的需要进行IO操作 索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构. primary key unique ...

  6. day36 joinablequeue、多线程理论、多线程的两种使用方式、守护线程、互斥锁、死锁、递归锁、信号量

    1.joinablequeue队列 joinablequeue与queue一样,也是一种队列,其继承自queue,也有queue中的put 与get 方法,但是在joinablequeue中有自己的 ...

  7. 关于MYSQL安装踩的坑

    前提:本人装的版本是mysql-8.0.18-winx64,win10系统,如果你安装的是其他版本的MYSQL,语法会跟下面有些许区别: 一,安装 https://dev.mysql.com/down ...

  8. oracle报:ORA-01034和ORA-27101的解决办法

    出现ORA-01034和ORA-27101的原因是多方面的:主要是oracle当前的服务不可用,shared memory realm does not exist,是因为oracle没有启动或没有正 ...

  9. mybatis-plus 错误 java.lang.NoClassDefFoundError

    错误 java.lang.NoClassDefFoundError: org/apache/velocity/context/Context 使用mybatis-plus自动生成文件的时候,报下面的错 ...

  10. 基于 CentOS 7 搭建 SVN

    ⒈安装 SVN 服务端 1.安装 Subversion Subversion 是一个版本控制系统,相对于的 RCS . CVS ,采用了分支管理系统,它的设计目标就是取代 CVS . yum inst ...