活动地址:CSDN21天学习挑战赛
流畅的Python读书笔记(二)数据模型
上一篇文章介绍了特殊方法,根据之前的介绍,可以简单归纳出特殊方法的作用,即
当用户使用某些Python内置的函数时,由解释器隐式调用的方法
。特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们。也就是说没有
my_object.__len__()
这种写法,而应该使用
len(my_object)
。
然而,如果你使用的是Python内置的类型,比如说列表(list)、字符串(str)、字节序列(bytearray)等。那么CPython会抄个近路
,去访问对象中记录长度的字段值。原文是这样描述的:__len__ 实际上会直接返回 PyVarObject 里的 ob_size 属性。PyVarObject 是表示内存中长度可变的内置对象的 C 语言结构体。直接读取这个值比调用一个方法要快很多。
很多时候,特殊方法的调用是隐式的,比如 for i in x:
这个语句,背后其实用的是iter(x)
,而这个函数的背后则是x.__iter__()
方法。当然前提是这个方法在x
中被实现了。
通常自己写的代码中不需要直接调用这些特殊方法,除非有大量元编程,以及__init()__
方法,这个方法常用来构建超类。
对内置的类使用内置的方法一般能够获得更快的速度。但是自己不要想当然随意添加一些特殊方法,比如__foo__()
。因为说不定以后Python就会使用这个标识符添加新的特殊方法,这时候你的行为便会和Python的行为发生冲突。
如何使用特殊方法
这是本篇笔记的重点,主要介绍一些常用特殊方法的使用,了解掌握即可,较简单。
而整篇的介绍基本围绕运算符重载
展开。
运算符重载
:扩展运算符的功能,使其支持更多的运算。对于我们自己定义的一个类MyObject
,如果有其实例对象obj1
,obj2
,且类本身没有实现运算符重载,那么我们便不能对这两个实例对象进行相关的运算操作,即不支持obj1+obj2
等类似的运算。
在Python中想要实现运算符重载,那么就必须实现相应的特殊方法。
一个模拟二维向量的自定义类
书中通过一个自定义的二维向量类来演示如何使用特殊方法以及实现运算符重载。
给出类的完整定义,后面会详细解释每个方法的含义:
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scale):
return Vector(self.x * scale, self.y * scale)
def __bool__(self):
return bool(abs(self))
字符串表示形式
先提前介绍以下:
tip:有时,我们在格式化字符串时,经常会使用
%s
来作为占位符。现在引入%r
。
格式化符 意义 ‘%s’ 返回要格式化对象的 __str__()
方法的返回值‘%r’ 返回要格式化对象的 __repr__()
方法的返回值
对于上面的那段代码,我们先只关注__repr__(self)
部分:
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
Python中有一个内置的函数叫做repr
,它能把一个对象用字符串形式表示出来。实际上,Python在遇到repr()
函数时,会自动转换成调用__repr()__
方法,这个特殊方法会返回对象的字符串表示形式。通过以下代码可以发现有无__repr__()
的区别:
# 类的定义中无__repr()__方法时
v1 = Vector(3, 4)
print(v1)
#结果为:<__main__.Vector object at 0x000001F9E46751C0>
# 类的定义中有__repr()__方法时
v1 = Vector(3, 4)
print(v1)
#结果为:Vector(3, 4)
还需要介绍一个特殊方法__str()__
,这个方法也是返回一个字符串。__repr__
和__str__
的区别在于,后者是在 str()
函数被使用,或是在用print
函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
发现没有,我们使用print()
函数打印一个对象时,实际上Python解释器会隐式调用__str()__
方法来将对象转换成字符串,但是根据上面介绍的,Python解释器调用的是__repr__()
方法,这是不是有问题呢?
没有的,这里涉及到一个规则,如果一个对象没
有 __str__
函数,而 Python 又需要调用它的时候,解释器会用__repr__
作为替代。也就是说,当用户使用print
或者显示使用str()
时,Python解释器会优先去执行__str()__
,如果其不存在,再去调用__repr()__
。
为了更好地理解两者之间的差异,下面给出一段修改过的代码:
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return '这是Vector的__repr()__方法'
def __str__(self):
return '这是Vector的__str()__方法'
v1 = Vector(3, 4)
print(v1)
print(str(v1))
print(repr(v1))
运行结果如下:
这是Vector的__str()__方法
这是Vector的__str()__方法
这是Vector的__repr()__方法
所以,如果你只想实现这两个方法中的一种,那么推荐是'__repr__()
,因为没有__str__()
,解释器就会执行__repr__()
。
算术运算符重载
这里只简单介绍+
和*
的重载,更多运算符的重载以后介绍。
通过 __add__
和 __mul__
,上面的示例代码为向量类带来了 +
和*
这两个算术运算符。值得注意的是,这两个方法的返回值都是新创建的向量对象,被操作的两个向量(self
或 other
)还是原封不动,代码里只是读取了它们的值而已。
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scale):
return Vector(self.x * scale)
代码浅显易懂,不做介绍。给出一个应用示例:
v1 = Vector(3, 4)
v2 = Vector(4, 5)
print(v1 + v2)
print(v1 * v2)
自定义布尔值
Python中的任何类型都可以转换成bool类型,默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对__bool__
或者 __len__
函数有自己的实现。
bool(x)
背后是调用x.__bool__(self)
的结果,如果不存在__bool__()
,那么bool(x)
会尝试调用x.__len__(self)
,此时,若返回0,则bool(x)
结果为False
,否则为True
。
特殊方法一览
https://docs.python.org/3/reference/datamodel.html
一章列出了 83 个特殊方法的名字,其中 47 个用于实现算术运算、位运算和比较操作。
本篇小结
-
str(x)
会调用x.__str__()
,repr(x)
会调用x.__repr__()
。当需要调用__str__()
时,__str__()
不存在,那么便会调用__repr__()
。 -
bool(x)
会调用x.__bool__()
,当x.__bool__()
不存在时,调用x.__len__()
,此时返回0,则为False
,否则为True
。
参考资料
- 流畅的Python