You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上下文协议的对象,帮你隐藏了生成器的手动调用逻辑。

具体执行步骤拆解

  1. 进入with语句时:
    当执行with basic_body(a, ...):,Python会触发上下文管理器的__enter__方法——这个方法内部会调用生成器的next(),让basic_body从开头执行,直到碰到yield语句后暂停,把控制权交给with块内的代码(也就是你编写的填充main区域的article内容)。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 01:53:12