Python学习之路20-数据模型

《流畅的Python》笔记。
本篇是Python进阶篇的开始。本篇主要是对Python特殊方法的概述。

1. 前言

数据模型其实是对Python框架的描述,它规范了这门语言自身构件模块的接口,这些模块包括但不限于序列、迭代器、函数、类和上下文管理器。不管在哪种框架下写程序,都会花费大量时间去实现那些会被框架本身调用的方法,Python也不例外。Python解释器碰到特殊句法时,会使用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(所以特殊方法也叫双下方法 dunder method),这些特殊方法名能让自己编写的对象实现和支持以下的语言构架,并与之交互:

迭代、集合类、属性访问、运算符重载、函数和方法的调用、对象的创建和销毁、字符串表示形式和格式化、管理上下文(即with块)。

下面通过一些例子来介绍常用的特殊方法。

2. Python风格纸牌

首先介绍两个特殊方法__getitem____len__这两个特殊方法。以下代码创建了一个纸牌类:

import collectionsCard = 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]

namedtuple,即命名元组,类似于C/C++中的struct,定义如下:

collections.namedtuple(typename, field_names, verbose=False, rename=False)

第一个参数是元组名;第二个是该元组中含的属性名;第三个参数表示在构建该命名元组之前先打印出该命名元组的结构,如果在控制台输入第3行代码,并置verboseTrue的话,会输出该命名元组的内部结构,实际上它是一个继承自tuple的类,由于输出过长,请大家自行实验;如果该命名元组的元素名中有Python关键字,则需要置第四个参数为True,这些与关键字重名的元素名会被特殊处理。

用命名元组创建一个不带方法的对象十分简单:

>>> from chapter20 import Card, FrenchDeck
>>> beer_card = Card("7", "diamonds")
>>> beer_card
Card(rank='7', suit='diamonds')

由于FrenchDeck实现了__getitem__方法,所以可以像操作ListTuple一样操作FrenchDeck,比如随机访问,切片:

>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
>>> from random import choice
>>> choice(deck)
Card(rank='4', suit='clubs')
>>> choice(deck)
Card(rank='J', suit='clubs')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), 
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

由于实现了该方法,FrenchDeck还是个可迭代对象,即可以用for循环对其访问(也可以反向访问reversed):

>>> for card in deck:
>>> ... print(card)Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
-- snip --
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')

迭代通常是隐式的,譬如说一个集合类型没有实现__contains__方法,那么in运算符就会按顺序做一次迭代搜索(调用__getitem__),于是in运算符可以用在FrenchDeck上:

>>> Card('2', 'spades') in deck
True

如果对上述deck变量调用sorted函数,Python将按ASCII码进行排序,但这并不是扑克牌的正确排序,所以下面我们自定义排序方法:

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)

此时输出的结果就是先按点数排序,再按花色排序。

3. 如何使用特殊方法

需要明确一点,特殊方法的存在是为了给Python解释器调用到,作为程序员并不需要调用他们,也即是说,没有my_object.__len__()这种写法,而应该是len(my_object)。说到__len__方法,如果是Python内置类型,CPython会抄个近路,该方法实际上会直接返回PyVarObject里的ob_size属性,而PyVarObject是表示内存中长度可变的内痔对象的C语言结构体。

很多时候特殊方法的调用是隐式的,比如for i in x:这个语句,背后其实用的是iter(x),而这个函数的背后则是x.__iter__()方法,当然前提是这个方法在x中被实现(如果没被实现则会调用__getitem__方法)。

直接调用这个值比调用一个方法快很多。直接调用特殊方法的频率应该远远低于你去实现它们的次数。

通过内置的函数(例如leniterstr等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度更快。

还有一点值得注意:不要想当然地随意添加特殊方法,比如__foo__之类的,因为虽然现在这个名字没有被Python内部使用,以后就不一定了。

3.1 自定义向量Vector

使用5个特殊方法实现Vector的字符串输出,取绝对值(如果是复数则是取模),返回布尔值,加法和数乘等运算:

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)# 在Python中,只有0,NULL才是False,其余均为Truedef __bool__(self):# 更简单的写法是:# return bool(self.x or self.y)return bool(abs(self))# 实现加法运算符重载def __add__(self, other):return Vector(self.x + other.x, self.y + other.y)# 实现乘法运算符重载,这里是数乘,且还没有实现交换律(需要实现__rmul__方法)def __mul__(self, scalar):return Vector(self.x * scalar, self.y * scalar)

