前言
先来看下面的代码:
acquire方法如下
这两段代码,就是我今天想和大家分享的三个知识点:
- with
- yield
- @contextmanager
下面,我们一起来学习这三个知识点
一、yield
1. 定义
yield
是 Python 中一个关键字,用于构造一个生成器函数。
生成器函数是一种特殊的函数,它可以在执行过程中多次返回一个值,每次返回后会暂停执行,然后再次调用时继续执行。生成器函数一般使用 yield
语句来实现这种功能。
yield
语句在生成器函数中的用法类似于 return
语句。不同之处在于,yield
会将值返回给调用者,但不会终止函数的执行。相反,它会暂停函数的执行,保存当前的状态,并在稍后恢复执行。
2. 示例一
import time
def generate_numbers():
yield 1
yield 2
yield 3
return "done"
def run_generator_numbers():
gen = generate_numbers()
try:
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
except StopIteration as e:
print(e.value)
if __name__ == '__main__':
run_generator_numbers()
这段代码是关于生成器的使用示例。首先,我们定义了一个生成器函数generate_numbers(),它使用yield语句来生成数字序列1, 2, 3。然后,在run_generator_numbers()函数中,我们创建了一个生成器对象gen,并尝试使用next()函数来逐个获取生成器中的值。
首先,我们调用next(gen)获取生成器中的第一个值1,并打印出来。接着,我们再次调用next(gen)获取生成器中的第二个值2,并打印出来。然后,我们再次调用next(gen)获取生成器中的第三个值3,并打印出来。
在这之后,我们尝试再次调用next(gen)来获取生成器中的下一个值。但是,由于生成器中没有更多的值可供获取,所以会抛出StopIteration异常。我们通过捕获该异常,并打印出异常的value属性(即生成器函数中的返回值)来结束生成器的运行。
看下效果:
2. 示例二
import time
def generate_loop():
i = 0
# 打印当前时分秒
while i < 5:
now = time.strftime("%H:%M:%S", time.localtime())
yield now
time.sleep(1)
i += 1
return "done"
def run_generator_loop():
gen = generate_loop()
j = 0
try:
while True:
j += 1
print("迭代输出第" + str(j) + "次 " + next(gen))
except StopIteration as e:
print(e.value)
if __name__ == '__main__':
run_generator_numbers()
结果如下
4. 对yield总结
由此我们可以得出结论
1)使用next获取生产器中的值的时候,程序会一直等待生成器函数generate_loop 执行到 yield now 这行,并给出结果,run_generator_loop里的while true 才会开始新一轮循环
2)生成器函数会将所有的 yield 都执行完成,才会往下走到return语句,并以异常的值来返回
二、with
1. 简介
在我们编程的时候,经常会遇到打开数据库或者读取文件等的场景。印象最深的就是书上一般介绍在操作完数据库或文件后必须手动关闭连接,或者文件。这样代码会比较臃肿,而且大家经常忘记写。
Python提供的with语句是一种方便的方式,用于处理资源的管理和清理。通过使用with语句,你可以确保在代码块中使用资源后,它们会被自动释放,而不需要手动关闭或清理资源。这对于文件操作、网络连接等需要手动关闭或清理的情况特别有用。
2.示例
with后面必须跟着上下文对象(例如数据库连接,文件打开后的文件对象)。上下文对象必须定义__enter__()
和__exit__()
方法,以便在进入和离开with块时执行特定的操作。看下面的例子。
import os
class OpenFile:
def __init__(self, file_name, mode):
self.file_name = file_name
self.mode = mode
self.file_object = None
def __enter__(self):
if not os.path.exists(self.file_name):
self.mode = "w+"
self.file_object = open(self.file_name, self.mode)
return self.file_object
def __exit__(self, exc_type, exc_value, traceback):
if self.file_object:
self.file_object.close()
# 使用示例
filename = 'example.txt'
# 读取文件内容
with OpenFile(filename, 'r') as file:
try:
content = file.read()
print(content)
except FileNotFoundError:
print(f"File {filename} not found.")
# 文件写入示例
with OpenFile(filename, 'w+') as file:
file.write('Hello, World2!')
# 重新读取文件内容
with OpenFile(filename, 'r') as file:
content = file.read()
print(content)
看结果:
三、@contextmanager
1. 疑惑
大家看到上面的with的用法之后,会感觉它并没有让我们的编程变得简单,多出了两个要定义的函数,很麻烦。那这时候我们就可以使用@contextmanager
这个装饰器 搭配 yield
来简化它
2. 示例代码
@contextmanager
注解可以自动帮我们实现__enter__()
和__exit__()
方法,因此被它装饰的方法可以直接被with 使用。那大家就开始疑惑了,怎么自动实现呢?
这时候就需要搭配yield
。还记得它的特性吗?在它这一行没有执行完成之前,会阻塞住使用它的方法。因此,我们可以把上下文对象,通过它来给到with,例如代码里的,就是通过yield返回给with的
file_object = open(file_name, mode)
yield file_object
完整实例:
import os
from contextlib import contextmanager, ExitStack
from contextlib import contextmanager
@contextmanager
def open_file(file_name, mode):
file_object = None
try:
# 判断如果文件不存在就创建
if not os.path.exists(file_name):
mode = "w+"
file_object = open(file_name, mode)
yield file_object
except FileNotFoundError:
print(f"File {filename} not found.")
finally:
if file_object:
file_object.close()
# 使用示例
filename = 'example.txt'
# 读取文件内容
with open_file(filename, 'r') as file:
try:
content = file.read()
print(content)
except FileNotFoundError:
print(f"File {filename} not found.")
# 文件写入示例
with open_file(filename, 'w+') as file:
file.write('Hello, World!')
# 重新读取文件内容
with open_file(filename, 'r') as file:
content = file.read()
print(content)
总结
看完了这一节,大家可以再去看看开头的那两段代码,是不是简单多了呢?