• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell
您的位置:首页 > 脚本语言 >python > python 接口_从协议到抽象基类详解

python 接口_从协议到抽象基类详解

作者: 字体:[增加 减小] 来源:互联网

通过本文主要向大家介绍了python,抽象基类等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

抽象基类的常见用途:实现接口时作为超类使用。然后,说明抽象基类如何检查具体子类是否符合接口定义,以及如何使用注册机制声明一个类实现了某个接口,而不进行子类化操作。最后,说明如何让抽象基类自动“识别”任何符合接口的类——不进行子类化或注册。

Python文化中的接口和协议

接口在动态类型语言中是怎么运作的呢?首先,基本的事实是,Python语言没有 interface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如__getitem__ 或 __add__。

按照定义,受保护的属性和私有属性不在接口中:即便“受保护的”属性也只是采用命名约定实现的(单个前导下划线);私有属性可以轻松地访问,原因也是如此。不要违背这些约定。

另一方面,不要觉得把公开数据属性放入对象的接口中不妥,因为如果需要,总能实现读值方法和设值方法,把数据属性变成特性,使用obj.attr 句法的客户代码不会受到影响。 Vector2d 类就是这么做的,Vector2d 类的第一版,x 和 y 是公开属性。

vector2d_v0.py:x 和 y 是公开数据属性

class Vector2d:

 def __init__(self, x, y):
 self.x = x
 self.y = y

 def __iter__(self):
 return (n for n in (self.x, self.y))

我们把 x 和 y 变成了只读特性。这是一项重大重构,但是 Vector2d 的接口基本没变:用户仍能读取my_vector.x 和 my_vector.y。

class Vector2d:

 def __init__(self, x, y):
 self.__x = x
 self.__y = y

 @property
 def x(self):
 return self.__x

 @property
 def y(self):
 return self.__y

 def __iter__(self):
 return (i for i in (self.x, self.y))

Python喜欢序列

Python 数据模型的哲学是尽量支持基本协议。对序列来说,即便是最简单的实现,Python 也会力求做到最好。

下图展示了定义为抽象基类的 Sequence 正式接口。

Sequence 抽象基类和 collections.abc 中相关抽象类的UML 类图,箭头由子类指向超类,以斜体显示的是抽象方法

现在,看看下面??中的 Foo 类。它没有继承 abc.Sequence,而且只实现了序列协议的一个方法: __getitem__ (没有实现 __len__ 方法)。

>>> class Foo:
...   def __getitem__(self, pos):
...     return range(0, 30, 10)[pos]
...
>>> f = Foo()
>>> f[1]

>>> for i in f: print(i)
...



>>> 20 in f
True
>>> 15 in f
False

虽然没有 __iter__ 方法,但是 Foo 实例是可迭代的对象,因为发现有__getitem__ 方法时,Python 会调用它,传入从 0 开始的整数索引,尝试迭代对象(这是一种后备机制)。尽管没有实现 __contains__ 方法,但是 Python 足够智能,能迭代 Foo 实例,因此也能使用 in 运算符:Python 会做全面检查,看看有没有指定的元素。

综上,鉴于序列协议的重要性,如果没有 __iter__ 和 __contains__方法,Python 会调用 __getitem__ 方法,设法让迭代和 in 运算符可用。

下面的FrenchDeck 类也没有继承 abc.Sequence,但是实现了序列协议的两个方法:__getitem__ 和 __len__。

class FrenchDeck:
 ranks = [str(n) for n in range(2, 11)] + list('JQKA')
 suits = 'spades diamonds clubs hearts'.split()

 def __init__(self):
 self._cards = [Cards(rank, suit) for suit in self.suits
      for rank in self.ranks]

 def __len__(self):
 return len(self._cards)

 def __getitem__(self, pos):
 return self._cards[pos]

使用猴子补丁在运行时实现协议

