python 数据模型

点击上方“算法猿的成长“,关注公众号,选择加“星标“或“置顶

总第 129 篇文章,本文大约  4500 字,阅读大约需要 15 分钟

最近开始阅读《流畅的python》,也会开始更新这本书的学习笔记

第一篇的内容是第一章 python 数据模型,主要是介绍 python 类中的特殊方法(或者说魔术方法),这类特殊方法的实现可以让我们自定义的类对象能够使用 python 标准库的方法,同时也有助于接口方法的一致性。

本文的代码例子:

https://github.com/ccc013/CodesNotes/blob/master/FluentPython/1_Python%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B.ipynb


前言

数据模型其实是对 Python 框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列、迭代器、函数、类和上下文管理器。

通常在不同框架下写程序,都需要花时间来实现那些会被框架调用的方法,python 当然也包含这些方法,当 python 解释器碰到特殊的句法的时候,会使用特殊方法来激活一些基本的对象操作,这种特殊方法,也叫做魔术方法(magic method),通常以两个下划线开头和结尾,比如最常见的 __init__, __len__ 以及 __getitem__ 等,而 obj[key] 这样的操作背后的特殊方法是 __getitem__,初始化一个类示例的时候,如 obj= Obj() 的操作背后,特殊方法就是 __init__

通过实现 python 的这些特殊方法,可以让自定义的对象实现和支持下面的操作:

  • 迭代

  • 集合类

  • 属性访问

  • 运算符重载

  • 函数和方法的调用

  • 对象的创建和销毁

  • 字符串表示形式和格式化

  • 管理上下文(也就是 with 块)

一摞 Python 风格的纸牌

接下来尝试自定义一个类,并实现两个特殊方法:__getitem____len__ ,看看实现它们后,可以对自定义的类示例实现哪些操作。

这里自定义一个纸牌类,并定义了数字和花色,代码如下所示:

