Python中yield为何无需二次调用即可恢复?上下文管理器解析
为什么用
@contextlib.contextmanager的yield无需二次调用就能恢复执行? 先看你提供的代码:
# file: your_app/views.py import contextlib import inspect from airium import Airium from django.http import HttpResponse @contextlib.contextmanager def basic_body(a: Airium, useful_name: str = ''): """Works like a Django/Ninja template.""" a('<!DOCTYPE html>') with a.html(lang='en'): with a.head(): a.meta(charset='utf-8') a.meta(content='width=device-width, initial-scale=1', name='viewport') # do not use CSS from this URL in a production, it's just for an educational purpose a.link(href='https://unpkg.com/@picocss/pico@1.4.1/css/pico.css', rel='stylesheet') a.title(_t=f'Hello World') with a.body(): with a.div(): with a.nav(klass='container-fluid'): with a.ul(): with a.li(): with a.a(klass='contrast', href='./'): a.strong(_t="⌨ Foo Bar") with a.ul(): with a.li(): a.a(klass='contrast', href='#', **{'data-theme-switcher': 'auto'}, _t='Auto') with a.li(): a.a(klass='contrast', href='#', **{'data-theme-switcher': 'light'}, _t='Light') with a.li(): a.a(klass='contrast', href='#', **{'data-theme-switcher': 'dark'}, _t='Dark') with a.header(klass='container'): with a.hgroup(): a.h1(_t=f"You're on the {useful_name}") a.h2(_t="It's a page made by our automatons with a power of steam engines.") with a.main(klass='container'): yield # This is the point where main content gets inserted with a.footer(klass='container'): with a.small(): margin = 'margin: auto 10px;' a.span(_t='© Airium HTML generator example', style=margin) # do not use JS from this URL in a production, it's just for an educational purpose a.script(src='https://picocss.com/examples/js/minimal-theme-switcher.js') def index(request) -> HttpResponse: a = Airium() with basic_body(a, f'main page: {request.path}'): with a.article(): a.h3(_t="Hello World from Django running Airium") with a.p().small(): a("This bases on ") with a.a(href="https://picocss.com/examples/company/"): a("Pico.css / Company example") with a.p(): a("Instead of a HTML template, airium has been used.") a("The whole body is generated by a template " "and the article code looks like that:") with a.code().pre(): a(inspect.getsource(index)) return HttpResponse(bytes(a)) # from django.http import HttpResponse
你疑惑的点在于:明明yield是生成器的语法,为什么这里不需要手动调用生成器方法(比如next())就能恢复执行?下面是具体原理:
核心原因:@contextlib.contextmanager封装了生成器的执行流程
这个装饰器的作用就是把带yield的函数,自动转换成符合Python上下文协议的对象,帮你隐藏了生成器的手动调用逻辑。
具体执行步骤拆解
进入
with语句时:
当执行with basic_body(a, ...):,Python会触发上下文管理器的__enter__方法——这个方法内部会调用生成器的next(),让basic_body从开头执行,直到碰到yield语句后暂停,把控制权交给with块内的代码(也就是你编写的填充main区域的article内容)。with块代码执行完毕后:
当with块里的所有代码执行完成,Python会触发上下文管理器的__exit__方法——这个方法内部会再次调用生成器的next(),让basic_body恢复执行,跑完yield之后的所有代码(也就是生成footer、主题切换脚本的部分),最终完成完整的HTML结构。
和普通生成器的区别
普通生成器函数调用后返回生成器对象,必须手动调用next()或send()才能推进执行;但@contextlib.contextmanager帮你做了封装:
__enter__方法负责启动生成器到yield暂停;__exit__方法负责恢复生成器执行剩余代码。
结合你的代码理解
在basic_body中,yield正好卡在<main>标签内部:
- 进入
with块时,函数已经生成了HTML头部、导航、header部分,停在<main>内部等待填充; with块里的代码往Airium对象里添加主体内容;with块结束后,函数继续生成footer和脚本,最终拼接出完整的HTML页面。
内容的提问来源于stack exchange,提问作者arennuit
相关产品推荐
相关产品推荐