FrenchDeck 类有个重大缺陷:无法洗牌。如果 FrenchDeck 实例的行为像序列,那么它就不需要 shuffle 方法,因为已经有 random.shuffle 函数可用,文档中说它的作用是“就地打乱序x”(https://docs.python.org/3/library/random.html#random.shuffle)。

标准库中的 random.shuffle 函数用法如下:

>>> from random import shuffle
>>> my_list = list(range(1, 11))
>>> shuffle(my_list)
>>> my_list
[5, 7, 9, 2, 10, 1, 8, 6, 4, 3]

然而,如果尝试打乱 FrenchDeck 实例,会出现异常,如下面的 ?? 所示。

Traceback (most recent call last):
 File "/Users/demon/PycharmProjects/FluentPython/接口:从协议到抽象基类/FrenchDeck.py", line 37, in <module>
 shuffle(deck)
 File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/random.py", line 274, in shuffle
 x[i], x[j] = x[j], x[i]
TypeError: 'FrenchDeck' object does not support item assignment  

错误消息相当明确,“'FrenchDeck' object does not support itemassignment”('FrenchDeck' 对象不支持为元素赋值)。这个问题的原因是,shuffle 函数要调换集合中元素的位置,而 FrenchDeck 只实现了不可变的序列协议。可变的序列还必须提供 __setitem__ 方法。

解决办法为FrenchDeck 打猴子补丁,把它变成可变的,让random.shuffle 函数能处理

from collections import namedtuple
from random import shuffle

Cards = namedtuple('Cards', ['rank', 'suit'])

class FrenchDeck:
 ranks = [str(n) for n in range(2, 11)] + list('JQKA')
 suits = 'spades diamonds clubs hearts'.split()

 def __init__(self):
 self._cards = [Cards(rank, suit) for suit in self.suits
      for rank in self.ranks]

 def __len__(self):   #获取长度的len
 return len(self._cards)

 def __getitem__(self, position): #能够支持切片取值的处理
 return self._cards[position]


deck = FrenchDeck()    #实例化

def set_card(deck, position, card):  #猴子不定,其实就是启动调用内部__setitem__设置值得外部函数
 deck._cards[position] = card

FrenchDeck.__setitem__ = set_card  #把猴子补丁传递给内部的魔术方法
shuffle(deck)    #打乱洗牌的序列
print(deck[:5])    #获取前五个卡牌的值

以上代码的执行结果为:

[Cards(rank='K', suit='diamonds'), Cards(rank='4', suit='spades'), Cards(rank='A', suit='clubs'), Cards(rank='K', suit='spades'), Cards(rank='6', suit='clubs')]  

这里的关键是,set_card 函数要知道 deck 对象有一个名为 _cards 的属性,而且 _cards 的值必须是可变序列。然后,我们把 set_card 函数赋值给特殊方法 __setitem__,从而把它依附到 FrenchDeck 类上。这种技术叫猴子补丁:在运行时修改类或模块,而不改动源码。猴子补丁很强大,但是打补丁的代码与要打补丁的程序耦合十分紧密,而且往往要处理隐藏和没有文档的部分。

定义抽象基类的子类

在下面的?? 中,我们明确把 FrenchDeck2 声明为collections.MutableSequence 的子类。

frenchdeck2.py:FrenchDeck2,collections.MutableSequence的子类

import collections


Card = collections.namedtuple('Card', ['rank', 'suit'])


class FrenchDeck2(collections.MutableSequence):
 ranks = [str(i) for i in range(1, 11)] + list('JQKA')
 suits = 'spades diamonds clubs hearts'.split()

 def __init__(self):
 self._cards = [Card(rank, suit) for suit in self.suits
     for rank in self.ranks]

 def __len__(self):    #支持查看长度
 return len(self._cards)

 def __getitem__(self, position):  #支持切片
 return self._cards[position]

 def __setitem__(self, position, value):  #支持洗牌
 self._cards[position] = value

 def __delitem__(self, position):  #但是继承MutableSequence的类必须实现 __delitem__ 方法,这是MutableSequence 类的一个抽象方法。
 del self._cards[position]

 def insert(self, position, value):  #此外,还要实现insert方法,这是MutableSequence类的第三个抽象方法
 self._cards.insert(position, value)

Sequence 和 MutableSequence 抽象基类的方法不全是抽象的。

MutableSequence 抽象基类和 collections.abc 中它的超类的 UML 类图(箭头由子类指向祖先;以斜体显示的名称是抽象类和抽象方法)

FrenchDeck2 从 Sequence 继承了几个拿来即用的具体方法__contains__、__iter__、__reversed__、index 和count。FrenchDeck2 从MutableSequence 继

您可能想查找下面的文章:

  • Python底层封装实现方法详解
  • Python实现随机生成任意数量车牌号
  • 使用Python实现Wake On Lan远程开机功能
  • python动态文本进度条的实例代码
  • python基于property()函数定义属性
  • Python 常用的安装Module方式汇总
  • python中OrderedDict的使用方法详解
  • Python编程生成随机用户名及密码的方法示例
  • Python实现统计文本文件字数的方法
  • Python部署web开发程序的几种方法

相关文章

  • python中的yield使用方法
  • Python中zfill()方法的使用教程
  • Python实现一个简单的MySQL类
  • Python2.x中str与unicode相关问题的解决方法
  • python连接mysql数据库示例(做增删改操作)
  • 用Python编写一个基于终端的实现翻译的脚本
  • python模块简介之有序字典(OrderedDict)
  • python正则匹配查询港澳通行证办理进度示例分享
  • Python 调用DLL操作抄表机
  • Python 提取dict转换为xml/json/table并输出的实现代码

文章分类

  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell

最近更新的内容

    • 详解Python Socket网络编程
    • python轻松实现代码编码格式转换
    • python基础教程之常用运算符
    • Python 变量类型及命名规则介绍
    • Python中列表(list)操作方法汇总
    • Python的Flask开发框架简单上手笔记
    • Python学习资料
    • 在Python的web框架中中编写日志列表的教程
    • TensorFlow实现打印每一层的输出
    • Python程序中的观察者模式结构编写示例

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有