import collections
# 用 nametuple 构建一个类来表示纸牌
Card = collections.namedtuple('Card', ['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 = [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]

其中辅助用到 collections 库的 nametuple ,用来表示一张纸牌,其属性包括数字 rank 和 花色 suit ,下面是对这个 Card 的简单测试:

# 测试 Card
beer_card = Card('7', 'diamonds')
beer_card

接着就是测试自定义的 FrenchDeck 类,这里会调用 len() 方法看看一摞纸牌有多少张:

# 测试 FrenchDeck
deck = FrenchDeck()
len(deck)

然后是进行索引访问的操作,这里测试从正序访问第一张,以及最后一张纸牌的操作:

print(deck[0], deck[-1])

如果想进行随机抽取卡牌,可以结合 random.choice 来实现:

# 随机抽取,结合 random.choice
from random import choicechoice(deck)

由于我们实现 __getitem__  方法是获取纸牌,所以也可以支持切片(slicing)的操作,例子如下所示:

# 切片
print(deck[:3])
print(deck[12::13])

另外,实现 __getitem__ 方法就可以支持迭代操作:

# 可迭代的读取
for card in deck:print(card)

反向迭代也自然可以做到:

# 反向迭代
for card in reversed(deck):print(card)break

另外,当然也可以自定义排序规则,如下所示:

# 制定排序的规则
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)def spades_high(card):rank_value = FrenchDeck.ranks.index(card.rank)return rank_value * len(suit_values) + suit_values[card.suit]# 对卡牌进行升序排序
for card in sorted(deck, key=spades_high):print(card)

总结一下,实现 python 的特殊方法的好处包括:

  • 统一方面的名称,如果有别人采用你自定义的类,不用花更多精力记住不同的名称,比如获取数量都是 len() 方法,而不会是 size 或者 length

  • 可以更加方便利用 python 的各种标准库,比如 random.choicereversedsorted ,不需要自己重新发明轮子

如何使用特殊方法

这里分两种情况来说明对于特殊方法的调用:

  1. python 内置的类型:比如列表(list)、字典(dict)等,那么 CPython 会抄近路,即 __len__ 实际上会直接返回 PyVarObject 里的 ob_size 属性。PyVarObject 是表示内存中长度可变的内置对象的 C 语言结构体,直接读取这个值比调用一个方法要快很多

  2. 自定义的类:通过内置函数(如 len, iter, str 等)调用特殊方法是最好的选择。

对于特殊方法的调用,这里还要补充说明几点:

  • 特殊方法的存在是为了被 Python 解释器调用的。我们不需要调用它们,即不需要这么写 my_object.__len__(),而应该是 len(my_object),这里的 my_object 表示一个自定义类的对象。

  • 通常对于特殊方法的调用都是隐式的。比如 for i in x 循环语句是用 iter(x) ,也就是调用 x.__iter__()  方法。

  • 除非有大量元编程存在,否则都不需要直接使用特殊方法;

接下来是实现一个自定义的二维向量类,然后自定义加号的特殊方法,实现运算符重载。

代码例子如下所示:

# 一个简单的二维向量类
from math import hypotclass Vector:def __init__(self, x=0, y=0):self.x = xself.y = ydef __repr__(self):return 'Vector(%r, %r)' % (self.x, self.y)def __abs__(self):return hypot(self.x, self.y)def __bool__(self):return bool(abs(self))def __add__(self, other):x = self.x + other.xy = self.y + other.yreturn Vector(x, y)def __mul__(self, scalar):return Vector(self.x * scalar, self.y * scalar)

这里除了必须实现的 __init__外,还实现了几个特殊方法:

  • __add__: 加法运算符;

  • __bool__ :用于判断是否真假,也就是在调用bool() 方法;默认情况下是自定义类的实例总是被认为是真的,但如果实现了 __bool__或者 __len__ ,则会返回它们的结果,bool()首先尝试返回 __bool__ 方法,如果没有实现,则会尝试调用 __len__ 方法

  • __mul__ :实现的是标量乘法,即向量和数的乘法;

  • __abs__ :如果输入是整数或者浮点数,返回输入值的绝对值;如果输入的是复数,返回这个复数的模;如果是输入向量,返回的是它的模;

  • __repr__ : 可以将对象用字符串的形式表达出来;

这里要简单介绍下 __repr____str__ 两个方法的区别:

  • __repr__ :交互式控制台、调试程序(debugger)、%str.format 方法都会调用这个方法来获取字符串形式;

  • __str__ :主要是在 str()print() 方法中会调用该方法,它返回的字符串会对终端用户更加友好;

  • 如果只想实现其中一个方法,__repr__  是更好的选择,因为默认会调用 __repr__ 方法。

接下来就是简单测试这个类,测试结果如下所示:

特殊方法一览

下面分别根据是否和运算符相关分为两类的特殊方法:

和运算符无关的特殊方法

类别方法名
字符串/字节序列表现形式__repr__, __str__,__format__,__bytes__
数值转换__abs__,__bool__,__complex__,__int__,__float__,__hash__,__index__
集合模拟__len__,__getitem__,__setitem__,__delitem__,__contains__
迭代枚举__iter__,__reversed__,__next__
可调用模拟__call__
上下文管理__enter__, __exit__
实例创建和销毁__new__,__init__,__del__
属性管理__getattr__,__getattribute__,__setattr__,__delattr__,__dir__
属性描述符__get__,__set__,__delete__
跟类相关的服务__prepare__,__instancecheck__,__subclasscheck__

和运算符相关的特殊方法

类别方法名和对应的运算符
一元运算符__neg__ -, __pos__ +,__abs__ abs()
众多比较运算符__lt__ <, __le__ <=, __eq__ ==, __ne__ !=, __gt__ >, __ge__ >=
算术运算符__add__ +, __sub__ -, __mul__ *, __truediv__ /, __floordiv__ //, __mod__ %, __divmod__ divmod(), __pow__ **或者pow(), __round__ round()
反向算法运算符__radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__
增量赋值算术运算符__iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__, __imod__, __ipow__
位运算符__invert__ ~, __lshift__ <<, __rshift__ >>, __and__ &, __or__ |, __xor__ ^
反向位运算符__rlshift__, __rrshift__, __rand__, __rxor__, __ror__
增量赋值位运算符__ilshift__, __irshift__, __iand__, __ixor__, __ior__

这里有两类运算符要解释一下:

  • 反向运算符:交换两个操作数的位置的时候会调用反向运算符,比如 b * a 而不是 a * b

  • 增量赋值运算符:把一种中缀运算符变成赋值运算的捷径,即是 a *= b 的操作

为什么 len 不是普通方法

len 之所以不是普通方法,是为了让 Python 自带的数据结构变得高效,前面也提到内置类型在使用 len 方法的时候,CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法,因此速度会非常快。而在 python 的内置类型,比如列表 list、字符串 str、字典 dict 等查询数量是非常常见的操作。

这种处理方式实际上是在保持内置类型的效率和保证语言的一致性之间找到一个平衡点。


小结

本文介绍了两个代码例子,说明了在自定义类的时候,实现特殊方法,可以实现和内置类型(比如列表、字典、字符串等)一样的操作,包括实现迭代、运算符重载、打印类实例对象等,然后还根据是否和运算符相关将特殊方法分为两类,并列举出来了,最后也介绍了 len 方法的例子来说明 python 团队是如何保持内置类型的效率和保证语言一致性的。


精选AI文章

1. 10个实用的机器学习建议

2. 深度学习算法简要综述(上)

3. 深度学习算法简要综述(上)

4. 常见的数据增强项目和论文介绍

5. 实战|手把手教你训练一个基于Keras的多标签图像分类器

精选python文章

1. Python 基础入门--简介和环境配置

2. python 学习资源推荐

3. 快速入门 Jupyter notebook

4. Jupyter 进阶教程

5. 10个高效的pandas技巧

精选教程资源文章

1. [资源分享] TensorFlow 官方中文版教程来了

2. [资源]推荐一些Python书籍和教程,入门和进阶的都有!

3. [Github项目推荐] 推荐三个助你更好利用Github的工具

4. Github上的各大高校资料以及国外公开课视频

5. GitHub上有哪些比较好的计算机视觉/机器视觉的项目?

欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

 

如果觉得不错,在看、转发就是对小编的一个支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408392.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java基础之写文件——使用多个视图缓冲区(PrimesToFile2)

控制台程序。本例将对应于每个素数的数据以三个连续数据项的形式写入&#xff1a; 1、以二进制值表示的字符串长度值&#xff08;最好是整型&#xff0c;但本例使用double类型&#xff09;&#xff1b; 2、素数值的字符串表示”Primennn“&#xff0c;其中数字的位数明显是变化…

react学习(32)----onref

// 父组件 import React from react import Son from ./son import { Button } from antdclass Father extends React.Component {child: anyconstructor(props) {super(props)}sonRef (ref) > {this.child ref // 在这里拿到子组件的实例}clearSonInput () > {this.c…

每日一学:如何用matplotlib展示图片

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 130 篇文章&#xff0c;本文大约 1000 字&#xff0c;阅读大约需要 5 分钟前言今天简单介绍如何通过 matplotlib 展示图片&#xff0c;分为以下几种情况&#xff1a;直接用 matplotli…

Servlet 过滤器

一、过滤器介绍 在Servlet 2.3中定义了过滤器&#xff0c;它能够对Servlet容器的请求和响应进行检查和修改。 Servlet过滤器能够在Servlet被调用之前检查Request对象&#xff0c;并修改Request Header 和 Request内容。 Filter可以过滤Servlet&#xff0c;JSP&#xff0c;HTML。…

每日一学:如何读取网络图片

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 131 篇文章&#xff0c;本文大约 1300 字&#xff0c;阅读大约需要 3 分钟前言有时候我们需要读取的图片是网络上的图片&#xff0c;应该怎么操作呢&#xff1f;这里也是介绍两个常用…

Windows Phone 如何振动手机?

1. 导入命名空间。 using Windows.Phone.Devices.Notification; 2. 通过调用对 VibrationDevice 类的静态 GetDefault 方法获取对振动控制器的引用。 VibrationDevice vibrationDevice VibrationDevice.GetDefault(); 3. 通过调用 VibrationDevice 类的 Vibrate 方法开始振动。…

编写高效的PyTorch代码技巧(上)

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 132 篇文章&#xff0c;本文大约 7000 字&#xff0c;阅读大约需要 20 分钟原文&#xff1a;https://github.com/vahidk/EffectivePyTorch作者&#xff1a;vahidk前言这是一份 PyTorc…

react学习(35)----getFieldDecorator will override value

我在自定义组件中定义了value值&#xff0c;getFieldDecorator会覆盖我们定义的值&#xff0c; 需要添加默认值可以使用在getFieldDecorator的时候&#xff0c;设置initialValue&#xff0c; 删除在自定义组件中定义的value就可以了&#xff01;

【ATT】Reverse Nodes in k-Group

ListNode* reverseBetween(ListNode* prev,ListNode* next)//reverse prev->next, last->prev之间的链表{ListNode* last prev->next;ListNode* cur last->next;while(cur!next){last->next cur->next;cur->next prev->next;prev->next cur;cu…

编写高效的PyTorch代码技巧(下)

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 133 篇文章&#xff0c;本文大约 3000 字&#xff0c;阅读大约需要 15 分钟原文&#xff1a;https://github.com/vahidk/EffectivePyTorch作者&#xff1a;vahidk前言这是一份 PyTorc…

统计(1 - 2)

统计学基础定义 Statistics的前部分为“state”&#xff0c;政府&#xff0c;原由是统计是300年前被首次应用在政府部门统计人口出生和死亡信息的&#xff1b;如今的统计学早已被应用在各个专业领域&#xff1b; 统计学是用以收集数据、分析数据和数据推论的一组概念、原则和方…

2020年计算机视觉学习指南

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 134 篇文章&#xff0c;本文大约 3000 字&#xff0c;阅读大约需要 10 分钟原文&#xff1a;https://towardsdatascience.com/guide-to-learn-computer-vision-in-2020-36f19d92c934作…

是选择Keras还是PyTorch开始你的深度学习之旅呢?

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 135 篇文章&#xff0c;本文大约 7000 字&#xff0c;阅读大约需要 20 分钟原文&#xff1a;https://medium.com/karan_jakhar/keras-vs-pytorch-dilemma-dc434e5b5ae0作者&#xff1…

关于myeclipse打开jsp巨慢解决方案

作为企业级开发最流行的工具&#xff0c;用Myeclipse开发java web程序无疑是最合适的&#xff0c;java web前端采用jsp来显示&#xff0c;myeclipse默认打开jsp的视图有卡顿的现象&#xff0c;那么如何更改jsp默认的打开方式&#xff0c;让我们可以进行更快速的jsp开发呢? 简单…

event

听取了网友:kenwang的意见,我的Blog在记流水账啊,现在才发现我发表的都是代码,一个感想也没有,以后要慢慢改正。明天要培训公司的框架&#xff0c;后天要搬家&#xff0c;这个周未没有得休息。

60分钟快速入门PyTorch

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 136 篇文章&#xff0c;本文大约 26000 字&#xff0c;阅读大约需要 60 分钟PyTorch 是由 Facebook 开发&#xff0c;基于 Torch 开发&#xff0c;从并不常用的 Lua 语言转为 Python …

react学习(38)----react是什么

什么是组件&#xff1f; 官方定义&#xff1a;将一些简短、独立的代码片段组合成复杂的 UI 界面&#xff0c;这些代码片段被称作“组件”。 解读&#xff1a;我们可以理解为能够组成一个UI界面的每一个独立的代码片段&#xff0c;例如表单的代码集合&#xff0c;轮播图的代码集…

大端与小端

/*************************************大端与小端&#xff1a;与大端存储格式相反&#xff0c;在小端存储格式中&#xff0c;低地址中存放的是字数据的低字节&#xff0c;高地址存放的是字数据的高字节**************************************//*联合体union的存放顺序是所有…

react学习(39)----react中的Hello World

ReactDOM.render(<h1>Hello, world!</h1>,document.getElementById(root) ); 它将在页面上展示一个 “Hello, world!” 的标题。