2025-05-08 08:00:00
annotationlib
模块提供了灵活的类型提示检查方式# Python 3.14 - 不再需要引号,即使类型未定义
>>> from annotationlib import get_annotations, Format
>>> def new_way(arg: Undefined): # 直接使用未定义类型
... pass
# Python 3.13 及之前版本需要这样写
>>> def old_way(arg: "Undefined"): # 必须加引号,否则报错
... pass
# 可以用不同格式查看类型提示
>>> get_annotations(new_way, format=Format.STRING) # 字符串形式
{'arg': 'Undefined'}
>>> get_annotations(new_way, format=Format.FORWARDREF) # 前向引用对象形式
{'arg': ForwardRef('Undefined', owner=<function new_way at 0x...>)}
--with-tail-call-interp
参数。1
python -m pdb -p 1234 # 直接连接到PID为1234的Python进程
_Py_DebugOffsets
中,调试工具可以直接访问PyThreadState
中新加了外部调试支持结构,可以注入调试脚本sys.remote_exec()
功能可以检查和控制运行中的进程f
一样,这种新字符串前面加 t
。from string.templatelib import Template
name = "World"
template = t"Hello {name}" # 返回一个 Template 对象,而不是字符串
Template
类型的对象。有了这个对象,我们就能对模板内容做各种安全处理,比如 HTML 转义和 SQL 注入防护:from string.templatelib import Template, html
# 有恶意代码
evil = "<script>alert('evil')</script>"
# 用模板处理,防止 XSS 攻击
template = t"<p>{evil}</p>"
# 使用 html() 函数转义危险字符
html_safe = html(template)
assert html_safe == "<p><script>alert('evil')</script></p>"
compression.zstd
模块,正式把 Zstandard 压缩格式纳入标准支持。这种由 Meta 公司开发的格式既压得小又压得快,甚至超过很多老牌压缩算法,非常适合处理大数据和网络通信场景。tarfile
、zipfile
和 shutil
模块,现在都能直接处理 Zstandard 压缩包了。而原有的 lzma
、bz2
等压缩模块也被整合到新的 compression
包下,统一了访问方式。// 看看调试模式是否开启
int debug_enabled = _PyConfig_GetBool(config, "debug");
// 指定模块搜索路径
_PyConfig_SetString(config, "pythonpath", "/custom/path");
# 设置环境变量开启 JIT
PYTHON_JIT=1 python3.14 your_script.py
sys._jit
模块检查 JIT 状态:import sys
if hasattr(sys, "_jit"):
print(f"JIT状态: {"启用" if sys._jit.is_enabled() else "未启用"}")
else:
print("JIT不支持")
PYTHON_BASIC_REPL
环境变量。finally
块里用 return
、break
和 continue
跳出去。这种写法以后会被视为语法错误。finally
中的跳转语句通常会导致代码难懂、难维护,容易把异常情况给吞掉,造成莫名其妙的问题。# Python 3.13 允许这种危险写法,但异常会被吞
try:
raise ValueError("重要错误")
except Exception as e:
print(f"捕获到错误: {e}")
raise
finally:
# 这里写 return 会打断异常传递,调用方看不到原始错误
return "似乎一切正常"
# Python 3.14 中,上述代码将在语法层面被禁止
try:
operation()
except ValueError, TypeError: # 可以用逗号分隔多个异常类型
print("处理值错误或类型错误")
# 对异常组也适用同样的语法
try:
operation()
except* ValueError, TypeError: # 异常组也可以用逗号分隔
print("处理值错误或类型错误")
除了 macOS 和 Windows 外,其它平台上 multiprocessing
和 ProcessPoolExecutor
的默认启动方式从 fork
改成了 forkserver
。这能避免一些多线程的问题,但可能要修改依赖于旧行为的代码。
CPython 解释器优化了引用计数机制,去掉了一些不必要的计数更新,所以 sys.getrefcount()
和 Py_REFCNT()
返回的数字可能和以前不一样了。
PEP 738 修改了 async/await 关键字的解析规则,一些极端情况下可能造成不兼容。
distutils
模块在 3.12 中已经被强烈警告,3.14 版本会完全移除。如果还在使用,应该迁移到 setuptools
或 sysconfig
。
imp
模块所有标记为弃用的函数已经删除,应改用 importlib
。
asyncio
模块中的一些废弃函数如 @coroutine
装饰器已被移除,应该用 async/await
语法替代。
XML 模块默认开启了更严格的安全限制,可能会导致原有较宽松的解析器设置失效。
SSL/TLS 模块默认禁用了某些过时的加密算法和协议版本,对于需要与旧系统通信的应用可能要额外配置。
2025-05-05 08:00:00
uv
是一个用 Rust 开发的超高性能 Python 包管理器。pip
工作流的即插即用替代方案。(译注:uv 在 2024.02 发布,本文写于 2024.08,翻译于 2025.05)pip
替代品扩展成为一个端到端的解决方案,可用于管理 Python 项目、命令行工具、单文件脚本,甚至 Python 本身。Cargo
:提供了一个快速、可靠且易用的统一接口。pip
工作流的即插即用替代方案。uv run
、uv lock
和 uv sync
。uv 现在能基于标准元数据创建跨平台的锁文件,并利用该文件来安装依赖。它是 Poetry、PDM 和 Rye 等工具的高性能替代品。uv tool install
和 uv tool run
(别名为 uvx
)。uv 能在隔离的虚拟环境中安装命令行工具,还能无需先安装就直接执行命令(如 uvx ruff check
)。它是 pipx
等工具的高性能替代品。uv python install
。uv 现在可以直接帮你安装 Python,替代 pyenv
等工具。uv run
命令就能执行这些独立的 Python 脚本。uv pip
(我们将它作为一等功能,继续维护并改进),uv 适用于任何 Python 工作流,从一次性脚本到大型的多包工作区开发。curl -LsSf https://astral.sh/uv/install.sh | sh
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
pip install uv
pipx install uv
[project]
name = "hello-world"
version = "0.1.0"
readme = "README.md"
dependencies = ["fastapi>=0.112"]
[[package]]
name = "fastapi"
version = "0.112.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2c/09/71a961740a1121d7cc90c99036cc3fbb507bf0c69860d08d4388f842196b/fastapi-0.112.1.tar.gz", hash = "sha256:b2537146f8c23389a7faa8b03d0bd38d4986e6983874557d95eed2acc46448ef", size = 291025 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/b0/0981f9eb5884245ed6678af234f2cbcd40f44570718caddc0360bdb4015d/fastapi-0.112.1-py3-none-any.whl", hash = "sha256:bcbd45817fc2a1cd5da09af66815b84ec0d3d634eb173d1ab468ae3103e183e4", size = 93163 },
]
[[package]]
name = "fastapi-cli"
version = "0.0.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typer" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/c5/f8/1ad5ce32d029aeb9117e9a5a9b3e314a8477525d60c12a9b7730a3c186ec/fastapi_cli-0.0.5.tar.gz", hash = "sha256:d30e1239c6f46fcb95e606f02cdda59a1e2fa778a54b64686b3ff27f6211ff9f", size = 15571 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/24/ea/4b5011012ac925fe2f83b19d0e09cee9d324141ec7bf5e78bb2817f96513/fastapi_cli-0.0.5-py3-none-any.whl", hash = "sha256:e94d847524648c748a5350673546bbf9bcaeb086b33c24f2e82e021436866a46", size = 9489 },
]
uv lock
,uv 仍然会为 Linux 和 Windows 生成解析方案,即使每个平台上所需的依赖集稍有不同。uv run
命令,它能在项目专属环境中运行命令,而且不需要手动激活虚拟环境。uv run
非常快速。每次执行时,它都会自动重新锁定和同步项目,确保你的环境始终是最新状态。完全不需要手动干预。uv run
能确保你的命令每次都在一个一致的、受锁文件管理的环境中运行。uv run
命令启动 FastAPI 应用的示例。uv run
,你再也不需要操心虚拟环境的激活、依赖包的管理或者项目的维护更新。一切都变得无比简单。本文翻译并首发于 Python猫:uv:统一的 Python 包管理
project.dependencies
部分,你可以定义项目的依赖和可发布的元数据。而在开发过程中,uv 还允许你通过 tool.uv.sources
为这些依赖项指定替代来源。anyio
,你可以运行 uv add --editable ../anyio
来生成以下 pyproject.toml
:[project]
name = "hello-world"
version = "0.1.0"
readme = "README.md"
dependencies = ["anyio"]
[tool.uv.sources]
anyio = { path = "../anyio", editable = true }
anyio
的依赖。但在本地开发时,使用 uv run
可以将 ../anyio
路径下的可编辑的包加到开发环境中。这样,项目的依赖定义保持不变,只是依赖的来源发生了变化。pyproject.toml
,但工作区共享一个锁文件,确保工作区使用一致的依赖集运行。[project]
name = "fastapi"
version = "0.1.0"
readme = "README.md"
dependencies = ["uvicorn"]
[tool.uv.sources]
uvicorn = { workspace = true }
[tool.uv.workspace]
members = ["libraries/*"]
uv run --package fastapi
或 uv run --package uvicorn
。本文翻译并首发于 Python猫:(https://pythoncat.top/posts/2025-05-05-uv)
uv tool install
在专用的隔离虚拟环境中安装命令行工具(如 Ruff)uvx
直接运行一次性命令,无需预先安装uvx posting
就能直接运行 Darren Burns 开发的 posting
终端界面程序(TUI)。uvx
命令在终端中运行 posting 的示例。uv tool list
查看所有已安装的工具及其可执行文件uv tool upgrade --all
将所有工具升级到最新版本uvx
执行命令几乎没有任何额外开销。$ curl -LsSf https://astral.sh/uv/install.sh | sh
$ uv python install 3.12
uv python
外,uv 还可以根据需要,自动下载所需的 Python 版本(如 uv run
、uv venv
等命令)。$ apt-get update && apt-get install -y curl
$ curl -LsSf https://astral.sh/uv/install.sh | sh
$ source $HOME/.cargo/env
$ uvx posting
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
requests
和 rich
,但不包含任何显式的依赖元数据。早些时候,你需要单独管理这些依赖项,例如使用专门的 requirements.txt 文件。uv add
自动将依赖声明嵌入到脚本中:$ uv add --script main.py "requests<3" "rich"
$ cat main.py
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
uv run main.py
将在一个隔离的、临时的虚拟环境中执行脚本,并安装所有依赖项。--with
标记在运行时指定依赖项,例如:uv run --with "requests<3" --with rich main.py
。2025-05-03 08:00:00
2025-05-02 08:00:00
__init__
方法存在的问题和新的最佳实践,第 99 期也分享了一篇文章佐证了第一篇文章的观点。我认为它们提出的是一个值得注意和思考的问题,因此将第一篇文章翻译成了中文。原作:Glyph
译者:豌豆花下猫@Python猫
原题:Stop Writing
__init__
Methods原文:https://blog.glyph.im/2025/04/stop-writing-init-methods.html
__init__
特殊方法有着重要的用途。如果你有一个表示数据结构的类——例如带有 x
和 y
属性的 2DCoordinate
——你如果想通过 2DCoordinate(x=1, y=2)
这样的方式构造它,就需要添加一个带有 x
和 y
参数的 __init__
方法。2DCoordinate
从公共 API 中移除,转而暴露一个 make_2d_coordinate
函数并使其不可导入,但这样你该如何在文档体现返回值或参数类型呢?x
和 y
属性并让用户自己分别赋值,但这样 2DCoordinate()
就会返回一个无效的对象。2DCoordinate
对象不仅是可变的,而且在每个调用点都必须被修改。typing.Protocol
直到 Python 3.8 才出现,所以在 3.7 之前的版本中,这会迫使你使用具体的继承并声明多个类,即使对于最基本的数据结构也是如此。__init__
方法并没有什么明显的问题,所以在这种情况下它是一个不错的选择。考虑到我刚才描述的所有替代方案的问题,它在大多数情况下成为了明显的默认选择,这是有道理的。__init__
“作为用户创建对象的默认方式,我们养成了一个习惯:在每个类的开头都放上一堆可以随意编写的代码,这些代码在每次实例化时都会被执行。FileReader
。fileio
模块中:open(path: str) -> int
read(fileno: int, length: int)
close(fileno: int)
fileio.open
返回一个表示文件描述符的整数【注1】,fileio.read
从打开的文件描述符中读取 length
个字节,而 fileio.close
则关闭该文件描述符,使其失效。__init__
方法所形成的思维习惯,我们可能会这样定义 FileReader
类:class FileReader:
def __init__(self, path: str) -> None:
self._fd = fileio.open(path)
def read(self, length: int) -> bytes:
return fileio.read(self._fd, length)
def close(self) -> None:
fileio.close(self._fd)
FileReader("./config.json")
的操作,来创建一个 FileReader
,它会将文件描述符 int
作为私有状态维护起来。这正是我们期望的;我们不希望用户代码看到或篡改 _fd
,因为这可能会违反 FileReader
的不变性。构造有效 FileReader
所需的所有必要工作——即调用 open
——都由 FileReader.__init__
处理好了。FileReader.__init__
变得越来越尴尬。fileio.open
,但后来,我们可能需要适配一个库,它因为某种原因需要自己管理对 fileio.open
的调用,并想要返回一个 int
作为我们的 _fd
,现在我们不得不采用像这样的奇怪变通方法:def reader_from_fd(fd: int) -> FileReader:
fr = object.__new__(FileReader)
fr._fd = fd
return fr
reader_from_fd
的类型签名接收的只是一个普通的int
,它甚至无法向调用者建议该如何传入的正确的int
类型。FileReader
的实例而不做实际的文件 I/O 时,都必须打桩替换自己的 fileio.open
副本,即使我们可以(例如)为测试目的在多个 FileReader
之间共享一个文件描述符。fileio.open
是同步操作。但有许多网络资源实际上只能通过异步(因此:可能缓慢,可能容易出错)API 获得,虽然这可能是一个假设性问题。如果你曾经想要写出 async def __init__(self): ...
,那么你已经在实践中碰到了这种限制。__init__
是一种反模式,我们需要一个替代方案。dataclass
定义属性,__init__
中执行的行为,改为用一个新的类方法来实现相同的功能,dataclass
属性来创建 __init__
FileReader
重构为一个 dataclass
。它会为我们生成一个 __init__
方法,但这不是我们可以随意定义的,它会受到约束,即只能用于赋值属性。@dataclass
class FileReader:
_fd: int
def read(self, length: int) -> bytes:
return fileio.read(self._fd, length)
def close(self) -> None:
fileio.close(self._fd)
__init__
调用 fileio.open
的问题时,我们又引入了它所解决的几个问题:FileReader("path")
的简洁便利。现在用户不得不导入底层的 fileio.open
,这让最常见的创建对象方式变得既啰嗦又不直观。如果我们想让用户知道如何在实际场景中创建 FileReader
,就不得不在文档中添加对其它模块的使用指导。_fd
作为文件描述符的有效性没有强制检查;它只是一个整数,用户很容易传入不正确的数字,但没有出现报错。dataclass
,无法解决所有问题,所以我们要加入第二项技术。classmethod
工厂来创建对象FileReader
本身之外的任何东西——来弄清楚该如何创建想要的 FileReader
。@classmethod
。让我们定义一个 FileReader.open
类方法:from typing import Self
@dataclass
class FileReader:
_fd: int
@classmethod
def open(cls, path: str) -> Self:
return cls(fileio.open(path))
FileReader("path")
替换为 FileReader.open("path")
,获得与__init__
相同的好处。await fileio.open(...)
,就需要一个签名为@classmethod async def open
的方法,这可以不受限于__init__
作为特殊方法的约束。@classmethod
完全可以是async
的,它还可对返回值作修改,比如返回一组相关值的tuple
,而不仅仅是返回构造好的对象。NewType
解决对象有效性问题int
,底层的 fileio.open 返回的就是普通整数,这点我们无法改变。但是为了有效校验,我们可以使用 NewType
来精确要求:from typing import NewType
FileDescriptor = NewType("FileDescriptor", int)
fileio.open
、fileio.read
和 fileio.write
已经接收 FileDescriptor
类型的整数,而不是普通整数。from typing import Callable
_open: Callable[[str], FileDescriptor] = fileio.open # type:ignore[assignment]
_read: Callable[[FileDescriptor, int], bytes] = fileio.read
_close: Callable[[FileDescriptor], None] = fileio.close
FileReader
,但改动很小。综合这些修改,代码变成了:from typing import Self
@dataclass
class FileReader:
_fd: FileDescriptor
@classmethod
def open(cls, path: str) -> Self:
return cls(_open(path))
def read(self, length: int) -> bytes:
return _read(self._fd, length)
def close(self) -> None:
_close(self._fd)
NewType
,而是让“属性齐全”的对象自然成为“有效实例”。NewType
只是一个方便的工具,帮助我们在使用int
、str
或bytes
等基本类型时施加必要的约束。__init__
方法。【注2】@classmethod
,为调用者提供方便且公开的对象构造方法。typing.NewType
来对基本数据类型(比如int
和str
)添加限制条件,尤其是当这些类型需要具备一些特殊属性时,比如必须来自某个特定库、必须是随机生成的等等。__init__
方法的所有好处:def __init__(self, maybe_a_filename: int | str | None = None):
这样的怪物。__init__
方法就像个异类。而其他的魔术方法,像__add__
或__repr__
,本质上是在处理类的一些高级特性。@dataclass
、@classmethod
和 NewType
,你可以构建出易用、符合 Python 风格、灵活、易测试和健壮的类。close(7)
关闭它。start
必须始终小于end
。这类规则总有例外。不过,在__init__
里执行任何 I/O 操作基本上都不是好主意,而那些在某些特殊情况下可能有用的其它操作,几乎都可以通过__post_init__
来实现,而不必直接写__init__
。2025-04-27 08:00:00
t
,称为模板字符串(Template Strings),即 t-string。name = "World"
# f-string 语法
formatted = f"Hello {name}!"
print(type(formatted)) # 输出:<class 'str'>
print(formatted) # 输出:Hello World!
# t-string 语法
templated = t"Hello {name}!"
print(type(templated)) # 输出:<class 'string.templatelib.Template'>
print(templated.strings) # 输出:('Hello ', '')
print(templated.interpolations[0].value) # 输出:World
print("".join(
item if isinstance(item, str) else str(item.value)
for item in templated
)) # 输出:Hello World!
Template
(来自 Python 3.14 新增的标准库模块 string.templatelib
)。# 使用 f-string 的不安全示例(SQL 注入风险)
sql_query = f"SELECT * FROM users WHERE name = '{user_input}'"
# 使用 f-string 的不安全示例(XSS 风险)
html_content = f"<div>{user_input}</div>"
# 使用 t-string 的安全示例
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
# 可以定义处理函数来转义内容
assert html(template) == "<p><script>alert('evil')</script></p>"
from string.templatelib import Template, Interpolation
data = {"name": "Python猫", "age": 18}
template = t"用户 {data['name']} 的年龄是 {data['age']}"
def standard_renderer(template: Template) -> str:
"""标准文本渲染"""
return "".join(
item if isinstance(item, str) else str(item.value)
for item in template
)
def json_renderer(template: Template) -> str:
"""JSON格式渲染"""
import json, re
values = {}
for item in template:
if not isinstance(item, str):
# 使用正则表达式从表达式中提取键名
# 匹配 data['name'] 或 data["name"] 模式中的name
match = re.search(r"\['([^']+)'\]|\[\"([^\"]+)\"\]", item.expression)
if match:
# 获取匹配的第一个分组
key = match.group(1) if match.group(1) else match.group(2)
values[key] = item.value
return json.dumps(values, ensure_ascii=False)
def xml_renderer(template: Template) -> str:
"""XML格式渲染"""
parts = ["<data>"]
for item in template:
if not isinstance(item, str) and hasattr(item, "expression"):
name = item.expression.split("'")[1] if "'" in item.expression else item.expression
parts.append(f" <{name}>{item.value}</{name}>")
parts.append("</data>")
return "\n".join(parts)
# 同一个模板,不同的输出格式
print(standard_renderer(template)) # 输出: 用户 Python猫 的年龄是 18
print(json_renderer(template)) # 输出: {"name": "Python猫", "age": 18}
print(xml_renderer(template)) # 输出: <data>\n <name>Python猫</name>\n <age>18</age>\n</data>
Template
类具有以下主要属性和方法:class Template:
strings: tuple[str, ...]
"""
模板中字符串部分的非空元组。
包含 N+1 个元素,其中 N 是模板中插值表达式的数量。
"""
interpolations: tuple[Interpolation, ...]
"""
模板中插值部分的元组。
如果没有插值表达式,这将是一个空元组。
"""
def __new__(cls, *args: str | Interpolation):
"""
创建一个新的 Template 实例。
参数可以按任意顺序提供。
"""
...
@property
def values(self) -> tuple[object, ...]:
"""
返回模板中每个 Interpolation 的 `value` 属性组成的元组。
如果没有插值表达式,这将是一个空元组。
"""
...
def __iter__(self) -> Iterator[str | Interpolation]:
"""
迭代模板中的字符串部分和插值表达式。
这些可能以任意顺序出现。不包含空字符串。
"""
...
访问原始字符串片段(strings
)
访问插值表达式及其计算结果(interpolations
)
直接获取所有插值的值(values
)
按顺序迭代模板的所有组成部分
__iter__
函数注释说出现顺序不固定,但 PEP 文档中它的具体实现却是按序的,我认为是注释有误。{}
作为插值表达式的分隔符.2f
)和转换说明符(如 !r
)'
、"
、'''
、"""
)t
和 T
都是有效的,就像 f
和 F
str
类型,而 t-string 返回 Template
类型def html(template: Template) -> str:
parts = []
for item in template:
if isinstance(item, str):
parts.append(item)
else: # Interpolation
parts.append(html_escape(item.value))
return "".join(parts)
user_input = "<script>alert('XSS')</script>"
safe_html = html(t"<div>{user_input}</div>")
# 输出: <div><script>alert('XSS')</script></div>
def safe_sql(template: Template) -> str:
parts = []
params = []
for item in template:
if isinstance(item, str):
parts.append(item)
else:
parts.append("?")
params.append(item.value)
return "".join(parts), params
user_id = "user' OR 1=1--"
query, params = safe_sql(t"SELECT * FROM users WHERE id = {user_id}")
# query: "SELECT * FROM users WHERE id = ?"
# params: ["user' OR 1=1--"]
import json
import logging
from string.templatelib import Template, Interpolation
class TemplateMessage:
def __init__(self, template: Template) -> None:
self.template = template
@property
def message(self) -> str:
# 格式化为可读消息
return f(self.template) # 使用自定义 f() 函数
@property
def values(self) -> dict:
# 提取结构化数据
return {
item.expression: item.value
for item in self.template.interpolations
}
def __str__(self) -> str:
return f"{self.message} >>> {json.dumps(self.values)}"
action, amount, item = "traded", 42, "shrubs"
logging.info(TemplateMessage(t"User {action}: {amount:.2f} {item}"))
# 输出: User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}
subprocess
模块能原生支持 t-string:# 不安全的 f-string 用法
subprocess.run(f"echo {message_from_user}", shell=True) # 命令注入风险
# 安全的 t-string 用法
subprocess.run(t"echo {message_from_user}") # 自动进行适当的命令转义
from string.templatelib import Template, Interpolation
import html
def smart_renderer(template: Template, context="text") -> str:
"""上下文感知的渲染器
根据context参数自动决定如何处理每个插值:
- "text": 普通文本模式,直接转为字符串
- "html": HTML模式,自动转义HTML特殊字符,防止XSS
- "sql": SQL模式,自动转义SQL特殊字符,防止注入
"""
parts = []
for item in template:
if isinstance(item, str):
parts.append(item)
else: # Interpolation
value = item.value
expression = item.expression
# 基于值类型和上下文进行智能处理
if context == "html":
# HTML模式:自动转义HTML特殊字符
parts.append(html.escape(str(value)))
elif context == "sql":
# SQL模式:防止SQL注入
if isinstance(value, str):
# 将1个单引号转义成2个
escaped_value = value.replace("'", "''")
parts.append(f"'{escaped_value}'")
elif value is None:
parts.append("NULL")
else:
parts.append(str(value))
else:
parts.append(str(value))
return "".join(parts)
# 同一个模板在不同上下文中的自动适配渲染
user_input = "<script>alert('evil')</script>"
template = t"用户输入: {user_input}"
print(smart_renderer(template, context="html")) # 输出: 用户输入: <script>alert('evil')</script>
# SQL注入防护示例
user_id = "1'; DROP TABLE users; --"
sql_template = t"SELECT * FROM users WHERE id = {user_id}"
print(smart_renderer(sql_template, context="sql")) # 输出: SELECT * FROM users WHERE id = '1''; DROP TABLE users; --'
# f-string 对于SQL注入,必须先处理值,再放入f-string
escaped_id = user_id.replace("'", "''")
sql_safe_id = f"'{escaped_id}'"
print(f"SQL查询(f-string): SELECT * FROM users WHERE id = {sql_safe_id}")
# f-string的嵌套:内部表达式立即求值,信息丢失
value = "world"
inner_f = f"inner {value}"
outer_f = f"outer {inner_f}"
print(outer_f) # 输出: outer inner world
print(type(outer_f)) # <class 'str'> - 只是普通字符串
# t-string的嵌套:保留完整结构信息
inner_t = t"inner {value}"
outer_t = t"outer {inner_t}"
print(type(outer_t)) # <class 'string.templatelib.Template'>
print(type(outer_t.interpolations[0].value)) # 也是Template对象!
# 可以访问和处理任意深度的嵌套结构
user = {"name": "Alice", "age": 30}
message = t"用户{user['name']}信息: {t'年龄:{user['age']}'}"
inner_template = message.interpolations[1].value
print(inner_template.strings) # 输出: ('年龄:', '')
print(inner_template.interpolations[0].value) # 输出: 30
import asyncio
# 模拟异步数据获取
async def fetch_data(key: str) -> str:
await asyncio.sleep(0.1) # 模拟网络延迟
return f"获取的{key}数据"
async def render_async(template):
tasks = {}
# 并行启动所有异步查询
for item in template.interpolations:
tasks[item.expression] = asyncio.create_task(
fetch_data(item.expression)
)
# 等待所有查询完成
for expr, task in tasks.items():
tasks[expr] = await task
# 组装结果
result = []
for item in template:
if isinstance(item, str):
result.append(item)
else:
result.append(tasks[item.expression])
return "".join(result)
async def main():
template = t"用户: {user}, 年龄: {age}"
result = await render_async(template)
print(result)
# asyncio.run(main())
Template
对象,开发者可以在字符串组合前对插值进行拦截和转换,从而避免常见的安全问题,并支持更多高级用例。2025-04-26 08:00:00
__init__
用法