通过内置对象理解 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)
...
"Lao Qi") upper(
'LAO\x00QI'
"Lao Qi") toggle(
'lAO\x00qI'
"Lao Qi") lower(
'lao qi'字节的大小是固定的,而字符串则由于编码规则,其长度会有所不同,比如按照常用的 unicode 编码标准
utf-8
进行编码:1
2
3
4
5
6
7
8
9
10
11
12
13'I♥🐍' x =
len(x)
3
x.encode()
b'I\xe2\x99\xa5\xf0\x9f\x90\x8d'
len(x.encode())
8
2] x[
'🐍'
2].encode() x[
b'\xf0\x9f\x90\x8d'
2].encode()) len(x[
4变量
x
引用的字符串I♥🐍
由三个字符构成,实际上共计 8 个字节,而表情符号🐍
有4个字节长。按照下面的演示,如果读取表情符的每个单独的字节,它的“值”总是在 0 到 255 之间:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
172] x[
'🐍'
2].encode() b = x[
b
b'\xf0\x9f\x90\x8d' # 4 bytes
1] b[:
b'\xf0'
1:2] b[
b'\x9f'
2:3] b[
b'\x90'
3:4] b[
b'\x8d'
0] # indexing a bytes object gives an integer b[
240
3] b[
141
下面来看一些针对字节的位操作的例子:
1 | def alternate_case(string): |
这不是一个很好的示例,因此不用耗费精力解释它,但它确实有效,而且,相比于为每个字符的更改创建一个新的 bytes
对象,它更有效。
另外一个内置函数 memoryview
与 bytearray
很类似,但它可以引用一个对象或一个切片,而不是为自己创建一个新的副本,允许你传一个对内存中“字节段”的引用,并在原地编辑它:
1 | 256)) array = bytearray(range( |
bin
, hex
, oct
, ord
, chr
and ascii
:实现最基本转换
bin
、hex
和 oct
三个内置函数实现了最基本的数制转换:
1 | 42) bin( |
轻松地实现了二进制、八进制和十六进制与十进制整数之间的转换。
1 | 0x20) type( |
虽然十进制容易理解,但在有的时候,用其他进制,也是有必要的,如:
1 | 255, 254]) bytes([ |
下面的示例中,则将文件的打开模式 mode
的值用八进制实现:
1 | import os |
请注意,bin
仅用于创建一个 Python 整数的二进制数时,如果想要的是二进制字符串,最好使用 Python 的字符串格式:
1 | f'{42:b}' |
内置函数 ord
和 chr
用于实现 ASCII 和 unicode 字符及其字符编码间的转换:
1 | 'x') ord( |
format
:文本格式
内置函数 format(string, spec)
是 string.format(spec)
的另一种方式。可以用它实现字符串的转换,比如:
1 | 42, 'c') # int to ascii format( |
在《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 | 0 for num in nums]) any([num == |
而不是生成器表达式:
1 | 0 for num in nums) any(num == |
用列表解析和生成器,两者有较大的区别:
1 | 10 for num in range(100_000_000)) any(num == |
使用列表解析的第二行代码不仅会在列表中毫无理由地存储1亿个值,然后再运行 any
,而且在我的机器上也需要10秒以上的时间。 同时,因为第一行代码是一个生成器表达式,它会逐个生成从 0 到 10 的数字,并将它们传给 any
,一旦计数达到 10,any
就会中断迭代并几乎立即返回 True
。这也意味着,在这种情况下,它的运行速度实际上快了一千万倍。
所以,要使用生成器。
关于生成器的更多知识,请查阅《Python 大学实用教程》(电子工业出版社)
(补充知识完毕)
abs
, divmod
, pow
and round
:数学基础
这四个数学函数在编程中非常常见,它们被直接放在随时可用的内置函数中,而不是放在 math
模块中。
它们非常简单:
abs
返回一个数字的绝对值,例如:1
2
3
4
5
642) abs(
42
-3.14) abs(
3.14
3-4j) abs(
5.0divmod
返回除法运算后的商和余数:1
2
3
4
5
6
77, 2) divmod(
(3, 1)
5327, 100) quotient, remainder = divmod(
quotient
53
remainder
27pow
返回一个值的指数运算结果:1
2
3
4100, 3) pow(
1000000
2, 10) pow(
1024round
按照四舍五入原则返回数字:1
2
3
4
5
6
7
8
9import math
math.pi
3.141592653589793
round(math.pi)
3
4) round(math.pi,
3.1416
1728, -2) round(
1700
isinstance
and issubclass
:类型检查
type
内置函数可以用于对象的类型检查,就像这样:
1 | def print_stuff(stuff): |
这个函数中检验参数对象是否是 list
类型。
1 | 'foo') print_stuff( |
目前看起来,它能运行,但是,实际上存在一些问题。 这里有一个例子:
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 | 'spam', 'eggs', 'steak') items = ( |
如果传入的实参不是列表,则不能输出实参对象类型。对此的一种解决方法就是通过多分支的 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 大学实用教程》(电子工业出版社)中对“多态”的讲解内容。
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
关注微信公众号,读文章、听课程,提升技能