Python有一个内置函数叫做repr。该函数通过特殊方法__repr__来得到一个对象的字符串表示形式,如果没有该特殊方法,当我们在控制台打印一个向量对象时,得到的字符串可能是<Vector object at 0x10e00070>

# 代码:
v1 = Vector(2, 4)
v2 = Vector(2, 1)
print(v1 + v2)
print(abs(v1))
print(v1 * 3)# 结果:
Vector(4, 5)
4.47213595499958
Vector(6, 12)

__repr____str__的区别与联系:前者方便我们调试和记录日志,后者则是给终端用户看的。后者是在str()函数被使用,或者是在print函数打印一个对象的时候才被调用,它返回的字符串对终端用户友好。如果只想实现这两个特殊方法中的一个,__repr__是更好的选择,因为如果一个对象没有__str__函数,Python又需要调用它时,解释器会用__repr__代替。

上述Vector类实现了__bool__方法,它可用于需要布尔值的上下文中(if, while, and, or, not等)。默认情况下,我们自己定义的类的实例总被认为是True,除非重写了这个类的__bool____len__方法。bool(x)的背后是调用x.__bool__();如果不存在__bool__方法,那么bool(x)会尝试调用x.__len__(),如果该方法返回0,则bool返回False,否则返回True

3.2 为什么len不是普通方法

“实用胜于纯粹”(Python之禅里的一句话)。len之所以不是一个普通方法,是为了让Python自带的数据结构可以走后门,abs也是同理。但多亏了它是特殊方法,我们也可以把len用于自定义数据类型。这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也印证了“Python之禅”中的另一句话:“不能让特例特殊到考试破坏既定规则”。

4. 总结

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具Python风格(Pythonic)的代码。后面的内容将围绕更多的特殊方法展开。


迎大家关注我的微信公众号"代码港" & 个人网站 www.vpointer.net ~

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

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

相关文章

String s1==s2面试题

这个程序运行的结果是什么&#xff1f;class StringTest1{public static void main(String[] args) {String s1"equal";String s2 "equal";if(s1s2){System.out.println("s1s2");}else{ System.out.println("s1!s2");} }} 输出 s1…

Vector:动态数组的使用和说明

摘自百度百科&#xff1a; 1. Vector 类在 java 中可以实现自动增长的对象数组; 创建了一个向量类的对象后&#xff0c;可以往其中随意地插入不同的类的对象&#xff0c;既不需顾及类型也不需预先选定向量的容量&#xff0c;并可方便地进行查找。对于预先不知或不愿预先定义数组…

Spring AOP 代理模式

记录几篇关于AOP & 代理模式 的博客&#xff0c;写的非常好&#xff0c;感谢作者分享&#xff01; Java中的代理模式——静态代理以及分析静态代理的缺点 Java中动态代理的两种方式JDK动态代理和cglib动态代理以及区别 Spring中的AOP以及切入点表达式和各种通知 Spring…

mongodb 多表查询

今天有一个业务涉及到mongodb的多表查询&#xff0c;大体记录下语句结构 db.table_a.aggregate([ {$lookup:{from:"table_b",localField:"userid",foreignField:"userid",as:"organization_doc"}}, //联表B{ $project:{ _id:1, card…

跨平台多线程编程

多线程介绍POSIX 1003.1-2001 定义了多线程编程的标准API。这个API就是广为人知的pthreads。它的目的在于为跨平台编写多线程程序提供便利。多线程程序的编写本文介绍了Linux 和 WIN32 平台下的多线程程序的编写方法Linux 系统对 pthreads 提供了良好的支持。一般地安装完Linux…

方法重载和方法重写

方法重载 &#xff08;1&#xff09; 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在&#xff0c;具有不同的参数个数/类型。 重载Overloading是一个类中多态性的一种表现。 &#xff08;2&#xff09; Java的方法重载&#xff0c;就是在类中可以…

shell获取/etc/passwd中的用户名和id

核心思想&#xff1a;cut 关键词&#xff1a;head tail cut #!/bin/bash # get the information about /etc/passwd PATH/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATHfile"/etc/passwd" linewc -l $file | cut -d" "…

