1,鸭子类型和白鹅类型

1.1,白鹅类型

白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__reversed__,index,count。

对于其中的抽象方法,子类在继承时必须具体化,其余非抽象方法在继承时可以自动获得,Sequence序列必须具体化的抽象方法是__len__和__getitem__

from collections import abc

class Foo(abc.Sequence):

    def __init__(self, components):
self._components = components def __getitem__(self, item):
return self._components[item] def __len__(self):
return len(self._components)

Foo通过继承abc.Sequence,明确了自己的身份,明确了自己需要实现哪些方法,并且会自动获得哪些方法, 这样的正式继承即为白鹅类型。

Foo的实例就是正式的Sequence类了,可以通过isinstance检查,可以使用自己创建的抽象方法,可以使用继承抽象基类获得的方法:

f = Foo(list('abcde'))
print(isinstance(f, abc.Sequence)) # 结果True
print(f[0]) # 'a',__getitem__
print(len(f)) # 5,__len__
print('b' in f) # True,__contains __
for i in f: # __iter__
  print(i)
print(list(reversed(f))) # ['e', 'd', 'c', 'b', 'a'], __reversed__
print(f.count('a')) # 1, count
print(f.index('a')) # 0, index

1.2,鸭子类型

鸭子类型没有明确的接口,只是遵循了一定的协议,比如python序列协议只需要实现__len__和__getitem__方法,对于序列,这点鸭子类型和白鹅类型中Sequence抽象基类的要求相同,但白鹅类型Sequence继承后能够自动获得抽象基类的方法,而鸭子类型不会有这些方法:

class Foo:

    def __init__(self, components):
self._components = components def __getitem__(self, item):
return self._components[item] def __len__(self):
return len(self._components)

Foo自己实现的2个方法可以用:

print(f[0])   # 'a', __getitem__
print(len(f)) # 5, __len__

Foo自己没有实现的3个方法不可用:

print(list(reversed(f)))  # error
print(f.count('a')) # error
print(f.index('a')) # error

下面2个自己没实现的方法也可以用,为什么呢?原因是python发现某些特殊方法没有实现时,会自动尝试调用其他特殊方法:

print('b' in f)   # 可以用,python的in测试会依次尝试调用__contains__,__iter__,__getitem__
for i in f: # 可以用,python的迭代会依次尝试调用__iter__,__getitem__
  print(f)

另外鸭子类型的Foo,无法通过isinstance检查:

print(isinstance(f, abc.Sequence))   # 结果False

2,抽象基类

抽象基类的主要作用:1)作为超类定义接口;2)isinstance检查;3)注册:不子类化就声明实现接口;4)自动识别:不注册/不子类化就让抽象基类能够识别虚拟子类。

猴子补丁:运行时修改类,不改变源码,FrenchDeck.__setitem__ = set_card,例如给FrenchDeck类补上set_card()方法。

2.1,自己定义抽象基类 - abc

自定义抽象基类需要继承abc.ABC(不继承无法约束子类必须实现抽象方法),并且用@abc.abstractmethod装饰抽象方法,抽象基类无法实例化。

子类在基础抽象基类时,必须将抽象方法实现,否则无法实例化。

import abc

class Tombola(abc.ABC):     # 继承abc.ABC

  @abc.abstractmethod       # 声明抽象方法
  def load(self):
    """这里是注释"""   def inspect(self): # 非抽象方法
    pass

2.2,内置抽象基类 - collections.abc

Iterable:__iter__

Container:__contains__

Sized:__len__

Sequence:不可变序列

Mapping:不可变映射

Set:不可变集合

MappingView

Callable、Hashable

Iterator、Iterable

可以通过对抽象基类实例化,来查看强制要求实现的抽象方法:

>>> import collections
>>> collections.Sequence()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Sequence with abstract methods __getitem__, __len__

2.3,内置抽象基类 - numbers

Number:数基类

Complex:复数 = 实数 + 虚数

Real:实数 = 有理数 + 无理数

Rational:有理数 = 整数 + 分数

Intergral:整数

例如:判断浮点数可以用isinstance(obj, numbers.Real)

如果obj是bool,int,float,fractions.Fraction,或外部库(例如numpy)提供的非复数类型,都能返回True

用type(obj, float)则比较局限

2.4,内置抽象基类 — io

定义了IO处理相关的抽象基类

2.5,注册 — 让基类拥有虚拟子类

通过注册,可以不用继承,就建立基类和“虚拟子类”之间的关系。有二个关键点:

1)注册后,python会相信我们,直接让“虚拟子类”通过isinstance检查,也不会约束虚拟子类必须实现抽象方法。

2)“虚拟子类”不会从抽象基类获得任何方法和属性。

