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}

在这个示例中,我们创建了两个字典 dict1dict2,然后将它们合并成一个 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})

下面补充展示了 ChainMapmap 属性和 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 对象的属性字典,其中包含了 nameage 属性及其对应的值。

 

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语句在使用上下文管理器时的工作机制。它包含以下几个关键点:

  1. with后面的表达式必须返回一个上下文管理器对象, 这个对象必须实现__enter____exit__两个方法
  2. 当执行with语句时, Python会首先调用这个上下文管理器对象的__enter__方法。
  3. __enter__方法的返回值会被绑定到as子句中指定的目标变量上。
  4. with语句块内部,我们可以使用这个绑定的目标变量。
  5. 不论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对象,它是我们自定义的上下文管理器对象。

这里的表述有些不太准确,让我重新解释一下:

  1. CustomOpen('example.txt')这个表达式创建并返回一个CustomOpen类的实例对象。
  2. 这个CustomOpen实例对象才是真正的上下文管理器对象

所以,更精确的说法是:

with CustomOpen('example.txt') as file:语句后面的表达式CustomOpen('example.txt')创建并返回一个CustomOpen类的实例,这个实例对象就是我们自定义的上下文管理器对象

为什么一个类的实例对象能够作为上下文管理器对象呢? 这是因为我们在CustomOpen类中实现了__enter____exit__两个特殊的方法。

Python会自动识别实现了这两个方法的对象为上下文管理器对象, 并在with语句块执行前调用__enter__方法,执行后调用__exit__方法。

所以,正确的理解是:

  1. CustomOpen是一个普通的Python类
  2. CustomOpen('example.txt')创建并返回这个类的一个实例对象
  3. 这个实例对象由于实现了__enter____exit__方法,被Python识别为一个上下文管理器对象
  4. 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"

Python中常用的上下文管理器对象有:

  1. _io.TextIOWrapper - 文本模式下的文件对象
  2. _io.BufferedWriter - 二进制模式下的可写文件对象
  3. _io.BufferedReader - 二进制模式下的可读文件对象
  4. threading.Lock - 线程锁对象
  5. threading.RLock - 可重入线程锁对象
  6. multiprocessing.Lock - 进程锁对象
  7. multiprocessing.RLock - 可重入进程锁对象
  8. contextlib.ExitStack - 用于动态管理上下文管理器堆栈
  9. wave.Wave_write - 用于写入Wave音频文件
  10. zipfile.ZipFile - 用于读写ZIP压缩文件
  11. gzip.GzipFile - 用于读写Gzip压缩文件
  12. bz2.BZ2File - 用于读写BZ2压缩文件
  13. lzma.LZMAFile - 用于读写LZMA压缩文件
  14. decimal.localcontext - 用于管理小数点计算上下文
  15. warnings.catch_warnings - 用于捕获和处理警告
  16. pytest.raises - 用于断言异常(在pytest测试框架中)

这些都是常见的实现了上下文管理器协议的对象类型,可以使用with语句自动获取和释放相应的资源。当然还有很多第三方库中定义的上下文管理器,数量较多,这里就不一一列举了。

 

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. 真尾调用、尾调用优化的概念理解

 

posted @ 2024-04-11 15:21  AlphaGeek  阅读(18)  评论(0)    收藏  举报