本文章内容包括:
- 电子书
- 单元习题
- 单元项目代码
《高阶Python:代码精进之路》电子版
链接地址:点击跳转
单元习题
Chapt 1
Example
-
Python 中没有变量声明,从理论上来说,是否能够有未初始化的数据?
-
如何理解 Python 中的整数是“无限长的”,为什么它们又不是无限的呢?
-
从理论上讲,是否存在能表示无限范围的类?
-
与大多数其他编程语言相比,Python对缩进的要求较严格?
-
在整个Python 项目中最好使用完全一致的代码缩进方案,但必须要求这样吗?在程序中哪些缩进必须一致?哪里可以有所不同?请举例说明。
-
准确说明为什么制表符会导致Python程序中的缩进出现问题(从而引人语法错误)?
-
Python 严格依赖代码缩进的好处是什么?
-
Python 函数可以同时返回多少个不同的值?
-
描述本章提出的针对函数的前向引用问题的解决方案。为什么会出现这类问题?9
-
在编写Python文本字符串时,应该根据哪些条件来选择引号(单引号、双引号或三引号)?
-
至少指出一个Python列表与其他语言(例如C)数组(一般是连续存储的单个类型的集合)的不同点。
Resolving
解析 (1):Python 中没有变量声明,但由于 Python 使用动态类型,因此不存在未初始化的数据。所有变量在使用前都需要被赋值。
解析 (2):Python 中的整数在理论上可以无限长,因为它们在内存中使用了任意精度的表示方式。然而,实际上整数的大小受到可用内存的限制,因此它们并不是“真正”无限的。
解析 (3):从理论上讲,Python 中不存在能表示无限范围的类。任何数据结构或类都会受到计算机物理内存和处理能力的限制。
解析 (4):是的,Python 对缩进的要求比大多数其他编程语言严格,因为 Python 使用缩进来表示代码块的层次结构,而不是使用花括号等其他标记符。
解析 (5):在整个 Python 项目中最好使用完全一致的代码缩进方案,但并不是必须要求的。在同一个代码块内,缩进必须一致,否则会引发语法错误。在不同模块或函数中,缩进风格可以不同,但为了代码可读性,建议保持一致。
解析 (6):制表符和空格混用可能导致 Python 程序中的缩进问题,因为不同的编辑器和工具对制表符的显示长度可能不同,导致语法错误。
解析 (7):Python 严格依赖代码缩进的好处是提高代码的可读性和一致性,强制开发者写出结构清晰的代码。
解析 (8):Python 函数可以通过元组、列表或字典同时返回多个不同的值,没有数量限制。
解析 (9):前向引用问题通常出现在函数调用在定义之前的情况。解决方案是将函数定义放在调用之前或使用函数声明和调用分离的方式。
解析 (10):选择引号时,主要考虑字符串中是否包含需要转义的字符。例如,如果字符串中包含单引号,使用双引号更方便,反之亦然。三引号用于多行字符串或包含多种引号的复杂字符串。
解析 (11):Python 列表可以存储不同类型的元素,而在 C 中,数组通常只能存储单一类型的元素,并且必须预先定义大小。
Chapter 2
Example
-
给字符串的索引字符赋值是否违反Python 字符串的不变性?
-
使用 += 运算符进行的字符串连接是否违反了Python 字符串的不变性?为什么?
-
Python 中有几种方法可以索引指定字符?
-
准确说明索引和切片的关系?
-
索引字符的数据类型是什么?切片产生的子字符串的数据类型是什么?
-
在 Python 中,字符串和字符“类型”之间是什么关系?
-
说出两个运算符和一种方法,使用它们可以从一个或多个较小的字符串构建较大的字符串。
-
如果要使用 index方法查找子字符串,那么先使用 in 或 not in 来测试目标字符串的好处是什么?
-
哪些内置的字符串方法和哪些运算符会产生布尔值(真/假)?
Resolving
解析 (1):给字符串的索引字符赋值会违反 Python 字符串的不变性,因为字符串是不可变的,一旦创建就不能修改其内容。
解析 (2):使用 +=
运算符进行字符串连接不会违反 Python 字符串的不变性。实际上,+=
创建了一个新的字符串对象并将其赋值给原变量,而不是修改原字符串。
解析 (3):Python 中有几种方法可以索引指定字符,主要包括通过正索引 (s[index]
) 和负索引 (s[-index]
) 的方式。
解析 (4):索引和切片是相关的概念。索引用于访问单个字符,而切片用于访问字符串的子字符串。切片的起始和结束位置可以看作是多个索引的范围。
解析 (5):索引字符的数据类型是字符串 (str
),切片产生的子字符串的数据类型也是字符串 (str
),即使结果仅包含一个字符。
解析 (6):在 Python 中,字符串和字符没有独立的“类型”区分。单个字符实际上是一个长度为一的字符串。
解析 (7):两个运算符是 +
和 *
,一种方法是 join()
。它们可以从一个或多个较小的字符串构建较大的字符串。
解析 (8):在使用 index
方法查找子字符串之前先使用 in
或 not in
测试目标字符串的好处是,可以避免 index
在未找到子字符串时引发 ValueError
异常。
解析 (9):内置的字符串方法如 startswith()
、endswith()
、isalpha()
以及运算符如 in
和 not in
会产生布尔值 (真/假)。
Chapter 3
Example
-
你能够编写同时使用正数索引和负数索引的程序或函数吗?这样做有弊端吗?
-
创建包含1000个元素的Python列表的最有效方法是什么?假设每个元素都被初始化为相同的值。
-
如何使用切片来从列表中每隔1个元素获取1个元素?(例如创建一个包含第1、33、5、7个等元素的新列表。)
-
描述索引和切片之间的区别。
-
当切片表达式中使用的索引超出范围时会发生什么?
-
如果将列表传递给函数,并且希望函数能够更改列表的值(即函数返回的列表有所不同),应避免怎样的操作?
-
什么是不平衡矩阵?
-
为什么创建任意大矩阵需要使用列表推导式或循环?
Resolving
解析 (1):你可以编写同时使用正数索引和负数索引的程序或函数。这种做法没有太大弊端,通常用于不同的场景。例如,正数索引从左到右访问元素,而负数索引从右到左访问元素。但需要注意的是,使用负数索引时要确保索引范围正确,否则会出现索引错误。
解析 (2):创建包含1000个元素的Python列表的最有效方法是使用乘法操作符:[value] * 1000
,其中 value
是初始化每个元素的值。
解析 (3):可以使用切片来从列表中每隔1个元素获取1个元素,方法是:new_list = original_list[::2]
。这将创建一个包含第1、3、5、7等元素的新列表。
解析 (4):索引用于访问单个元素,而切片用于访问一个范围内的元素,从而生成一个新的子列表。索引是对单个位置的引用,而切片是对起始位置、结束位置和步长的组合操作。
解析 (5):当切片表达式中的索引超出范围时,Python 不会引发错误,而是返回列表中可用的元素,或者返回空列表。
解析 (6):如果将列表传递给函数,并且希望函数能够更改列表的值,应避免使用 =
操作符对列表进行赋值,因为这会创建一个新的列表对象。可以通过直接修改列表的元素或使用切片赋值来更改原列表的内容。
解析 (7):不平衡矩阵是指行和列的数量不相等的矩阵,或者某些行的元素数量不同于其他行的矩阵。
解析 (8):创建任意大矩阵需要使用列表推导式或循环,因为 Python 中的列表是动态的,没有固定的大小限制。列表推导式和循环可以灵活地初始化和填充矩阵中的元素。
Chapter 4
Example
-
像 +=这样的赋值运算符仅是一种简便写法吗?它是否可以提高程序的运行性能?
-
在大多数计算机语言中,编写Python语句a,b=a+b,a,需要的最少语句数是多少?
-
在Python中,将包含100个整数的列表的元素全部初始化为0的最有效方法是什么?
-
用1,2,3,1,2,3,1,2,3...初始化包含99个整数的列表的最有效方法是什么?如果可能的话,请写出代码。
-
如果从 IDLE 中运行 Python 程序,如何最有效地输出多维列表。
-
可以对字符串使用列表推导式吗?怎样做?
-
如何从命令行获取用户编写的 Python 程序的帮助?从IDLE 中又如何获取呢?
-
在Python中,函数被称为“第一类对象”,但在大多数其他语言(例如 C++或Java)中则不是。Python函数(可调用对象)可以做哪些在C或C中无法做到的事情?
-
包装器(wrapper)、被包装的函数(wrapped function)和装饰器(decorator)有什么区别?
-
生成器函数返回值是什么(如果有返回值的话)?
-
从Python语言的角度来看,将普通函数转换为生成器函数需要进行的一项更改是什么?
-
至少说出生成器的一项优势。
Resolving
解析 (1):像 +=
这样的赋值运算符不仅是一种简便写法,在某些情况下也可以提高程序的运行性能。它避免了创建一个新的对象,因此可以减少内存分配和拷贝操作的开销。
解析 (2):在大多数计算机语言中,编写 Python 语句 a, b = a + b, a
需要的最少语句数为 2,即 temp = a
和 a = temp + b
。
解析 (3):在 Python 中,将包含 100 个整数的列表的元素全部初始化为 0 的最有效方法是使用乘法操作符:[0] * 100
。
解析 (4):用 1, 2, 3, 1, 2, 3, 1, 2, 3...
初始化包含 99 个整数的列表的最有效方法是:[1, 2, 3] * 33
。
解析 (5):从 IDLE 中运行 Python 程序,最有效地输出多维列表的方法是使用 pprint
模块中的 pprint
函数,它可以以更易读的格式输出列表。代码如下:
from pprint import pprint pprint(multidimensional_list)
解析 (6):可以对字符串使用列表推导式,将字符串视为字符的序列。例子如下:
char_list = [char for char in "example"]
解析 (7):从命令行获取用户编写的 Python 程序的帮助可以使用 python -m pydoc your_module
。在 IDLE 中,可以使用 help(your_module)
来获取帮助。
解析 (8):在 Python 中,函数是“第一类对象”,意味着它们可以像其他对象一样被赋值给变量、作为参数传递或作为返回值。Python 函数可以嵌套、闭包以及动态创建,而在 C++ 或 Java 中,函数通常不能直接作为对象操作。
解析 (9):包装器(wrapper)是一个将其他函数作为参数的函数,用于扩展或修改其行为。被包装的函数(wrapped function)是实际执行逻辑的函数,而装饰器(decorator)是一种特殊的包装器,用于在不修改原始函数代码的情况下扩展其功能。
解析 (10):生成器函数返回一个生成器对象,它是一个可迭代对象,可以逐个生成值,而不是一次性返回所有值。
解析 (11):从 Python 语言的角度来看,将普通函数转换为生成器函数需要将 return
语句更改为 yield
语句,yield
会在每次调用时返回一个值,并在下次迭代时继续执行。
解析 (12):生成器的一个主要优势是节省内存,因为它们按需生成值,而不是一次性生成所有值。
Chapter 5
Example
-
使用第一种格式化方法(%字符串类格式说明符)有什么优点?
-
使用全局 format 函数有什么好处?
-
与使用全局format 函数相比,使用字符串类的format 方法有什么优势?
-
format 函数和字符串类的 format 方法之间有什么关联?
-
问题4中的两种方法与各个类的_format-方法有何关联?
-
至少需要使用格式说明符(%)的哪些功能,才能输出浮点数按列排列的表格?
-
至少需要使用 format 方法的哪些功能,才能输出浮点数按列排列的表格?
-
举出一个例子说明repr和str函数提供了数据的不同表示形式。为什么repr能输出更多的字符?
-
使用format方法可以指定零(0)作为f字符或数字表达式的前导“0”。这是多余的语法吗?不是的话,请举出至少一个结果可能不同的例子?
-
三种方法中(格式说明符%、全局format 函数和字符串类的format 方法),谁支持宽度可变的输出字段?
Resolving
解析 (1):使用第一种格式化方法 (%
字符串类格式说明符) 的优点是它简单、易读,且与 C 语言中的格式化风格类似,适合简单的字符串插值操作。
解析 (2):使用全局 format
函数的好处是可以对非字符串对象进行格式化,并且支持更复杂的格式化需求。它是更通用的方式,适用于各种类型的数据。
解析 (3):与全局 format
函数相比,使用字符串类的 format
方法的优势在于它更直观,允许通过命名参数、索引和对齐方式对字符串进行灵活的格式化,同时还能直接在字符串中嵌入变量。
解析 (4):format
函数和字符串类的 format
方法之间的关联是,format
函数实际上是调用了被格式化对象的 __format__
方法,而字符串类的 format
方法则是基于模板字符串进行插值,最终也会调用对象的 __format__
方法。
解析 (5):问题4中的两种方法与各个类的 __format__
方法有密切关联。无论是 format
函数还是字符串类的 format
方法,最终都是通过调用对象的 __format__
方法来生成格式化后的字符串。
解析 (6):要输出浮点数按列排列的表格,至少需要使用格式说明符的对齐(-
)、宽度(数字
)、精度(.数字
)功能。例如:%10.2f
将以10个字符宽度和两位小数的形式输出浮点数。
解析 (7):要输出浮点数按列排列的表格,至少需要使用 format
方法的对齐(:<
、:>
、:^
)、宽度(数字
)、精度(.数字
)功能。例如:"{:10.2f}".format(value)
将以10个字符宽度和两位小数的形式输出浮点数。
解析 (8):repr
和 str
函数提供了数据的不同表示形式。str
提供的是面向用户的简洁表示,而 repr
提供的是面向开发者的详细表示,通常包括更精确的信息。举例:
value = 3.14159 print(str(value)) # 输出: 3.14159 print(repr(value)) # 输出: 3.14159
在复杂对象中,repr
通常包含更多的字符和信息。
解析 (9):使用 format
方法指定零 (0
) 作为 f
字符或数字表达式的前导“0”并非多余的语法。例如:
print("{:05d}".format(42)) # 输出: 00042 print("{:5d}".format(42)) # 输出: 42
前者使用前导0,后者则没有,结果不同。
解析 (10):在三种方法中,格式说明符 (%
)、全局 format
函数和字符串类的 format
方法都支持宽度可变的输出字段。格式说明符通过在 %
后指定宽度,全局 format
函数和字符串类的 format
方法通过在格式字符串中指定宽度来实现。
Chapter 6
Example
-
表达式 x*可以匹配的最少字符数和最多字符数是多少?
-
解释正则表达式(ab)c+和a(bc)+匹配结果的差别。哪一个模式等同于非限定模式 abc+ ?
-
在使用正则表达式时,何时需要使用以下语句?
import re
-
当使用方括号表示字符集时,哪些字符在什么情况下具有特殊含义?
-
编译正则表达式对象有什么好处?
-
re.match和re.search之类函数的返回值--match对象有哪些用法?
-
使用或运算符
(|)
和使用字符集([])
有什么区别? -
为什么说在正则表达式搜索模式中使用原始字符串指示符(r)很重要?在替换字符串中也是吗?
-
替换字符串中的哪些字符(如果有)具有特殊含义?
Resolving
解析 (1):表达式 x*
可以匹配的最少字符数是 0,最多字符数是无限个 x
。
解析 (2):正则表达式 (ab)c+
匹配的是字符串中包含 ab
后跟一个或多个 c
的部分,而 a(bc)+
匹配的是以 a
开头,后跟一个或多个 bc
的部分。模式 abc+
等同于 (ab)c+
,因为它要求 c
出现至少一次。
解析 (3):在使用正则表达式时,需要在代码中使用 import re
语句来导入 Python 的 re
模块,该模块提供正则表达式的匹配操作函数,如 re.match()
、re.search()
、re.sub()
等。
解析 (4):当使用方括号表示字符集时,字符 -
在字符集内且不在字符集开头或结尾时具有表示范围的特殊含义,如 [a-z]
。字符 ^
在字符集的开头表示取反字符集,如 [^a-z]
。
解析 (5):编译正则表达式对象的好处是可以在需要多次使用相同模式时提高匹配效率,因为正则表达式对象只需编译一次,可以重复使用,而无需每次都重新编译。
解析 (6):re.match
和 re.search
函数的返回值是一个匹配对象(match object
),它具有一些有用的方法,如 group()
(返回匹配的子字符串)、groups()
(返回所有匹配的子组)和 start()
、end()
(返回匹配子字符串的开始和结束位置)。
解析 (7):使用或运算符 (|)
用于匹配多个模式中的一个,如 (a|b)
,它可以匹配 a
或 b
;而字符集 ([])
用于匹配字符集内的任一单个字符,如 [ab]
,它只能匹配单个字符 a
或 b
。
解析 (8):在正则表达式搜索模式中使用原始字符串指示符 r
很重要,因为它会使反斜杠 (\
) 被视为普通字符,而不是转义符号,避免了正则表达式中的转义字符被误解释。在替换字符串中使用 r
指示符不那么常见,但在某些情况下也很有用。
解析 (9):替换字符串中的 \
(反斜杠)具有特殊含义,用于引用匹配的子组,如 \1
表示第一个匹配的子组,$
在某些实现中也用于表示子组引用。
Chapter 7
Example
-
简述贪婪模式和非贪婪模式在代码上的区别。将贪婪模式转换为非贪婪模式至少需要改动哪里?需要更改或添加哪些字符?
-
贪婪模式与非贪婪模式在什么时候会有区别?如果使用非贪婪模式进行匹配但是唯一可能匹配的项同贪婪模式的匹配结果一样会怎么样?
-
在一个仅查找一个匹配项的简单的字符串匹配中(不进行任何替换),使用非标记组是否会对结果产生影响?
-
描述一种使用非标记组将对程序结果产生重大影响的情况。
-
先行断言与标准正则表达式模式不同,它不消耗匹配的字符。描述一种情况,6在这种情况下使用先行断言会对程序结果产生影响。
-
正则表达式的正向先行断言与负向先行断言有什么区别?
-
在正则表达式中使用命名组替代仅按数字引用的组有什么好处?
-
使用命名组来识别目标字符串中的重复元素,例如字符串“The cowjumped overthe the moon'
-
Scanner 扫描器在分析字符串时会做哪些re.findall 函数不会做的事情?
-
扫描器对象是否必须被命名为scanner?
Resolving
解析 (1):贪婪模式在代码上使用 *
, +
, ?
等量词时会尽可能多地匹配字符,而非贪婪模式通过在量词后添加 ?
使其尽可能少地匹配字符。将贪婪模式转换为非贪婪模式需要在量词后添加 ?
,如 .*
转换为 .*?
。
解析 (2):贪婪模式与非贪婪模式在匹配多个可能结果时有区别。贪婪模式会尝试匹配尽可能多的字符,而非贪婪模式会尝试匹配尽可能少的字符。如果使用非贪婪模式匹配,唯一可能匹配的项与贪婪模式的匹配结果相同,那么结果不会有任何变化。
解析 (3):在仅查找一个匹配项的简单字符串匹配中(不进行任何替换),使用非标记组((?:...)
)不会对匹配结果产生影响,因为非标记组只影响分组行为,不影响匹配本身。
解析 (4):使用非标记组会对程序结果产生重大影响的情况是当你需要避免分组影响 group()
或 groups()
方法的结果时。例如,当你只想匹配某个模式而不想让这个匹配成为一个独立的捕获组时,使用非标记组可以避免额外的分组。
解析 (5):先行断言((?=...)
)不消耗匹配的字符,它只在匹配成功时继续匹配剩余的部分。一个使用先行断言会影响程序结果的情况是当你需要检查一个字符串中是否有某个模式后跟随特定内容,而不想包括这些内容在最终匹配中。例如,匹配所有在数字后面有空格的单词但不包括数字本身。
解析 (6):正向先行断言((?=...)
)用于匹配某个条件后的文本,而负向先行断言((?!...)
)用于匹配不符合某个条件的文本。前者要求条件成立,后者要求条件不成立。
解析 (7):在正则表达式中使用命名组替代仅按数字引用的组有助于提高代码的可读性和可维护性。命名组使得复杂表达式的逻辑更清晰,特别是当有多个捕获组时。
解析 (8):使用命名组来识别目标字符串中的重复元素:
import re text = "The cow jumped over the the moon" pattern = r"(?P<word>\b\w+\b)\s+(?P=word)" match = re.search(pattern, text) print(match.group()) # 输出: the the
这里使用了命名组 (?P<word>...)
来捕获重复的单词。
解析 (9):Scanner
扫描器在分析字符串时可以逐步处理匹配,允许用户在每个匹配项被找到时执行自定义的处理逻辑,而 re.findall
只是简单地返回所有匹配项,不提供这种交互性。
解析 (10):扫描器对象不需要被命名为 scanner
。这是一个变量名,可以根据程序的需求自由命名。
Chapter 8
Example
-
总结文本文件和二进制文件之间的区别
-
在哪些情况下,使用文本文件是最佳解决方案?在哪些情况下,使用二进制文件会更好?
-
使用二进制操作直接将 Python 整数写人磁盘时会遇到哪些问题?
-
说出使用 with 关键字打开文件的优点,
-
读取一行文本时,Python是否会读取句尾的换行符?当写人一行文本时,Python是否会在句尾自动添加换行符?
-
哪些文件操作支持随机访问?
-
什么时候最适合使用struct 软件包?
-
什么时候使用 pickle 软件包是最好的选择?
-
什么时候使用 shelve 软件包是最好的选择?
-
与使用其他数据字典相比,使用shelve软件包时有什么特殊限制?
Resolving
解析 (1):文本文件和二进制文件的区别在于数据的表示方式。文本文件以人类可读的字符形式存储数据,通常使用特定的字符编码(如 UTF-8),而二进制文件直接存储数据的字节序列,没有字符编码,适合存储图像、音频等非文本数据。
解析 (2):使用文本文件是最佳解决方案的情况包括需要处理人类可读的数据,如日志文件、配置文件、文本文档等。使用二进制文件更好的情况包括处理需要高效存储和读取的非文本数据,如图像、音频、视频或大型数据集。
解析 (3):将 Python 整数以二进制方式直接写入磁盘时可能遇到的问题包括字节顺序(endianness)和大小不同平台不兼容的问题。
解析 (4):使用 with
关键字打开文件的优点包括在完成文件操作后自动关闭文件,避免文件泄露和资源浪费。
解析 (5):读取一行文本时,Python 会读取行尾的换行符。如果需要去掉换行符,可以使用 rstrip()
方法。当写入一行文本时,Python 不会自动在行尾添加换行符,必须手动添加。
解析 (6):支持随机访问的文件操作包括 seek()
和 tell()
,它们允许在文件的任意位置读取或写入数据,而不需要顺序地从头到尾进行操作。
解析 (7):使用 struct
软件包最适合在需要将 Python 数据类型转换为 C 语言中的结构体或将二进制数据解析为 Python 数据类型时。它允许定义复杂的二进制数据结构并进行打包或解包。
解析 (8):使用 pickle
软件包是最好的选择的情况是当需要将 Python 对象序列化并保存到磁盘或在网络之间传输时。它能够将几乎所有 Python 对象转化为字节流并恢复为原对象。
解析 (9):使用 shelve
软件包最适合在需要将 Python 对象存储为键值对形式且数据可以持久化存储时。它类似于字典,但数据存储在磁盘文件中,适合需要快速存取和持久化数据的应用场景。
解析 (10):与使用其他数据字典相比,使用 shelve
软件包的特殊限制包括键必须是字符串,且值必须是可以被 pickle
序列化的对象。此外,shelve
存储的数据在并发访问时可能会出现数据一致性问题。
Chapter 9
Example
-
描述类与其实例之间的关系,是一对一还是一对多关系?
-
哪些信息仅在类的实例中存在?
-
类中包含哪些信息?
-
方法到底是什么?与标准函数有什么不同?
-
Python是否支持继承,如果支持,语法是什么?
-
Pyihon对封装(将实例或类变量设为私有)的支持如何?
-
类变量和实例变量之间的区别是什么?
-
在类的方法定义中,什么时候需要使用self?
-
__add__
和__radd__
方法有什么区别? -
何时需要反向方法?什么时候甚至不需要它仍然可以支持相应操作?
-
__iadd__
方法叫什么? -
子类会继承
__init__
方法吗?如果需要在子类中自定义其行为,该怎么办?
Resolving
解析 (1):类与其实例之间的关系是一对多的关系。一个类可以有多个实例,每个实例都是该类的一个具体对象。
解析 (2):实例中的信息包括实例变量和实例的状态。实例变量是与特定对象相关的属性,每个实例都有自己独立的值,这些值仅存在于该实例中。
解析 (3):类中包含的信息包括类变量、方法定义、类的元数据(如类名和继承关系)以及类级别的属性。类是实例的蓝图,定义了所有实例共有的行为和属性。
解析 (4):方法是定义在类中的函数,与标准函数的不同之处在于方法需要通过类的实例来调用,并且第一个参数通常是 self
,用于指代调用该方法的实例对象。
解析 (5):Python 支持继承,语法为:
class 子类名(父类名): pass
子类会继承父类的属性和方法,可以重写或扩展这些功能。
解析 (6):Python 对封装的支持通过将实例或类变量设为私有来实现,方法是在变量名前添加双下划线 __
。例如,__var
是一个私有变量。尽管如此,Python 并不完全禁止访问这些变量,而是通过名称重整(name mangling)来提供一定程度的封装。
解析 (7):类变量是属于整个类的属性,所有实例共享一个类变量。实例变量是属于某个特定实例的属性,每个实例都有自己的实例变量,互不影响。
解析 (8):在类的方法定义中,self
用于指代调用方法的实例。当方法需要访问或修改实例的属性或其他方法时,需要使用 self
。
解析 (9):__add__
方法用于定义两个对象相加时的行为,而 __radd__
方法用于处理反向加法运算,即当左操作数不支持加法运算时,Python 会调用右操作数的 __radd__
方法。
解析 (10):需要反向方法的情况是当两个操作数的类型不同,并且左操作数不支持该运算时。即使没有定义反向方法,如果左操作数支持并成功执行了该运算,反向方法也不会被调用。
解析 (11):__iadd__
方法是原位加法运算符对应的方法,通常用于实现类似 a += b
的操作,使其在原对象上进行修改,而不是创建新对象。
解析 (12):子类会继承父类的 __init__
方法。如果需要在子类中自定义其行为,可以在子类中重写 __init__
方法,并通过 super()
函数调用父类的 __init__
方法以保留父类的初始化逻辑:
class 子类(父类): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 子类的自定义初始化逻辑
Chapter 10
Example
-
比较 float 和 Decimal 类的优缺点。
-
考虑两个对象:Decimal('1.200)和Decimal('1.2')。它们在什么情况下是相同的?它们只是两种表示完全相同的值的方法,还是对应于不同的内部状态?
-
如果对 Decimal('1.200')和 Decimal('1.2')进行相等性测试会怎样?
-
为什么从字符串初始化Decimal对象通常比从浮点值初始化更好?
-
在计算中将 Decimal对象与整数结合起来容易吗?
-
组合 Decimal对象和浮点值容易吗?
-
给出一个可以使用Fraction 类精确表示却不能用Decimal 类精确表示的数值的示例。
-
给出一个可以用 Decimal或 Fraction 类精确表示却不能用浮点值精确表示的数值的示例。
-
考虑两个分数对象:Fraction(1,2)和Fraction(5,18)。这两个对象的内9部结构是否相同?为什么?
-
Fraction 类和整数类(int)之间的关系是什么?包含还是继承?
Resolving
解析 (1):float
的优点是计算速度快,内存占用少,但精度有限,容易出现舍入误差。Decimal
类的优点是精度高,适合精确的金融和科学计算,但计算速度较慢,内存占用较大。
解析 (2):Decimal('1.200')
和 Decimal('1.2')
在数值上相同,但在内部状态上不同。前者保留了三位小数,后者只保留了两位小数,因此它们的内部表示不同。
解析 (3):对 Decimal('1.200')
和 Decimal('1.2')
进行相等性测试会返回 False
,因为它们虽然数值相同,但内部表示不同。
解析 (4):从字符串初始化 Decimal
对象通常比从浮点值初始化更好,因为浮点值在转换过程中可能会引入精度误差,而字符串初始化可以保留精确的十进制表示。
解析 (5):将 Decimal
对象与整数结合起来是容易的,因为 Python 会自动将整数转换为 Decimal
对象以进行精确的运算。
解析 (6):组合 Decimal
对象和浮点值较为困难,因为浮点值的精度问题会影响运算的结果,通常需要将浮点值先转换为 Decimal
对象以避免误差。
解析 (7):Fraction(1, 3)
是一个可以使用 Fraction
类精确表示但不能用 Decimal
类精确表示的数值,因为 1/3
是无限循环小数,而 Decimal
类的精度是有限的。
解析 (8):Decimal('0.1')
或 Fraction(1, 10)
是可以用 Decimal
或 Fraction
类精确表示但不能用浮点值精确表示的数值,因为浮点表示的 0.1
实际上是一个近似值。
解析 (9):Fraction(1, 2)
和 Fraction(5, 18)
的内部结构不同,因为它们的分子和分母不同。Fraction
类存储的是分子和分母的值,因此它们是不同的对象。
解析 (10):Fraction
类与 int
类之间是包含关系,而非继承关系。Fraction
是 numbers.Rational
的子类,并且 int
也可以被视为分母为 1 的 Fraction
对象。
Chapter 11
Example
-
什么是概率分布?如果随机生成一些值,那么我们如何预测这些值呢?
-
真随机数和伪随机数之间有什么区别?为什么后者被认为“足够好”?
-
控制正态分布行为的两个主要因素是什么?
-
举例说明现实生活中的正态分布。
-
你希望小样本的概率分布表现如何?随着试验次数的增加,你期望它发生什么变化?
-
使用 random.shuffle 数可以重排什么样的对象?
-
在math 软件包中有哪些函数类别。
-
求幂与求对数有何关系?
-
Python 中的三个对数函数是什么?
Resolving
解析 (1):概率分布是描述随机变量可能取值及其相应概率的一种函数。如果随机生成一些值,可以通过已知的概率分布来预测这些值的分布情况。例如,正态分布描述了值在平均值附近出现的概率较高,远离平均值的概率较低。
解析 (2):真随机数由物理现象生成,完全不可预测;伪随机数由算法生成,尽管可预测,但在大多数应用中被认为“足够好”,因为它们的统计性质与真随机数接近,并且在可重复性和效率上有优势。
解析 (3):控制正态分布行为的两个主要因素是均值(决定分布的中心位置)和标准差(决定分布的宽度,即数据的离散程度)。
解析 (4):现实生活中的正态分布例子包括人的身高、智商分布等,这些数据通常集中在某个平均值附近,偏离的概率逐渐降低。
解析 (5):你希望小样本的概率分布与总体的概率分布相似,但由于样本较小,可能会出现较大偏差。随着试验次数的增加,样本分布应逐渐逼近理论概率分布,这就是大数定律的体现。
解析 (6):random.shuffle
可以重排可变序列的元素,如列表,打乱其元素的顺序。
解析 (7):math
软件包中的函数类别包括基本的算术运算(如加减乘除)、幂和对数函数、三角函数、双曲函数、角度转换函数、特殊函数(如阶乘)、以及常量(如 π 和 e)。
解析 (8):求幂是计算一个数的指数(如 a^b
),而求对数是幂运算的逆运算(如 log_a(x)
是找到 a
的幂为 x
的指数 b
)。换句话说,对数表示幂运算中的指数。
解析 (9):Python 中的三个对数函数是 math.log(x)
(默认以 e
为底,也可以指定其他底数),math.log2(x)
(以 2 为底),和 math.log10(x)
(以 10 为底)。
Chapter 12
Example
-
内置 array 包有什么优势?
-
array 包有哪些限制?
-
说明 array 包和 numpy 包之间的主要区别。
-
描述 empty、ones 和 zeros 函数之间的差异。
-
在用于创建新数组的fromfunction函数中,callable 参数的作用是什么?
-
通过加法运算将numpy数组与单值操作数(标量,例如int或浮点值)相结合时会发生什么?
-
在数组标量运算中可以使用赋值运算符(例如+=或*-)吗?运算符的作用是什么?
-
*固定长度的字符串可以包含在numpy数组中吗?将更长的字符串分配给这样的数组会发生什么?
-
通过加法(+)或乘法()之类的运算符对两个numpy数组进行运算时会发生什么?两个 numpy 数组之间必须满足什么要求?
-
如何使用布尔数组作为另一个数组的掩码?
-
使用标准 Python 及其软件包来计算大量数据的标准差的三种不同方法是什么?根据执行速度对它们进行排名。
-
利用布尔掩码生成的数组的维数是多少?
Resolving
解析 (1):内置 array
包的优势是它允许创建高效的、紧凑的、固定类型的数组。与 Python 的内置列表相比,array
更节省内存,并且提供更高的性能,特别是在处理大量相同类型的数据时。
解析 (2):array
包的限制是它只能存储相同类型的数据,且功能相对较少,缺乏高级数组操作和数学功能,例如多维数组、矩阵运算等。
解析 (3):array
包和 numpy
包之间的主要区别在于功能性。numpy
是一个功能强大的科学计算库,支持多维数组、矩阵运算、线性代数、傅里叶变换等高级操作,而 array
仅支持基本的单维或简单的多维数组操作。
解析 (4):empty
函数创建一个未初始化的数组,数组中的元素是随机值;ones
函数创建一个数组,所有元素都初始化为 1;zeros
函数创建一个数组,所有元素都初始化为 0。
解析 (5):在 fromfunction
函数中,callable
参数是一个函数,用于根据给定的索引生成数组的值。该函数会被逐个索引调用,并返回对应索引处的数组元素。
解析 (6):将 numpy
数组与单值操作数(标量)相结合进行加法运算时,标量会广播到数组的每个元素上,执行逐元素加法运算。
解析 (7):在数组标量运算中可以使用赋值运算符(例如 +=
或 *=
)。这些运算符会直接在数组上执行运算并更新数组的值,而不需要创建新的数组对象。
解析 (8):固定长度的字符串可以包含在 numpy
数组中。如果尝试将更长的字符串分配给这样的数组,字符串会被截断到数组中指定的固定长度。
解析 (9):通过加法(+
)或乘法(*
)之类的运算符对两个 numpy
数组进行运算时,两个数组必须具有相同的形状,或者其中一个数组可以被广播到另一个数组的形状。运算将逐元素执行。
解析 (10):可以使用布尔数组作为掩码来选择另一个数组中对应位置为 True
的元素。结果是一个新数组,仅包含那些对应布尔掩码中 True
位置的元素。
解析 (11):使用标准 Python 及其软件包来计算大量数据的标准差的三种方法是:
-
使用
statistics.stdev
函数。 -
使用
numpy.std
函数。 -
手动计算,即先计算均值,再计算每个元素与均值的差的平方和,最后取平均并开平方根。 按执行速度排名为:
numpy.std
>statistics.stdev
> 手动计算。
解析 (12):利用布尔掩码生成的数组的维数与原数组相同,但结果数组的形状可能不同,因为布尔掩码会移除不满足条件的元素。
Chapter 13
Example
-
可以使用哪些方法增加同一个图形中不同曲线之间的对比度?
-
阅读完本章后,你能否说出复利与较大的固定利率相比有什么优势?
-
直方图是什么?说出一种用numpy软件包生成这种图的方法。
-
如何调整X和Y轴之间的长宽比?
-
总结两个数组之间多种乘法的差异。三种数组乘法包括:点积、外积和两个numpy 数组的标准乘法。
-
在贷款购买房屋之前,可以使用哪个numpy函数计算每月的还款额?
-
numpy 数组可以存储字符串数据吗?如果可以,请至少说出一个它的限制。
Resolving
解析 (1):可以使用以下方法增加同一个图形中不同曲线之间的对比度:
-
使用不同的颜色、线型或标记符号。
-
调整线条的粗细和透明度。
-
添加图例和标签,以便更清晰地区分不同曲线。
解析 (2):复利的优势在于它能使投资随时间呈指数增长,特别是在长期投资中,这种效果比较大的固定利率更明显。复利通过将利息再投资,不断增加本金,进而增加收益。
解析 (3):直方图是一种显示数据分布的图形,通过将数据分组并统计每组数据的频数来显示数据的分布情况。使用 numpy
软件包生成直方图的一种方法是结合 matplotlib
库,例如:
import numpy as np import matplotlib.pyplot as plt data = np.random.randn(1000) plt.hist(data, bins=30) plt.show()
解析 (4):可以通过 matplotlib
库中的 plt.gca().set_aspect()
方法来调整 X 和 Y 轴之间的长宽比。例如,plt.gca().set_aspect('equal')
会使 X 和 Y 轴的单位长度相同。
解析 (5):两个数组之间的多种乘法差异包括:
-
点积(
np.dot
或@
运算符):这是一个标量乘法,或对于高维数组,是矩阵乘法。 -
外积(
np.outer
):这是两个向量的乘积,结果是一个矩阵,每个元素是两个向量对应元素的乘积。 -
标准乘法(
*
运算符):逐元素乘法,即两个数组中对应位置的元素相乘,结果是一个与原数组相同形状的数组。
解析 (6):在贷款购买房屋之前,可以使用 numpy
中的 np.pmt
函数来计算每月的还款额。
解析 (7):numpy
数组可以存储字符串数据,限制是字符串长度必须固定。所有字符串在数组中必须具有相同的长度,短字符串会被填充到固定长度,长字符串会被截断。
Chapter 14
Example
-
使用多个 import语句多次导人同一个模块是否合法?这样做的目的是什么?你能想到这样做的一个有用场景吗?
-
模块有哪些属性?(至少说出一个。)
-
进行循环导人(例如,两个模块互相导人)会建立依赖关系,这可能会使程序存在潜在的错误。如何设计程序以避免相互导人?
-
在Python中
__a11__
的作用是什么? -
在什么情况下会使用
__name__
属性或字符串'__main__'
? -
在开发RPN 解释器(逐行解释RPN脚本)时,添加程序计数器的目的是什么?
-
在设计一种简单的编程语言(例如RPN)时,该语言完整(从理论上讲它可以执行任何计算机任务)的最小操作集合是什么?
Resolving
解析 (1):使用多个 import
语句多次导入同一个模块是合法的。这样做的目的是为了在不同的模块或函数中确保所需模块被导入。一个有用的场景是:在模块的不同部分中使用局部导入,以减少初始导入时间或避免循环导入的问题。
解析 (2):模块的属性包括但不限于:__name__
(模块名)、__file__
(模块文件路径)、__doc__
(模块文档字符串)等。
解析 (3):避免相互导入的设计方法包括:
-
重构代码,将共享功能提取到一个独立的模块中,供其他模块导入。
-
使用延迟导入,将导入语句放入函数或方法内部,仅在需要时才导入。
-
通过动态导入或使用
importlib
来控制导入顺序。
解析 (4):__all__
的作用是定义模块的公开接口。当使用 from module import *
语句导入模块时,只有 __all__
中列出的名称会被导入。未列入 __all__
的模块内容不会被导出。
解析 (5):__name__
属性用于检查模块是被直接运行还是被导入。当模块被直接运行时,__name__
的值为 '__main__'
。在编写可复用模块时,通过检查 if __name__ == '__main__':
可以在模块被直接执行时运行特定代码,如测试代码或主程序。
解析 (6):在开发 RPN 解释器时,添加程序计数器的目的是跟踪当前正在执行的指令位置,确保每条指令按正确顺序执行,并管理程序流程控制(如跳转和循环)。
解析 (7):在设计简单的编程语言(如 RPN)时,为了完整性,最小操作集合通常包括:
-
基本算术操作(如加、减、乘、除)
-
数据操作(如存储、取值)
-
控制流操作(如条件判断、跳转、循环)
Chapter 15
Example
-
numpy 数组和 pandas 数据框之间有什么区别吗?如果有,如何在两者之间进行转换?
-
当用户输人股票代码时,什么地方可能出错,应该如何应对?
-
列举一些用于绘制股票图形的绘图方法
-
对于股票图形,为什么显示图例很重要?
-
如何限制 pandas 数据框的范围,使其覆盖不到一年的时间段?
-
什么是180天移动平均线?
-
本章的最后一个示例是否使用了“间接”导人?如果是,这是如何实现的?
Resolving
解析 (1):numpy
数组和 pandas
数据框的主要区别在于:numpy
数组是多维数组,适合处理同质数据;pandas
数据框是二维表格结构,适合处理异质数据(不同类型的列)。在两者之间转换可以通过 pandas.DataFrame(numpy_array)
将 numpy
数组转换为 pandas
数据框,或者通过 dataframe.to_numpy()
将 pandas
数据框转换为 numpy
数组。
解析 (2):当用户输入股票代码时,可能出错的地方包括:输入无效或不存在的股票代码,输入格式不正确等。应对措施包括:输入验证、错误处理机制(如提供友好的错误消息)、自动补全或提示有效股票代码等。
解析 (3):用于绘制股票图形的绘图方法包括:
-
matplotlib
中的plot
方法,用于绘制价格趋势线。 -
candlestick_ohlc
方法,用于绘制K线图。 -
pandas
中的plot
方法,可以轻松绘制时间序列数据。
解析 (4):在股票图形中显示图例很重要,因为它可以明确区分不同数据集或指标(如移动平均线、开盘价、收盘价等),从而使图表信息更加清晰和易于理解。
解析 (5):要限制 pandas
数据框的范围以覆盖不到一年的时间段,可以使用时间筛选。例如,假设数据框有 Date
列,代码如下:
df = df[(df['Date'] >= '2023-01-01') & (df['Date'] <= '2023-12-31')]
这段代码会筛选出 2023 年的记录。
解析 (6):180 天移动平均线是一种技术分析指标,通过计算股票在过去 180 天的平均价格来平滑短期波动,并观察长期趋势。它是通过滚动窗口的平均值计算得出的。
解析 (7):本章的最后一个示例使用了“间接”导入。通过在函数或方法内部进行导入,或者使用条件导入,仅在特定条件下导入所需的模块,从而实现间接导入。