综上所述:为避免运行时出错,注册后我们应该手动让“虚拟子类”实现“基类”的所有方法。

注册方法一:装饰器(python3.3之后支持)

@Tombola.register   # 装饰器注册
class TomboList(list):
  def load(self):
    pass

注册方法二:标准调用语法

class TomboList(list):
  def load(self):
    pass Tombola.register(TomboList) # 标准调用语法注册

注册后,虚拟子类的实例化对象,就能够通过isinstance检查:

t = TomboList()
isintance(t, Tombola) # True

对象注册后,如果缺少某些方法尚未实现,会报错提示

2.6,自动识别

抽象基类有个方法__subclasshook__,通过判断其他类是否具有一些特殊方法(失败返回NotImplement让虚拟子类继续判断),来识别其他类是不是自己的“虚拟子类”,例如:

class Struggle:
  def __len__(self):
    pass
s = Struggle()
isintance(s, abc.Sized) # True

必须具有__subclasshook__的抽象基类才能提供该功能。

ps:自己建了个类,实现了collections.Sequence所有方法,但是无法通过collections.Sequence检查,collections.Sequence貌似没有实现该方法。

guxh的python笔记七:抽象基类的更多相关文章

  1. Python高级主题:Python ABC(抽象基类)

    #抽象类实例 作用统一规范接口,降低使用复杂度.import abcclass Animal(metaclass = abc.ABCMeta): ##只能被继承,不能实例化,实例化会报错 @abc.a ...

  2. Python中的抽象基类

    1.说在前头 "抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是&quo ...

  3. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  4. python(七):元类与抽象基类

    一.实例创建 在创建实例时,调用__new__方法和__init__方法,这两个方法在没有定义时,是自动调用了object来实现的.python3默认创建的类是继承了object. class A(o ...

  5. python cookbook第三版学习笔记十六:抽象基类

    假设一个工程中有多个类,每个类都通过__init__来初始化参数.但是可能有很多高度重复且样式相同的__init__.为了减少代码.我们可以将初始化数据结构的步骤归纳到一个单独的__init__函数中 ...

  6. 流畅的python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  7. python面对对象编程---------6:抽象基类

    抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...

  8. Python 接口:从协议到抽象基类

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...

  9. python之抽象基类

    抽象基类特点 1.不能够实例化 2.在这个基础的类中设定一些抽象的方法,所有继承这个抽象基类的类必须覆盖这个抽象基类里面的方法 思考 既然python中有鸭子类型,为什么还要使用抽象基类? 一是我们在 ...

随机推荐

  1. MVEAN_day05 Nexus私服对的搭建

    序言:为什么要搭建私服 因为在公司中我们是以团队进行开发的,不在是在自己的本地仓库中进行,我们需要连接公司远程仓库进行jar依赖.在公司的局域网中搭建的私服,然后开发人员连接这台计算机,进行团队开发. ...

  2. [js]js设计模式-修改原型

    参考 操作原型 - 给原型添加属性 - 方法1: Fn.prototype.sum=function{} - 方法2: Fn.prototype={} //constructor指向了Object的原 ...

  3. Android adt-bundle 开发环境的搭建_windows

    本文完全拷贝的是:  https://www.jb51.net/article/87953.htm  的文章仅仅用于自己保存学习消息. Android开发环境有三种方式,分别是JDK+SDK+Ecli ...

  4. python 判断两个ip是不是处于同一网段

    a_ip:10.10.15.100b_ip:10.10.15.101c_ip:10.10.10.100netmask:255.255.255.0 def numtobinary(num): binar ...

  5. 【2017-05-18】WebForm的Repeater控件及简单控件

    <%@ %> - 这里面写一些声明和引用的 <%  %> - 编写C#代码的 <%= %> - 往界面上输出一个变量的值 <%# Eval("属性名 ...

  6. 【Spark-core学习之四】 Spark任务提交

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk1.8 scala-2.10.4(依赖jdk1.8) spark ...

  7. vue mand-mobile按2.0文档默认安装的是1.6.8版本

    vue mand-mobile按2.0文档默认安装的是1.6.8版本 npm list mand-mobilebigbullmobile@1.0.0 E:\webcode\bigbullmobile` ...

  8. oracle数据库调整字段顺序

    oracle数据库调整字段顺序 https://blog.csdn.net/xiaobaixie/article/details/77892034

  9. 约定优于配置---Java的eclipse项目配置

    0.测试文件夹test (测试文件的文件夹和源文件夹src是并行的关系,且位于同一目录) 以后源文件.java文件放在src目录下,相应的单元测试文件放在同级别的test目录下,且内部路径要相同 1. ...

  10. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...