通过内置对象理解 Python(六)
2021-11-03
bytearray and memoryview: 字节接口
bytearray 与 bytes 类似,它的意义体现在:
bytearray在一些低级操作中,比如有关字节和位运算,使用bytearray对于改变单个字节会更有效。例如下面的魔幻操作:1
2
3
4
5
6
7
8
9
10
11
12
13def upper(s):
return ''.join(chr(ord(c) & 223) for c in s)
...
def toggle(s): return ''.join(chr(ord(c) ^ 32) for c in s)
...
def lower(s): return ''.join(chr(ord(c) | 32) for c in s)
...
upper("Lao Qi")
'LAO\x00QI'
toggle("Lao Qi")
'lAO\x00qI'
lower("Lao Qi")
'lao qi'字节的大小是固定的,而字符串则由于编码规则,其长度会有所不同,比如按照常用的 unicode 编码标准
utf-8进行编码:1
2
3
4
5
6
7
8
9
10
11
12
13x = 'I♥🐍'
len(x)
3
x.encode()
b'I\xe2\x99\xa5\xf0\x9f\x90\x8d'
len(x.encode())
8
x[2]
'🐍'
x[2].encode()
b'\xf0\x9f\x90\x8d'
len(x[2].encode())
4变量
x引用的字符串I♥🐍由三个字符构成,实际上共计 8 个字节,而表情符号🐍有4个字节长。按照下面的演示,如果读取表情符的每个单独的字节,它的“值”总是在 0 到 255 之间:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17x[2]
'🐍'
b = x[2].encode()
b
b'\xf0\x9f\x90\x8d' # 4 bytes
b[:1]
b'\xf0'
b[1:2]
b'\x9f'
b[2:3]
b'\x90'
b[3:4]
b'\x8d'
b[0] # indexing a bytes object gives an integer
240
b[3]
141
下面来看一些针对字节的位操作的例子:
1 | def alternate_case(string): |
这不是一个很好的示例,因此不用耗费精力解释它,但它确实有效,而且,相比于为每个字符的更改创建一个新的 bytes 对象,它更有效。
另外一个内置函数 memoryview 与 bytearray 很类似,但它可以引用一个对象或一个切片,而不是为自己创建一个新的副本,允许你传一个对内存中“字节段”的引用,并在原地编辑它:
1 | array = bytearray(range(256)) |
bin, hex, oct, ord, chr and ascii :实现最基本转换
bin 、hex 和 oct 三个内置函数实现了最基本的数制转换:
1 | bin(42) |
轻松地实现了二进制、八进制和十六进制与十进制整数之间的转换。
1 | type(0x20) |
虽然十进制容易理解,但在有的时候,用其他进制,也是有必要的,如:
1 | bytes([255, 254]) |
下面的示例中,则将文件的打开模式 mode 的值用八进制实现:
1 | import os |
请注意,bin 仅用于创建一个 Python 整数的二进制数时,如果想要的是二进制字符串,最好使用 Python 的字符串格式:
1 | f'{42:b}' |
内置函数 ord 和 chr 用于实现 ASCII 和 unicode 字符及其字符编码间的转换:
1 | ord('x') |
format:文本格式
内置函数 format(string, spec) 是 string.format(spec) 的另一种方式。可以用它实现字符串的转换,比如:
1 | format(42, 'c') # int to ascii |
在《Python 大学使用教程》 一书中对字符串的格式化输出有详细介绍,并且在另外一本即将出版的书稿中,专门介绍了格式化输出,请参阅:【字符串格式化输出】,或者访问:http://www.itdiffer.com/self-learning.html 查阅。
any 和 all
这是两个非常 Pythonic 的函数,恰当使用,能让代码更短,可读性更强,体现了 Python 的精髓。例如:
假设编写一个验证请求是否合规的 API,接受来自请求的 JSON 数据,判断该数据中是否含有 id 字段,并且该字段的长度必须是 20 ,一种常见的写法是:
1 | def validate_responses(responses): |
用 all 函数优化之后为:
1 | def validate_responses(responses): |
all 的参数是布尔值组成的迭代器,若迭代器中有一个 False 值,函数 all 的返回即为 False 。否则返回 True 。
再看一个判断回文的示例:
1 | def contains_palindrome(words): |
与之相对的是
1 | def contains_palindrome(words): |
补充知识: any 和 all 内部的列表解析
我们可以把使用 any 或 all 的代码写成列表解析式:
1 | any([num == 0 for num in nums]) |
而不是生成器表达式:
1 | any(num == 0 for num in nums) |
用列表解析和生成器,两者有较大的区别:
1 | any(num == 10 for num in range(100_000_000)) |
使用列表解析的第二行代码不仅会在列表中毫无理由地存储1亿个值,然后再运行 any ,而且在我的机器上也需要10秒以上的时间。 同时,因为第一行代码是一个生成器表达式,它会逐个生成从 0 到 10 的数字,并将它们传给 any ,一旦计数达到 10,any 就会中断迭代并几乎立即返回 True 。这也意味着,在这种情况下,它的运行速度实际上快了一千万倍。
所以,要使用生成器。
关于生成器的更多知识,请查阅《Python 大学实用教程》(电子工业出版社)
(补充知识完毕)
abs, divmod, pow and round :数学基础
这四个数学函数在编程中非常常见,它们被直接放在随时可用的内置函数中,而不是放在 math 模块中。
它们非常简单:
abs返回一个数字的绝对值,例如:1
2
3
4
5
6abs(42)
42
abs(-3.14)
3.14
abs(3-4j)
5.0divmod返回除法运算后的商和余数:1
2
3
4
5
6
7divmod(7, 2)
(3, 1)
quotient, remainder = divmod(5327, 100)
quotient
53
remainder
27pow返回一个值的指数运算结果:1
2
3
4pow(100, 3)
1000000
pow(2, 10)
1024round按照四舍五入原则返回数字:1
2
3
4
5
6
7
8
9import math
math.pi
3.141592653589793
round(math.pi)
3
round(math.pi, 4)
3.1416
round(1728, -2)
1700
isinstance and issubclass :类型检查
type 内置函数可以用于对象的类型检查,就像这样:
1 | def print_stuff(stuff): |
这个函数中检验参数对象是否是 list 类型。
1 | print_stuff('foo') |
目前看起来,它能运行,但是,实际上存在一些问题。 这里有一个例子:
1 | class MyList(list): |
当然,items 仍然是一个列表,但是 print_stuff 函数不再识别它了。 原因很简单,因为 type(items) 的返回值是 MyList ,不是 list 。
也可以说,函数 type 没有考虑继承问题,如果改用 isinstance ,它不仅检查一个对象是否是一个类的实例,它还检查该对象是否是一个子类的实例:
1 | class MyList(list): |
类似地, issubclass 检查一个类是否是另一个类的子类。 isinstance 的第一个参数是一个对象,但 issubclass 的第一个参数是另一个类:
1 | issubclass(MyList, list) |
所以,应该将 print_stuff 函数中的 type 替换为 isinstance ,优化之后,继续测试:
1 | items = ('spam', 'eggs', 'steak') |
如果传入的实参不是列表,则不能输出实参对象类型。对此的一种解决方法就是通过多分支的 if 语句实现。如果只是内置对象还好办一些,尽管如此,分支太多,代码也是丑陋的。
为此,Python 中有一个含有各种内置类型的“类”,可以用它们来测试类的某些“行为”,而不是测试类本身。在我们的例子中,行为是作为其他对象的容器,称之为 Container:
1 | from collections.abc import Container |
每个容器对象类型都会在 Container 基类的检查中返回 True , issubclass 也行之有效:
1 | from collections.abc import Container |
把它添加到代码中,就变成:
1 | from collections.abc import Container |
最后要特别声明:在实际的编程中,不提倡对参数类型进行检查。具体原因,请参阅《Python 大学实用教程》(电子工业出版社)中对“多态”的讲解内容。
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
关注微信公众号,读文章、听课程,提升技能