Fluent Python2 【Chapter18_QA】
1. 理解下from collections import ChainMap的用法
from collections import ChainMap # 创建字典1 dict1 = {'a': 1, 'b': 2} # 创建字典2 dict2 = {'b': 3, 'c': 4} # 创建字典3 dict3 = {'c': 5, 'd': 6} # 将字典1、字典2和字典3合并成一个 ChainMap chain = ChainMap(dict1, dict2, dict3) # 访问键 'a',会先在 dict1 中查找 print(chain['a']) # 输出: 1 # 访问键 'b',会先在 dict1 中查找,因为在 dict1 中找到了,所以不会再查找 dict2 或 dict3 print(chain['b']) # 输出: 2 # 访问键 'c',会先在 dict1 中查找,然后在 dict2 中查找,最后在 dict3 中查找 print(chain['c']) # 输出: 4 # 访问键 'd',会直接在 dict3 中查找 print(chain['d']) # 输出: 6 # 更新 ChainMap 中的字典,会影响到底层的字典 chain['a'] = 10 print(dict1) # 输出: {'a': 10, 'b': 2}
在这个示例中,我们创建了两个字典 dict1 和 dict2,然后将它们合并成一个 ChainMap。当我们访问 ChainMap 中的键时,它会依次搜索底层的字典,直到找到对应的键。此外,我们也可以通过 [] 操作符更新 ChainMap 中的键值对,这会直接影响底层的字典。
通过以下示例,展示了 ChainMap 的常见用法,并对每个方法的作用进行了解释说明。
from collections import ChainMap # 创建字典1 dict1 = {'a': 1, 'b': 2} # 创建字典2 dict2 = {'b': 3, 'c': 4} # 创建字典3 dict3 = {'c': 5, 'd': 6} # 将字典1、字典2和字典3合并成一个 ChainMap chain = ChainMap(dict1, dict2, dict3) # 1. keys() 方法返回所有键的集合 print(chain.keys()) # 输出: KeysView(['c', 'd', 'b', 'a']) # 2. values() 方法返回所有值的集合 print(chain.values()) # 输出: ValuesView([5, 6, 3, 1]) # 3. items() 方法返回所有键值对的集合 print(chain.items()) # 输出: ItemsView([('c', 5), ('d', 6), ('b', 3), ('a', 1)]) # 4. new_child() 方法创建一个新的 ChainMap,其中包含指定的映射,而不影响原始 ChainMap new_chain = chain.new_child({'e': 7, 'f': 8}) print(new_chain) # 输出: ChainMap({'e': 7, 'f': 8}, {'c': 5, 'd': 6}, {'b': 3, 'a': 1}) # 5. parents 属性返回一个新的 ChainMap,其中包含原始 ChainMap 的所有映射,但不包含最后一个映射 print(chain.parents) # 输出: ChainMap({'c': 5, 'd': 6}, {'b': 3, 'a': 1}) # 6. reversed() 方法返回一个反转的 ChainMap reversed_chain = reversed(chain) print(reversed_chain) # 输出: ChainMap({'c': 5, 'd': 6}, {'b': 3, 'a': 1})
下面补充展示了 ChainMap 的 map 属性和 update 方法的用法:
from collections import ChainMap # 创建字典1 dict1 = {'a': 1, 'b': 2} # 创建字典2 dict2 = {'b': 3, 'c': 4} # 创建字典3 dict3 = {'c': 5, 'd': 6} # 将字典1、字典2和字典3合并成一个 ChainMap chain = ChainMap(dict1, dict2, dict3) # 7. map 属性是一个只读映射列表,其中包含 ChainMap 中所有的映射 print(chain.maps) # 输出: [{'a': 1, 'b': 2}, {'b': 3, 'c': 4}, {'c': 5, 'd': 6}] # 8. update() 方法用于将另一个映射或可迭代对象中的键值对添加到 ChainMap 中的第一个映射中 chain.update({'e': 7, 'f': 8}) print(chain) # 输出: ChainMap({'e': 7, 'f': 8}, {'c': 5, 'd': 6}, {'b': 3, 'a': 1})
2. vars()函数的功能是什么?
以下是 vars() 函数的用法示例:
class Person: def __init__(self, name, age): self.name = name self.age = age person1 = Person("Alice", 30) print(vars(person1)) # 输出 {'name': 'Alice', 'age': 30}
在这个示例中,vars() 返回了 person1 对象的属性字典,其中包含了 name 和 age 属性及其对应的值。
3. python中上下文管理器的概念理解
Python中的上下文管理器(Context Manager)是一种用于规定带特定运行时上下文的对象的协议。
它允许你在有需要的情况下,以一种统一且可控的方式获取和释放资源。
上下文管理器的主要作用是简化资源的获取和释放过程,从而避免编码错误导致的资源泄漏。
通俗的解释:
想象一下,你在家做一些活动(上下文),比如打开冰箱拿食物。一般来说,拿完食物后你需要关上冰箱门,以免电费浪费、食物变质等等。
上下文管理器就相当于在你打开冰箱门时帮你自动记录下来,等你拿完食物后,它会提醒你"嘿,别忘了关冰箱门啦"。
如果你不小心忘了关门,它也会在最后给你一个警告。
Python中的上下文管理器通过with语句和__enter__和__exit__两个魔术方法来实现。让我们来看一个例子:
class File: def __init__(self, filename, mode): self.filename = filename self.mode = mode def __enter__(self): print('Opening file') self.file = open(self.filename, self.mode) return self.file def __exit__(self, exc_type, exc_value, traceback): print('Closing file') self.file.close() if exc_type is not None: print('Exception occurred') with File('example.txt', 'w') as f: # 多加几个断点,多去调式下试试,体会下这个with, __enter__, __exit__的调用顺序 print('Writing to file') f.write('Hello, World!')
在这个例子中,我们定义了一个File类,它实现了上下文管理器协议。当我们使用with File('example.txt', 'w') as f:语句时,它会自动调用__enter__方法来打开文件,并将文件对象赋值给f。我们可以像平常一样使用f来读写文件。
等我们离开with块时,无论发生什么情况,上下文管理器都会自动调用__exit__方法,确保文件被正确关闭。如果在操作文件的过程中发生了异常,__exit__方法也会被调用,并获得异常的类型、值和traceback,让你有机会处理异常。
Python中许多标准库都使用了上下文管理器,例如文件操作、线程锁、数据库连接等,它们都需要获取和释放某种资源。使用上下文管理器可以大大简化编码,避免由于手动管理资源而导致的常见错误,提高了代码的健壮性。
4. 如下这段话不是很理解,换言之 “上下文管理器对象到底长什么样,返回结果是什么”。
求解with 后面的表达式得到的结果是上下文管理器对象,不过,绑定到目标变量(在 as 子句中)上的值是在上下文管理器对象上调用__enter__ 方法返回的结果。
这句话描述了with语句在使用上下文管理器时的工作机制。它包含以下几个关键点:
with后面的表达式必须返回一个上下文管理器对象, 这个对象必须实现__enter__和__exit__两个方法。- 当执行
with语句时, Python会首先调用这个上下文管理器对象的__enter__方法。 __enter__方法的返回值会被绑定到as子句中指定的目标变量上。- 在
with语句块内部,我们可以使用这个绑定的目标变量。 - 不论
with语句块内是否发生异常,一旦离开with语句块,Python都会自动调用这个上下文管理器对象的__exit__方法。
让我们通过一个示例来更好地理解:
class CustomOpen: def __init__(self, filename): self.filename = filename def __enter__(self): print('Opening file') self.file = open(self.filename) return self.file # 返回文件对象 def __exit__(self, exc_type, exc_value, traceback): print('Closing file') self.file.close() with CustomOpen('example.txt') as file: # 所谓的"上下文管理器对象" 其实就是CustomOpen # 这里 file 绑定的是 __enter__ 返回的文件对象 content = file.read() print(content)
5. 追问:with CustomOpen('example.txt') as file: 语句后面的表达式返回一个CustomOpen对象,它是我们自定义的上下文管理器对象?这个对象是CustomOpen?还是其他的,要如何去理解呢
让我们再次仔细看一下这句话:
with CustomOpen('example.txt') as file:语句后面的表达式返回一个CustomOpen对象,它是我们自定义的上下文管理器对象。
这里的表述有些不太准确,让我重新解释一下:
CustomOpen('example.txt')这个表达式创建并返回一个CustomOpen类的实例对象。- 这个
CustomOpen实例对象才是真正的上下文管理器对象。
所以,更精确的说法是:
with CustomOpen('example.txt') as file:语句后面的表达式CustomOpen('example.txt')创建并返回一个CustomOpen类的实例,这个实例对象就是我们自定义的上下文管理器对象。
为什么一个类的实例对象能够作为上下文管理器对象呢? 这是因为我们在CustomOpen类中实现了__enter__和__exit__两个特殊的方法。
Python会自动识别实现了这两个方法的对象为上下文管理器对象, 并在with语句块执行前调用__enter__方法,执行后调用__exit__方法。
所以,正确的理解是:
CustomOpen是一个普通的Python类CustomOpen('example.txt')创建并返回这个类的一个实例对象- 这个实例对象由于实现了
__enter__和__exit__方法,被Python识别为一个上下文管理器对象 with语句在使用这个上下文管理器对象时,会自动调用它的__enter__和__exit__方法 【可以自己多加断点,去IDE里面体会下这个过程】
希望这个解释能够更清晰一些。关键是要理解with后面的表达式返回的是一个实现了上下文管理器协议的对象实例,而不是类本身。
6. 追问,这个东西算是 上下文管理器吗?
<_io.TextIOWrapper name='mirror.py' mode='r' encoding='cp936'>
是的,_io.TextIOWrapper对象是一个上下文管理器对象。
在Python中,文件对象实现了上下文管理器协议,也就是说它们有__enter__和__exit__方法。
当你使用open()函数打开一个文件时,它返回的是一个_io.TextIOWrapper对象(如果是文本模式)或_io.BufferedReader对象(如果是二进制模式)。
这些对象都实现了__enter__和__exit__方法,使得它们可以在with语句中使用。当你使用with open('path/to/file') as f:
语句时,解释器会自动调用f对象(文件对象)的__enter__方法获取相应的资源(打开文件), 然后在with块执行完毕后,自动调用__exit__方法释放资源(关闭文件)。
所以,<_io.TextIOWrapper name='mirror.py' mode='r' encoding='cp936'>这个对象就是一个上下文管理器。
它代表一个已打开的文本文件,在with语句块结束时会自动关闭文件并释放相关资源。
使用上下文管理器的一个主要优点是,即使在with块中发生了异常,上下文管理器对象的__exit__方法依然会被调用,这样可以保证资源一定会被正确释放,防止资源泄漏。
所以,你可以放心地将_io.TextIOWrapper对象视为一个上下文管理器对象,它会自动帮你管理文件资源的获取和释放。
7. python常用的所有的上下文管理器都列举出来,列举出来的形式如"TextIOWrapper"
8. sys.stdout.write的用法熟悉
sys.stdout.write是Python中用于向标准输出流(通常是终端或命令行窗口)写入字符串的函数。它是sys.stdout对象的一个方法,sys.stdout代表标准输出流。
使用方法:
sys.stdout.write(string) # only accpet string as input parameter
例如:
import sys sys.stdout.write('Hello, World!\n')
上面的代码会将'Hello, World!'字符串写入到标准输出流中,并加上一个换行符\n。在终端或命令行窗口中,你会看到输出:
Hello, World!
与print()函数不同, sys.stdout.write()不会自动添加换行符,你需要手动添加\n来实现换行。
另外,sys.stdout.write()只能接受字符串作为参数,如果你想要输出其他类型的对象,需要先将其转换为字符串。
通常情况下,我们更习惯使用print()函数来输出,因为它更加方便和直观。但是,sys.stdout.write()在某些情况下也有它的用武之地,例如当你需要精细控制输出格式,或者将输出重定向到文件等情况。
在上下文管理器的例子中,通过替换sys.stdout.write函数,可以拦截和修改所有写入标准输出流的操作,从而实现特殊的效果,如颠倒字符串顺序等。这展示了Python的强大灵活性。
9. 为什么sys.stdout.write被替换后,print()打印出来的就是逆序的,难道print()打印背后调用的是sys.stdout.write()方法?[from example18-2]
import sys class LookingGlass: def __enter__(self): # <1> self.original_write = sys.stdout.write # <2> sys.stdout.write = self.reverse_write # <3> return 'JABBERWOCKY' # <4> def reverse_write(self, text): # <5> self.original_write(text[::-1]) def __exit__(self, exc_type, exc_value, traceback): # <6> sys.stdout.write = self.original_write # <7> if exc_type is ZeroDivisionError: # <8> print('Please DO NOT divide by zero!') return True # <9> # <10> with LookingGlass() as what: print('Alice, Kitty and Snowdrop') print(what)
如上代码中,LookingGlass 类的 reverse_write 方法被设置为 sys.stdout.write 的替换。
这意味着任何调用 sys.stdout.write 的地方都会调用 reverse_write 方法。
reverse_write 方法接受一个字符串作为参数,并将该字符串的逆序写入到标准输出。
print 函数在 Python 中实际上是 print() 函数,它是一个内置函数,用于将文本输出到标准输出。print() 函数内部实际上是调用 sys.stdout.write 方法来写入文本的。
因此,当我在 with 语句块中调用 print('Alice, Kitty and Snowdrop') 时,由于 sys.stdout.write 已经被替换为 reverse_write,所以输出的文本是逆序的。
当您打印 what 对象时,它会打印出 'JABBERWOCKY',但由于 sys.stdout.write 已经被替换,输出也是逆序的。
所以,当 sys.stdout.write 被替换后,print() 打印出来的文本就是逆序的,因为 print() 函数内部实际上是调用 sys.stdout.write 方法来写入文本的。
10. try/except/finally语句的理解
在Python中,try/finally 语句是一种异常处理机制,它用于确保某些代码块中的资源(如文件、网络连接、数据库连接等)被正确释放或清理,无论是否发生异常。
概念和含义
- try 块:这是代码块的开始部分,放置了可能引发异常的代码。如果在这个块中发生异常,代码将跳过
try块中的剩余代码,并执行finally块。 - finally 块:这是代码块的结束部分,放置了在
try块执行完毕后无论是否发生异常都一定会执行的代码。即使try块中的代码没有引发异常,finally块也会执行。 - except 块(可选):这是用于捕获和处理异常的代码块。如果
try块中的代码引发了异常,并且该异常与except块中指定的异常类型匹配,那么except块中的代码将被执行。
通俗解释
想象你正在做一道复杂的菜,需要使用很多厨具和调料。在尝试烹饪的过程中,你可能会不小心打破一个碗或者不小心放错了调料。但是,无论发生什么,你都需要确保在烹饪完成后,所有的厨具和调料都被正确清洗和放回原位。
- try 块就像是烹饪过程,你在这里做菜,可能会遇到问题。
- finally 块就像是烹饪完成后,你需要确保所有的厨具和调料都被清理干净,无论烹饪过程是否顺利。
- except 块就像是处理烹饪过程中可能出现的特定问题,比如碗打破了或者调料放错了。
举例说明
try: # 打开文件 with open('example.txt', 'w') as file: file.write('Hello, world!') # 尝试进行一些可能会引发异常的操作 num = 10 / 0 except ZeroDivisionError: # 处理除以零的异常 print('Error: Division by zero is not allowed.') finally: # 无论是否发生异常,都确保文件被关闭 print('Cleaning up: Closing the file.')
在这个例子中,无论 try 块中的代码是否引发异常,finally 块中的代码都会被执行。这确保了文件在操作完成后被正确关闭,即使操作过程中出现了异常。
11. 真尾调用、尾调用优化的概念理解

浙公网安备 33010602011771号