龙空技术网

Python学习之路20-数据模型

VPointer701 472

前言:

今天小伙伴们对“python ascii码排序”大约比较关怀,兄弟们都需要了解一些“python ascii码排序”的相关知识。那么小编也在网络上网罗了一些对于“python ascii码排序””的相关知识,希望小伙伴们能喜欢,同学们快快来了解一下吧!

本篇是Python进阶篇的开始。本篇主要是对Python特殊方法的概述。

1. 前言

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

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

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

2. Python风格纸牌

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

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

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

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

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

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

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

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

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

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

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__方法)。

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

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

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

3.1 自定义向量Vector

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

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

__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)的代码。后面的内容将围绕更多的特殊方法展开。

标签: #python ascii码排序