进度条控制(Windows 公共进度栏控件的功能)

MSDN&#xff1a;https://msdn.microsoft.com/zh-cn/library/sys15k39.aspx 构造 CProgressCtrl 对象&#xff1a;  CProgressCtrl(); 创建进度栏控件&#xff1a;  virtual BOOL Create( DWORD dwStyle(风格), const RECT& rect(位置大小), CWnd* pParentWnd(父窗体),…

本地浏览器缓存sessionStorage(临时存储) localStorage(长期存储)的使用

对浏览器来说&#xff0c;使用 Web Storage 存储键值对比存储 Cookie 方式更直观&#xff0c;而且容量更大&#xff0c;它包含两种&#xff1a;localStorage 和 sessionStorage sessionStorage&#xff08;临时存储&#xff09; &#xff1a;为每一个数据源维持一个存储区域&am…

HTML5的历史

HTML5和WHATWG[1] 2004年Opera的IanHickson&#xff08;现在Google工作&#xff09;发起的HTML新标签扩展以适应新的web应用&#xff0c;该发起遭到W3C的拒绝&#xff0c;而W3C凭空杜撰的作风引起了部分人的不满&#xff0c;来自Opera,Apple&#xff0c;以及Mozilla的代表开始…

B-TREE、B+TREE、数据库索引

推荐文章&#xff1a; B-Tree详解 SQL夯实基础&#xff08;五&#xff09;&#xff1a;索引的数据结构 MySQL B树索引和哈希索引的区别 感谢作者分享&#xff01;

《重构-改善既有代码的设计》-第1例:租赁影片(1)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 买了《重构 - 改善既有代码的设计 》一书&#xff0c;一直没有好好看&#xff0c;大致过了下也觉得只是有点点印象而已&#xff0c;最后…

java类加public和不加public

加public表示全局类&#xff0c;该类可以import到任何类内。不加public默认为保留类&#xff0c;只能被同一个包内的其他类引用。

STL数据结构

STL数据结构 1.priority_queue #include<queue> pritority<int>q;(大根堆) priority_queue<int,vector<int>,greater<int> >q;(小根堆) struct no{ int x,v; bool operator <(const no &T)const{return v>T.v;} // v值xiao的优先 }; q…

《netty实战》阅读笔记(2)——Netty 的数据容器ByteBuf

ByteBuffer 当我们进行数据传输的时候&#xff0c;往往需要使用到缓冲区&#xff0c;常用的缓冲区就是JDK NIO类库提供的java.nio.Buffer。 实际上&#xff0c;7种基础类型&#xff08;Boolean除外&#xff09;都有自己的缓冲区实现&#xff0c;对于NIO编程而言&#xff0c;我们…

JAVA HASHMAP 用法

import java.util.HashMap;public class Student { String name; String sex; public Student(String n,String s) { namen; sexs; } public String toString(){ return ("姓名:"name"\n""性别:"sex"\n…

有了二叉查找树、平衡树为啥还需要红黑树?

参考文章&#xff1a; 有了二叉查找树、平衡树为啥还需要红黑树&#xff1f; 漫画AVL树 谢谢作者分享&#xff01;

32位Windows7上8G内存使用感受+xp 32位下使用8G内存

我推荐做开发的朋友:赶快加入8G的行列吧....呵呵..超爽...速度超快...基本没有等待的概念...深有体会... 为什么要使用8G内存&#xff1f;在国内外各大论坛上&#xff0c;这都是一个有争议的问题。问题的反方论据非常充分&#xff1a; 除了少数专业领域&#xff0c;大多数应用程…

《重构-改善既有代码的设计》-第1例:租赁影片(2)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 上接 重构-改善既有代码的设计-第1例&#xff1a;租赁影片&#xff08;1&#xff09; 2 运用多态取代与价格相关的条件逻辑 2.1 最好不…

elasticsearch 的查询 /_nodes/stats 各字段意思

/_nodes/stats 字段意思 “” 1 { 2 "_nodes": {3 "total": 1,4 "successful": 1,5 "failed": 06 },7 "cluster_name": "ELKTEST",8 "nodes": {9 "lnlHC8yERCKXCuAc…