textwrap.dedent() 是处理多行字符串缩进的标准方法,按首行非空行缩进基准去除每行前缀空白,不破坏内部缩进,且兼容混合缩进;需配合 抑制首行换行,避免开头多余 。

用 textwrap.dedent() 去掉多行字符串首行缩进
python 多行字符串(""" 或 ''')写在缩进代码块里时,字符串内容会把缩进一起带上,导致输出意外空格。这不是 bug,是语法定义行为——字符串字面量原样保留换行和空白。
最稳妥的解法是用标准库 textwrap.dedent(),它按首行非空行的缩进基准来剪掉每行前缀:
import textwrap <p>def get_sql(): return textwrap.dedent(""" SELECT id, name FROM users WHERE active = true """)
dedent() 不会动首行(所以用 换行避免首行空行),也不影响内部缩进(比如 SQL 里的 WHERE 缩进仍保留)。它只处理“公共前导空白”,对混合缩进(空格+制表符)也健壮。
常见错误:直接用 .strip() 或 .lstrip() ——它们只去首尾空白,不解决中间行缩进问题;或者手动切片,容易错位、难维护。
立即学习“Python免费学习笔记(深入)”;
用括号拼接避免反斜杠续行和缩进污染
如果字符串本身不需换行语义(比如长 URL、json 片段),与其硬塞进三引号再 dedent,不如用括号隐式拼接:
url = ( "https://api.example.com/v2/items?" "page=1&" "limit=100&" "sort=created_at" )
这样写:
- 每行可独立缩进,不带入字符串内容
- 无须导入
textwrap - 编辑器自动格式化友好(Black、Ruff 都支持)
- 调试时打印出来就是干净一行,没换行符干扰
注意:不能混用 + 拼接——那会生成多个临时字符串对象,有性能开销;括号拼接是编译期行为,零运行时成本。
""" 字符串里用 抑制首行换行
三引号字符串默认把开头的换行也吃进去,导致结果开头多一个 。比如:
s = """hello world""" # s[0] 是 ' '
实际想让 hello 紧贴变量赋值位置,就用 把首行换行干掉:
s = """ hello world""" # s[0] 是 'h'
这个技巧轻量、无依赖,适合简单场景。但别滥用:如果后面还要 dedent, 就多余;如果字符串里本身要表达反斜杠,得写成 ,易读性下降。
容易踩的坑:只加 却忘了 dedent,或反过来用了 dedent 却没压首行换行,结果开头或每行都多出空格。
模板字符串中缩进更麻烦,优先用 jinja2 或 String.Template
纯 Python 的 f-string 和 .format() 无法自动处理三引号里的缩进逻辑。一旦插入变量,dedent 就得放在插值之后,容易漏调或顺序错:
# 错误示范:dedent 在 format 前,变量值可能带缩进 template = textwrap.dedent(""" Name: {name} Age: {age} """).format(name="Alice", age=30) <h1>正确做法:先插值,再 dedent(如果需要)</h1><p>template = textwrap.dedent(f""" Name: {name} Age: {age} """)
更复杂场景(比如条件分支、循环)建议直接上 jinja2:Template 默认 trim_blocks/lstrip_blocks 可关掉首行/行首空白;string.Template 虽简单,但不支持逻辑,缩进全靠人工对齐。
关键点:缩进处理不是“写完再修”,而是从字符串定义方式开始选型——三引号 + dedent 适合静态内容,括号拼接适合线性文本,模板引擎适合动态结构。