Fluent Python2 【Chapter20_QA】
1. 方法阻塞的概念理解
在 Python 中,方法阻塞指的是当调用某个方法时,当前线程会被暂时挂起,直到该方法执行完成或者达到某个条件才会继续执行后续代码。在阻塞状态下,程序会等待某个操作完成,期间无法执行其他任务。
通俗地说,就像你在餐厅排队点餐一样。当你排到自己的位置时,你会向服务员点菜,然后等待厨师烹饪完成,才能继续前进,这段时间你不能做其他事情,就像是被“阻塞”了一样。
以下是阻塞和非阻塞的代码示例:
阻塞示例:
import time def slow_task(): print("开始执行慢速任务...") time.sleep(3) # 模拟耗时操作,阻塞了3秒钟 print("慢速任务执行完成!") print("开始执行程序...") slow_task() print("程序执行完毕!")
在上面的代码中,调用 slow_task() 方法会阻塞当前线程 3 秒钟,直到任务执行完成后才会继续执行后续代码。
非阻塞示例:
import threading import time def slow_task(): print("开始执行慢速任务...") time.sleep(3) # 模拟耗时操作,阻塞了3秒钟 print("慢速任务执行完成!") print("开始执行程序...") t = threading.Thread(target=slow_task) t.start() # 启动一个新的线程执行慢速任务,不会阻塞主线程 print("程序继续执行,不受慢速任务阻塞影响!")
在这个示例中,我们通过创建一个新的线程来执行耗时的慢速任务 slow_task(),主线程可以继续执行后续代码,不会被任务阻塞。
2. python中print()函数的flush=True, False(默认)的理解
原理:
print() 函数会把内容放到内存中, 内存中的内容并不一定能够及时刷新显示到屏幕中(应该是要满足某个条件,这个条件现在还不清楚)。 使用flush=True之后,会在print结束之后,不管你有没有达到条件,立即将内存中的东西显示到屏幕上,清空缓存。
使用场景:
1.尤其是在while循环中,要想每进行一次while循环体,在屏幕上更新打印的内容就得使用flush = True的参数。
2. 打开一个文件, 向其写入字符串, 在关闭文件f.close()之前, 打开文件是看不到写入的字符的。 要想在关闭之前实时的看到写入的字符串,应该用flush = True.
参考:python print(xx, flush = True) 全网最清晰的解释
3. 我们常说的服务器,其实是指电脑,但是python开发中经常提到的服务器,应该不是电脑硬件这块的含义把?我理解是不是类似软件更多点,比如nginx, http.server类似这些
您的理解是正确的。在计算机科学和网络技术中,服务器通常指的是一个硬件设备或软件服务,用于提供数据、文件、网络资源或服务。在Python开发中,当我们提到服务器时,我们通常指的是软件服务器,这些服务器软件运行在物理或虚拟的服务器硬件上。
例如,Nginx是一个高性能的HTTP和反向代理服务器,它广泛用于Web服务器、负载均衡器、邮件代理服务器和通用TCP/UDP代理服务器。而http.server是Python标准库中提供的一个简单的HTTP服务器,用于测试或简单的Web服务。
在Python开发中,我们经常使用服务器软件来托管我们的应用程序,例如:
- Web服务器:如Nginx、Apache、Gunicorn等,它们处理HTTP请求,并返回Web页面或API响应。
- 应用服务器:如Django的WSGI服务器、Flask的Werkzeug服务器,它们运行Python应用程序,处理业务逻辑。
- 数据库服务器:如MySQL、PostgreSQL、MongoDB等,它们存储和检索数据。
- 消息队列服务器:如RabbitMQ、Kafka等,它们用于异步消息传递。
- 文件服务器:如FTP服务器、Samba服务器等,它们提供文件共享服务。
这些服务器软件可以是独立的应用程序,也可以是集成在更大的软件栈中,如使用Docker、Kubernetes等容器化技术或微服务架构。
因此,在Python开发中,我们所说的服务器通常指的是这些软件服务器,而不是指物理硬件设备。
4. 如何理解对于如下代码<3>解释的这段话 [flags_threadpoool.py]
实例化ThreadPoolExecutor, 作为上下文管理器。executor.__exit__方法将调用executor.shutdown(wait=True), 在所有线程都执行完毕前阻塞线程。???
from concurrent import futures from flags import save_flag, get_flag, main # <1> def download_one(cc: str): # <2> image = get_flag(cc) save_flag(image, f'{cc}.gif') print(cc, end=' ', flush=True) return cc def download_many(cc_list: list[str]) -> int: with futures.ThreadPoolExecutor() as executor: # <3> 如何理解? res = executor.map(download_one, sorted(cc_list)) # <4> return len(list(res)) # <5> if __name__ == '__main__': main(download_many) # <6>
让我来解释一下这句话的意思:
with futures.ThreadPoolExecutor() as executor:
ThreadPoolExecutor是 Python 的concurrent.futures模块中的一个类,用于创建一个线程池,以便执行并发任务。
当你使用with语句创建一个ThreadPoolExecutor对象时,它实际上是一个上下文管理器。这意味着它实现了__enter__和__exit__方法。
在__enter__方法中,它会创建并返回一个ThreadPoolExecutor实例。这就是为什么在with语句中,你可以使用executor变量来表示这个线程池实例。
关键是在__exit__方法中,它会做一些清理工作。具体来说,它会调用executor.shutdown(wait=True)。
executor.shutdown(wait=True)的作用是:
- 阻止新的任务被提交到线程池
- 等待所有当前正在执行的任务完成
- 终止所有空闲线程
换句话说,当你离开with语句块时,__exit__方法会确保所有任务都已经执行完毕,并且所有线程都已经被正确终止和清理。这可以避免资源泄漏和其他潜在的问题。
所以,这句话的意思是:当你使用with futures.ThreadPoolExecutor() as executor:语句时,你实例化了一个ThreadPoolExecutor作为上下文管理器。
当你离开with块时,上下文管理器的__exit__方法会调用executor.shutdown(wait=True)来阻塞线程,直到所有任务都执行完毕,然后再终止所有线程。(阻塞就是为了等待其他线程执行完毕,阻塞的理解参考问题1)
这种机制确保了线程池在使用完毕后得到了正确的清理和终止,避免了资源泄漏和其他潜在问题。
或者也可以这样理解:
当使用 with 语句与 ThreadPoolExecutor 上下文管理器一起工作时,executor.__enter__() 方法被调用来创建和管理线程池。在 executor.__exit__() 方法被调用之前,所有的任务都应该已经提交给线程池,并且正在执行。
executor.__exit__() 方法调用 executor.shutdown(wait=True) 的目的是为了等待所有提交的任务执行完成。这是为了确保所有任务都得到正确处理,并且线程池中的线程在执行完所有任务后能够安全地关闭。
如果不等待所有任务完成就关闭线程池,可能会导致某些任务没有得到处理,或者资源没有正确释放。
因此,executor.shutdown(wait=True) 需要阻塞线程,直到所有任务都执行完毕。这是因为 shutdown 方法需要确保所有任务都已经完成,这通常需要等待线程执行完毕。
如果您不需要等待所有任务完成,您可以使用 shutdown(wait=False) 方法。这将立即关闭线程池,而不等待任何正在执行的任务。但是,这种做法可能会导致资源泄露或任务失败,因此在大多数情况下,建议使用 wait=True。
5. try/except/else结构中,else什么时候执行,什么时候不执行
在Python中,try/except/else 结构是一种异常处理机制,用于捕获并处理程序执行中可能出现的异常。
else 语句的执行时机
- else 语句:在
try块中的代码没有抛出任何异常的情况下,else块中的代码将会执行。 - else 语句:在
try块中的代码抛出异常并被except块捕获之后,else块中的代码不会执行。 - else 语句:在
try块中的代码抛出异常但未被任何except块捕获时,else块中的代码也不会执行。
通俗解释
想象一下,你正在尝试打开一个文件,但是你不知道这个文件是否存在。你可能会这样做:
- 尝试打开文件。
- 如果文件不存在,抛出一个异常。
- 如果文件存在,执行一些操作。
try: file = open('example.txt', 'r') content = file.read() print(content) except FileNotFoundError: print('File not found.') else: print('File was found and read.')
6. 为什么如下代码中,await coro就可以得到result了?不需要coro.result()么?如何理解逐个as_complete()的作用,能处理什么问题,输入输出又是什么?
import asyncio import socket from collections.abc import Iterable, AsyncIterator from typing import NamedTuple, Optional class Result(NamedTuple): # <1> domain: str found: bool OptionalLoop = Optional[asyncio.AbstractEventLoop] # <2> async def probe(domain: str, loop: OptionalLoop = None) -> Result: # <3> if loop is None: loop = asyncio.get_running_loop() try: await loop.getaddrinfo(domain, None) except socket.gaierror: return Result(domain, False) return Result(domain, True) async def multi_probe(domains: Iterable[str]) -> AsyncIterator[Result]: # <4> loop = asyncio.get_running_loop() coros = [probe(domain, loop) for domain in domains] # <5> for coro in asyncio.as_completed(coros): # <6> result = await coro # <7> yield result # <8>
在如上的代码中,await coro可以直接获取result的原因是因为coro本身就是一个awaitable对象,即一个协程对象。当我们使用await语句时,Python会自动获取协程对象的结果,无需显式调用coro.result()。
至于asyncio.as_completed()的作用,它是一个用于并发执行多个协程任务,并获取它们结果的高级工具。让我们逐步分析一下这段代码:
coros = [probe(domain, loop) for domain in domains]创建了一个协程对象列表,每个协程对象代表一个probe()任务。asyncio.as_completed(coros)会并发地运行所有coros中的协程任务,并返回一个AsyncIterator对象。- 在
for coro in asyncio.as_completed(coros):循环中,每次迭代都会获取一个已经完成的协程任务coro。 - 使用
await coro可以直接获取该协程任务的结果, 无需调用coro.result()。 - 将获取到的结果
yield出来,供multi_probe()函数的调用者使用。
总的来说,asyncio.as_completed()的作用是并发地运行多个协程任务,并在它们完成时一一返回结果,而不需要等待所有任务都完成。这在处理多个独立的I/O操作时非常有用,可以充分利用异步编程的优势,提高效率。
举个例子,如果我们需要并行探测100个域名是否可访问,使用asyncio.as_completed()可以让所有探测任务同时进行,而不是一个一个地等待。这样,即使某些域名探测时间较长,也不会影响整体的效率。
输入输出方面:
- 输入是一个可迭代对象
domains,包含了需要探测的域名列表。 - 输出是一个
AsyncIterator对象,每次迭代都会产出一个Result对象,包含探测结果(域名和是否可访问)。
通过asyncio.as_completed()的高级功能,我们可以方便地实现并发探测,充分利用异步编程的优势,提高整体效率。
7. 追问:什么时候需要用到.result()方法呢?请举例说明,之前在其他代码看到过有用.result()获取协程执行的结果

浙公网安备 33010602011771号