Python 的新 t-strings

模板字符串(也称为 t-strings)已被正式接受为 Python 3.14 的一项功能,该版本将于 2025 年底发布。

我很兴奋;t-strings 为 Python 中更安全、更灵活的字符串处理打开了大门。

t-strings 有什么重大意义?

自 Python 3.6 引入以来,f-strings 已成为一种非常流行的字符串格式化方式。它们简洁、易读且功能强大。

事实上,它们是如此讨人喜欢,以至于许多开发人员在任何情况下都使用 f-strings ……即使在不应该使用它们的时候!

唉,f-字符串经常被危险地(错误地)用于格式化包含用户输入的字符串。我见过将 f 字符串用于 SQL(f “SELECT * FROM users WHERE name = ‘{user_name}’”)和 HTML(f“<div>{user_name}</div>”)。这些字符串并不安全!如果 user_name 包含恶意值,就会导致 SQL 注入跨站脚本攻击

 

元素周期表

模板字符串是对 Python 的 f-strings 的概括。f-strings 会立即变成字符串,而 t-strings 则会评估为一种新类型 string.templatelib.Template

from string.templatelib import Template
name = "World"
template: Template = t"Hello {name}!"

重要的是,Template实例不是字符串。Template类型不提供自己的 __str__() 实现,也就是说,调用 str(my_template) 不会返回有用的值。模板在使用前必须经过处理;处理代码可以由开发者编写,也可以由库提供,并且可以安全地转义动态内容。

我们可以想象一个提供 html() 函数的程序库,该函数接收一个Template 并返回一个安全转义的字符串:

evil = "<script>alert('bad')</script>"
template = t"<p>{evil}</p>"
safe = html(template)
assert safe == "<p>&lt;script&gt;alert('bad')&lt;/script&gt;</p>"

当然,t-strings 的作用不仅仅是安全,它还能实现更灵活的字符串处理。例如,html() 函数可以返回一种新类型 HTMLElement。它还可以在 HTML 本身中接受各种有用的替换:

attributes = {"src": "roquefort.jpg", "alt": "Yum"}
template = t"<img {attributes} />"
element = html(template)
assert str(element) == "<img src='roquefort.jpg' alt='Yum' />"

如果你使用过 JavaScript,可能会对 t-strings 感到熟悉。它们与 JavaScript 的标记模板类似

如何使用 t 字符串?

为了支持处理,Templates 允许开发人员访问字符串及其插值,然后再将它们组合成最终字符串。

Template 的 .strings.values 属性会返回元组:

name = "World"
template = t"Hello {name}!"
assert template.strings == ("Hello ", "!")
assert template.values == (name,)

字符串总是比值多一个(可能为空)。也就是说,t“”.strings == (“”,)t“{name}”.strings == (“”, “”)

作为快捷方式,也可以遍历Template

name = "World"
template = t"Hello {name}!"
contents = list(template)
assert contents[0] == "Hello "
assert contents[1].value == name
assert contents[2] == "!"

编写复杂处理代码的开发人员也可以访问每个插值的细节:

name = "World"
template = t"Hello {name!s:>8}!"
assert template.interpolations[0].value == name
assert template.interpolations[0].expression == "name"
assert template.interpolations[0].conversion == "s"
assert template.interpolations[0].format_spec == ">8"

除了支持字面形式(t “foo”)外,Template 还可以直接实例化:

from string.templatelib import Template, Interpolation
template = Template(
	"Hello ",
	Interpolation(value="World", expression="name"),
	"!"
)

字符串和插值可以任意顺序提供给Template 构造函数。

一个简单的 t-string 示例

假设我们想编写代码,将所有替换的单词转换成猪拉丁文。这只需要一个简单的函数:

def pig_latin(template: Template) -> str:
	"""Convert a Template to pig latin."""
	result = []
	for item in template:
		if isinstance(item, str):
			result.append(item)
		else:
			word = item.value
			if word and word[0] in "aeiou":
				result.append(word + "yay")
			else:
				result.append(word[1:] + word[0] + "ay")
	return "".join(result)

name = "world"
template = t"Hello {name}!"
assert pig_latin(template) == "Hello orldway!"

这是一个愚蠢的例子;如果你想看一些不那么愚蠢的例子,请查看 PEP 750 示例库

t-strings 发布后的下一步是什么?

t-string 是一个强大的新特性,它将使 Python 字符串处理更安全、更灵活。我希望看到它们被用于各种库和框架,尤其是那些处理用户输入的库和框架。

此外,我希望工具生态系统也能支持 t-strings。例如,我希望看到blackruff格式的 t-string 内容,如果它们是 HTML 或 SQL 等常见类型,vscode 还能为它们着色。

能在这个项目中认识 Jim、Paul、Koudai、Lysandros 和 Guido 并与他们一起工作,以及与 Python 社区中更多的在线成员交流,是一件非常有趣的事情。我迫不及待地想看到 t-strings 发布后,开发者们会用它来构建什么!

阅读余下内容
 

《 “Python 的新 t-strings” 》 有 434 条评论

  1. 从各方面考虑,这都很酷。基本上,它取代了

     db.execute(“QUERY WHERE name = ?”, (name,))
    

     db.execute(“QUERY WHERE name = {name}”)
    

    这种语法糖带来的好处是否超过了新语言特性所增加的复杂性?我认为在本例中确实如此,原因有二:

    1. 允许库开发人员对 {} 扩展做任何他们想做的事是件好事,而且可能会产生一些好的用途。

    2. 在一种语言中通用模板语法,使所有库都能以同样的方式解决这个问题,这也许是件好事。

    • 大约 20 年前,我做了一个安全的 OCaml 实现,最新版本在这里:

      https://github.com/darioteixeira/pgocaml

      请注意,变量在编译时已安全、正确地插值。通过(在编译时)检查实时数据库中的列类型,它还进行了跨边界类型检查。

      • 是的,严格来说,你所做的比 Python 的人做的更强大。而且你是在 20 年前完成的。干得好,给个赞。然而,现在是 2025 年,Python 的普及势不可挡,却(大约)没有人关心 OCaml(以及所有其他比 Python 更好的语言)。这让我很难过。

        • 网络效应是一头野兽!

          但我想说的是,Python 能像火箭一样腾飞,我们已经很幸运了。虽然它不是我最喜欢的语言,但也有比它更糟糕的语言。

        • 我每天都会在 C、OCaml、Python、bash 和 Rust 之间切换(Perl 也是如此,但程度较轻)。并非所有东西都能登上 HN 的头版。

        • 有趣的是,大多数人都明确选择不使用 “更好的 ”语言。难道大多数人的判断力真的那么差吗?还是说 “更好 ”的定义实际上是随着时间的推移而采用的?

          • 在他们看来,Python 显然更好,只是他们优化的指标与你不同。在我看来,Python 更好,因为它易于学习。

            • Python并不容易学,甚至连安装和运行都是个挑战。

              Python之所以流行,是因为老师都是这么教的。

              • 如果一个人不是绝对的初学者,却在搞懂Venv方面遇到困难,那他可能不是从事技术工作的料。在这个领域里,有无数学科比这更具挑战性,也更复杂。

                另外,在 2025 年,只需使用 uv 即可。

              • 在现代 Linux 上,你可以在命令提示符下输入 `python`,获得 REPL。在 Windows 上,你可以从官方网站下载安装程序(就像通常在 Windows 上安装任何东西一样),然后在命令提示符下使用 `py`。

                开始教授 Python 时不需要 “导入 ”任何东西。即使这样,您也可以用标准库做很多事情。即便如此,除非您在 Linux 上使用的是 3.11 或更高版本,否则您可以让 Pip 以 `–user` 的方式安装,直到您真正需要在项目之间隔离东西为止。(即使是 Linux 上的新 Python,教师也可以通过在 `/usr/local/bin`中单独安装 Python 来避免这种情况。是的,这是 “作弊”,这取决于课堂环境。但这也是问题的一部分:安装障碍是自学者的障碍,而不是学生的障碍)。

                只有当你的项目存在相互冲突的依赖关系时,和/或当你准备发布自己的软件时,你才需要学习虚拟环境,并学习正确的测试和开发实践。(这在很大程度上与编程无关,而且在任何语言中都不是小事)。

                当你的学生达到这个阶段时,你可以给他们一个链接,如 https://chriswarrick.com/blog/2018/09/04/python-virtual-envi… .

                老师们教 Python 是因为它既容易教,又与现实世界相关,尤其是因为它的模板被减少到了最低限度。你不必在前面解释诸如 “public ”或 “static ”之类的专业术语。在相当长的一段时间内(如果有的话),你不必使用类。你可以自然地表达迭代。可以自然地从能力的角度来考虑类型。

                在我看来,Python 具有 Lisp 在教学上的所有优点,而且语法提示足以防止 “迷失在括号的海洋中”。(当然,Python 也缺少 Lisp 家族的许多其他优秀特性)。

                • 根据我的经验,人们首先要搞清楚 numpy 是什么鬼东西,以及如何获取它(venv、conda、pip、uv、uvx……),因为 python 数组太烂了,所以人们用外部 C 库来解决这个问题。然后,他们发现其他一些依赖库需要之前的 python 版本,但他们的 python 是全局安装的,其他依赖库也是为此安装的。这些都是 python 独有的问题。Lisp 没有这些问题

                • > 你不必事先解释 “public ”或 “static ”这样的专业术语。

                  在 Python 中,尤其是在阅读(或拷贝)其他人的代码时,如果你还没看清楚 __name__ == “__main__”,任何潜在的程序员都会问 “这他妈是什么东西”。

                  即使是 “def ”也是个奇怪的关键字。

                  不要让我开始教初学者哪些数据类型是引用传递,哪些是值传递。

                  试着向小学生解释

                   def foo(a):
                         a = a + 1
                  

                  不会改变调用者的变量,但

                   def bar(a):
                         a.append(1)
                  

                  这样做是不对的。

                • > 在我看来,Python 具有 Lisp 在教学上的所有优点,还有足够的语法提示,可以避免 “迷失在括号的海洋中”。(当然,Python 也缺少 Lisp 家族的许多其他优秀特性)。

                  你在这里说的话让我想起了彼得-诺维格(Peter Norvig)15 年前在这个网站上说过的话: https://news.ycombinator.com/item?id=1803815

                  > 我是 Peter Norvig。我学习 Python 并不是因为我认为它是更好的/可接受的/实用的 Lisp,而是因为它是更好的伪代码。有几个学生声称,他们很难从我的人工智能教科书中的伪代码映射到 Russell 和我在网上找到的 Lisp 代码。于是,我寻找与我们的伪代码最相似的语言,发现 Python 是最匹配的语言。然后,我必须自学足够的 Python 来实现教科书中的示例。我发现 Python 非常适合解决某些类型的小问题,而且它拥有我需要的库,可以与谷歌和网络上的许多其他东西集成。

                  基本上,Python 的教学效果更好,因为它看起来像伪代码,而且很容易快速上手。

                  • 这是有道理的,但看到它在教学之外被实际采用却令人沮丧。这种特性与大中型优质应用软件的编程语言完全背道而驰,几乎是格格不入的。

                    如果我们在生活中的其他地方也使用这种逻辑,我们就都会吹笛子,骑着三轮车和平衡车到处跑了。但出于某种原因,在科技领域,一切都与 “Hello World ”有关。

                    • 赢家是那些成本较低(……就 python 而言,学习成本较低)、质量足够好(……比 OCaml、Lisps、Haskel 更好,但绝对不是 JS 或 Java)的废料市场进入者,这并不是一个新故事。我不同意你的类比。

              • 你不需要教初学者。初学者需要学习的东西不会超过标准库,当你需要的东西超过标准库时,你要么给他们运行所需的单个命令,要么,更有可能的是,让他们使用一个模板项目,在这个项目中,像 Poetry 这样的工具会自动完成这些工作。

                这通常并不是说在 2025 年管理 Python 包很难,而是很多人在第一次学习编程之前并没有从概念上了解操作系统是如何工作的,这很容易与你学习的第一门语言混为一谈。

                • > 你不需要教初学者。

                  gp 的说法是:

                  > Python 更好,因为它易于学习、

                  我相信我们都同意:它并不简单。

              • 我来给你介绍一下 uv[0]。是的,这个工具不是用 Python 写的,这确实说明了一些问题,但我想说的是,这么多人都在努力支持 Python,这就更说明问题了。

                [0] https://docs.astral.sh/uv/

                • 如果你想做生物信息学,人们会坚持用 conda,然后你的团队会说 “用 rye”,这些策略在某种程度上是兼容的,但最终会以互不兼容的方式让你抓狂,每当你遇到一个小障碍时,都会让你的代码运行停滞不前。

              • 它之所以成功,很大程度上是因为它一直拥有非常出色的外来函数接口。如果你有一个用 C 语言编写的科学或数学库,那么你可以将它连接到 Python,然后你就突然拥有了一种(相当简洁的)脚本语言的所有灵活性,来协调你的高速 C 语言。

                numpy 和 tensorflow 就是很好的例子。

                • tensorflow 非常糟糕,这也是为什么它基本上已经死了,取而代之的是(py)torrent。

              • 学习 Python 作为第一语言的人所知甚少,完全可以让他们污染全球环境。懂其他语言的人可以理解 venv 的用途。

                相反,他们可以输入 python 打开 shell,然后使用 python 立即运行他们的文件。

          • 动力 + 生态系统往往比实际语言的优劣更重要。

            • 如果语言没有足够的优点来激发和维持这种兴趣,那么这种势头和生态系统一开始就不会实现。

          • 我觉得你太不公平了。人们并不笨。

            问题还在于好到什么程度。

            在一个足够好的类型系统之外,优势开始变得平缓,其他因素开始变得更加重要。

            我不太了解 python,但作为一个用 OCaml 和 Typescript 写过大量代码的人来说,Typescript 最严格的编译器选项已经足够好了。

            • 不,人们并不笨。他们很实际。因此,他们选择做实用的事情,在这种情况下就是选择 Python。在我看来,Python 是更好的语言。

    • 除了 SQL 注入安全之外,服务器端参数绑定还有其他好处吗?例如,使用 PG 的扩展协议(二进制),而不仅仅是原始 SQL 字符串。缓存参数化准备语句等。

      还有

       db.execute(t “QUERY WHERE name = {name}”)
      

      危险地接近于

       db.execute(f “QUERY WHERE name = {name}”)
      

      只差一个字符,你就把自己变成了可注入的人。

      我不认为这个新格式指定器适用于 SQL 查询。

      • 模板是一种与字符串截然不同的鸭子类型,而且故意不支持 __str__()。SQL 工具可以提供一个 `safe_execute(Template)`,如果传递的是字符串而不是模板,它就会抛出。可以想象,未来的库将只支持 Template,而放弃所有接受字符串的函数,成为真正安全的查询库。

        > 缓存参数化准备语句等。

        模板提供了构建可缓存参数化预处理语句等所需的所有数据。对于支持命名参数的数据库引擎,你甚至可以让插值表达式自动命名参数(从你的示例中获取字符串 “name ”作为填充槽的变量名),以获得额外的调试/缓存优势。

      • 但 t“… ”和 f“… ”的类型不同;我们可以让 db.execute 拒绝字符串,只接受模板对象。

      • 要解决这个问题,可以使用需要输入模板的 execute(stmt) 函数。

        在 Javascript 中,sql`where id = ${id}`与普通字符串插值`where id = ${id}`非常接近,而提供 sql 标记的数据库库都有拒绝字符串的 query(stmt) 函数。

      • > 除了 SQL 注入安全之外,服务器端参数绑定还有其他好处吗?例如,使用 PG 的扩展协议(二进制)而不仅仅是原始 SQL 字符串。缓存参数化预处理语句等。

        所有这些都可以在模板字符串之上实现。

        > 只要一个字符的差别,你就可以把自己变成可注入的人了。

        这不仅仅是一个字符的差别,而是一种不同的类型。因此,`db.execute` 可以静态和动态地拒绝字符串。

        > 我不认为

        绝对正确。

        > 这个新的格式指定以任何方式适用于 SQL 查询。

        这正是 PEP 750 的动机之一。

        • > 我不认为

          > 绝对正确。

          你的其他评论都很有价值,但这句话既刻薄又没有必要。

        • 吹毛求疵:

          > 这不仅仅是一个字符的区别,而是类型的不同。因此,`db.execute` 可以静态或动态地拒绝字符串。

          在本例中,这实际上没有什么帮助,因为 SQL 语句不需要参数,所以 db.execute 总是需要接受字符串。

          • > db.execute 总是需要接受一个字符串。

            不,不带占位符的 t 字符串完全没问题。即使没有参数,也可以使用它。

        • > 缓存参数化的准备语句等

          我在文章中没有明确提到这一点,但模板类型的设计考虑到了缓存。特别是,在许多情况下,.strings 元组很可能是有用的缓存键。

        • >> 我不认为 >definitely true。

          我以为我们已经抛开了中学时代的游戏策略。

        •  from string.templatelib import Template
              def execute(query: Template)
          

          如果将 mypy 作为 pr 进程的一部分运行,应该可以通过静态分析防止出现此问题。

          这是运行时检查的补充。

          • 我们将看到库开发人员犯的第一个错误是

             def execute(query: Union[str, Template]):
            

            也许是因为他们希望自己的 execute 函数向后兼容,或者只是因为他们确实希望允许原始字符串或模板字符串。

            • > 他们确实想让原始字符串成为模板字符串。

              我认为这是一个无效用例:

              1. 您可以创建没有占位符的模板字符串。

              2. 2. 即使调用者确实需要传入字符串(因为他们是从文件执行的,或者 t-strings 不支持分面等),他们也可以……明确地将字符串封装在模板中。

        • > 这就是 PEP 750 的动机之一。

          Python 是出了名的动机错误。我们不是在 “诉诸权威”。我们有指出错误的自由。

      • > 只差一个字符,你就把自己变成了可注入的人。

        不;一个字符的差异就会导致 “类型错误”,希望代码库能通过预测这种常见的误用模式使其更有参考价值。

        • 很多其他事情也是如此,这就是为什么我们要让类型检查程序和衬垫程序做到完美严谨,而不是期望人类永远不会犯错。

      • > 我不认为这个新格式规范适用于 SQL 查询。

        同意。只要有这样一个功能,就会引发 Python 数据库生态系统中无休止的争论。

      • 当!感谢您指出这一点。

        我看了好几遍你的评论才注意到一个是 F,另一个是 T。

        这不会有好结果的。虽然我从概念上喜欢它,但这几个像素的字母差别会给以后的工作带来很大的麻烦。

        • tstrings 和 fstrings 是完全不同类型的文字。

          CS 已经在 1 和 1.0 完全不同类型的情况下生存了几十年。

          • 上周我进行了一次长时间的调试,重点是我必须使用的一个库中的 1 和 1.0 混淆…

            • 是啊,发生这种情况真让人扫兴。真希望 JSON 从未尝试过类型。

          • 重读我的评论。我是说注意到你有一个 “f ”或一个 “t”,而这两个都是非常相似的字符。

            • 是的,但你会得到一个错误,因为字符串和模板是不同的类型,有不同的接口。

              • 多点击几次 “parent”,看看引发这个主题的代码示例。它使用相同函数的方式无法区分用户是否有意使用了字符串(包括 f-字符串)和 t-字符串。

                • 是的,家长被误导了。正如多个回复所指出的,库可以区分传递的是普通字符串还是 t-string,因为 t-string不是字符串实例,而是创建了一个单独的库类型。如果用户错误地使用了 f 前缀而不是 t 前缀,在设计合理的库中,运行时将会遇到 “TypeError”(或在类型注解和检查器的情况下提前发出警告),而不是 SQL 注入。

                  • 在这个特殊的例子中,它不能这样做,因为这里有三种方式,它不能区分正确的有意使用和意外使用 f 字符串而不是 t 字符串:

                     db.execute(“SELECT foo FROM bar;”)
                      db.execute(f “SELECT foo FROM bar WHERE id = {foo_id};”)
                      db.execute(t “SELECT foo FROM bar WHERE id = {foo_id};”)
                    

                    第一个和第二个对 execute() 来说是一样的,因为它看到的只是一个字符串。但第二个字符串是错的,是第三个字符串的一个难以察觉的错字。

                    如果没有 f-strings 就不会有问题,因为它可以像你说的那样按类型进行区分。但我们这里有一种错误的、容易引起 SQL 注入的用法,无法通过类型将其与正确的纯字符串用法区分开来。

          • 因为它们都传递给了 “execute”,而 “execute ”无法区分 f 字符串和非插值查询,所以它只能相信你做了正确的事情。把 “t ”打成 “f ”会引入 SQL 注入,但很难被发现。

    • 或者您也可以在类似 sh 的库中使用它,如

       sh(t “stat {some_file}”)
      

      有了 t 字符串,你就可以先对 `some_file` 的内容进行适当的转义处理,然后再将其传递给 shell。

      我得看看 shell 中的操作顺序,不过你甚至可以把它变成类似 “stat ”$( base64 -d [base64 encoded content of some_file]) “这样的东西,从而提高一点安全性/脚枪潜力。

    • 一个潜在的问题是,这看起来与他们试图覆盖的模式有多接近。

       db.execute(f “QUERY WHERE name = {name}”)
      

       db.execute(t “QUERY WHERE name = {name}”)

      • 关键是 t 字符串不是字符串。Db.execute(t“……”)会产生异常,因为 t“…… ”不是字符串,不能被解释为字符串。
        为了让函数库接受 t 字符串,他们需要创建一个新函数。或者改变旧函数的行为和方法签名,我想他们可以这么做,但任何设计合理的库都不会这么做。
        处理 t 字符串需要在库中添加新函数。

        • 是的,但错误在于写 f 而不是 t,我认为 f 可以正常工作。
          为了进一步澄清:
          问题不在于错误地用 t 代替 f => 这是我们想要的,为此我们需要实现一个新函数
          问题是写 f 而不是 t => 我认为这将无声地工作(我不是 Python 开发人员,只是想了解语言设计)。

          • >是的,但问题在于写 f 而不是 t,我认为 f 会正常工作。
            f-strings 和 t-strings 是不兼容的类型,它们不会 “正常工作”,除非有人修改库使其正常工作,只要没人这么做,这就不是问题。

          • > 问题在于写 f 而不是 t => 我想这将会无声地工作(我不是 Python 开发人员,只是想了解语言设计)
            完全没有理由这样做。即使在最糟糕的情况下,您必须以 t-strings 所不支持的方式动态组成查询,您也可以直接实例化一个 Template 对象。

      • 但 f string 版本不会因为没有名称参数而导致查询失败吗?

          • 说得好。也许数据库 api 可以拒绝字符串,而要求模板。

            • 这对一个全新的功能来说是一个重大的突破性改变。我相信可以做得很好,但这让我不寒而栗。

    • 3. 它可以防止开发人员尝试

       db.execute(f “QUERY WHERE name = {name}”)
      

       db.execute(“QUERY WHERE name = %s” % name, ())
      

      或其他手动插值字符串的方法–因为如果给定的是字符串(无论它是如何构造的)而不是一个 `Template` 实例,`db.execute` 可能会标记一个 `TypeError` 错误。

      • 如果名称来自用户输入,执行函数可以将其识别为 t-string,防止 SQL 注入。f-string 会立即求值为字符串,而 t-string 会求值为模板对象,需要进一步处理才能将其转化为字符串。

        • 那么有用的部分就是你必须编写的额外的执行函数(不只是像注释中所说的替代),额外的函数同样可以确认进入 f-string 的值的安全性。

          我明白了一般情况,但即便如此,与执行 db.execute(f “QUERY WHERE name = {safe(name)}”) 相比,这似乎也是一种隐含的反模式。

          • 这个例子的问题是,从哪里获得 `safe`?将模板传入 `db.execute` 可以让 `db` 实例专门为其连接的后端处理安全问题。否则,你就需要创建一个带有数据库连接的 `safe` 函数,以正确处理字符串。

            此外,如果 `safe` 只返回一个字符串,就会失去 `db.execute` 以不同方式传递参数的能力 — 你将失去一个变量被插值到字符串中的信息。

            • db.safe与新的 db.execute相同,其中包含了你为t-string创建的安全检查,但我可以看到进一步使用值或比这更复杂的情况下的一些好处(尽管到目前为止我还不是我自己的代码库的粉丝)。

              • 是的,但它必须类似于 `db.safe(“SELECT * FROM table WHERE id = {}”, row_id)`,而不是 `db.execute(t “SELECT * FROM table WHERE id = {row_id}”)`。

                我自己更倾向于第二种。

                • 不,只要 `db.execute(f “QUERY WHERE name = {db.safe(name)}”)` 就可以了。

                  你可以显式地在 db.safe 中添加安全性,而不是隐式地在 db.execute 中添加。

                  如果你想花哨一点,还可以在 db.safe 中将 name 赋值给 db.foos,以便以后使用它(甚至在 execute 中)。

                  • 这只是额外的模板,有什么用呢?

                    我想你可能忽略了一件事,在 t-string 版本中,`db.execute` 使用的不是字符串;t-string 解析的是特定类型的对象。因此,它是在自动执行 `db.safe` 操作。

                  • 你当然可以写这样的代码。这样做的目的是为了避免因忘记调用 safe() 而意外导致代码注入。JavaScript 也有同样的功能,有些 SQL 库只允许传递模板字符串,而不允许传递普通字符串,因此无法生成带有代码注入的字符串。如果必须动态生成查询,它们允许参数是另一个模板字符串,然后这些参数会被正确合并。这就是减少按键次数,降低出错的可能性。我们都可以直接编写非键入式程序集,而且只要非常注意,就能安全地编写。

                  • 但如果有人省略了 “safe”,它仍然可以工作,但允许注入。

                    • 如果有人忘记使用 t “而使用 f”,情况也是一样。

                      至少 db.safe 说出了它的作用,而不像 t”。

                    • 其实不然,因为 f“” 是一个字符串,而 t“” 是一个模板,所以可以让 `db.execute` 只接受模板,也许可以让

                      db.execute(Template)`和`db.unsafeExecute(str)`。

                    • 如果不这样做,而是添加 `db.safe_execute(tpl: Template)`,那就又回到了用户可能忘记调用安全函数的风险。

                      此外,如果传递的字符串是模板所期望的,你就会相信库实现者会引发运行时异常。仅仅依靠类型检查/字迹检查是不够的。

                      我并不是说模板不是向前迈出的好一步,只是说如果使用不当,模板也会出现我们现在遇到的同样问题。

                    • 然后让 `db.unsafe_execute` 接受一个字符串。

                    • 是的,可以。我只是说这样做会破坏 `db.execute`,因为它不能像现在这样接受字符串。库可能不想为此添加一个破坏性的修改。

                    • 您的内部程序可以标记类型不匹配,和/或函数可以在运行时拒绝接受 f“”。这是因为 t“” 生成的是 Template,而不是 str。

                      Template 的功能也更强大/更简洁,因为 stringify 函数可以随心所欲地处理 “格式化 ”参数。

                      还要注意的是,并不要求模板必须变成字符串才能使用。

                  • db.safe 是做什么的?它怎么知道在 SQL 的那一点上转义的安全方法是什么?它不知道它是在字符串内部,还是在字段名位置,是表示值还是表名。

                    为了进一步说明这个问题,请考虑类似的 html.safe:f<a href={html.safe(url)}>”{html.safe(desc)</a>” – 对 html.safe 的两次调用需要完全不同的转义,它如何知道应用哪种转义呢?

                • 第一个已经存在,如

                   db.execute("SELECT * FROM table WHERE id = ?", (row_id,))
          • 但你必须记住每次都调用正确的 safe() 函数:

             db.execute(f "QUERY WHERE name = {name}")
             db.execute(f "QUERY WHERE name = {safe_html(name)}")
            

            哎呀,你完蛋了,没有任何东西可以检测到这一点。而 t 字符串就不会有这样的问题,它不会被滥用。

          • 一些 SQL 引擎支持单独接受参数,这样一旦抽象语法树已经建立,参数值就会绑定到查询中,这比字符串转义的诡计要安全得多。

            • 如果可以的话,我总是更喜欢使用预处理语句,但遗憾的是,在花哨的新无服务器执行环境中,这也不太可行,因为数据库适配器往往无法支持预处理语句。

              对我来说,这样更容易识别是否安全,因为插值模板字符串是否经过了适当的消毒,可能一眼就看不出来。

          • > 那么有用的部分就是你必须编写的额外执行函数

            嗯,不,是库作者编写的。而且,库作者还要检测你传递的是一个模板实例,还是一个由你选择的格式化方法创建的字符串(错误的)。在 f-string 中使用`safe(name)`会丢失类型信息,并可能出现更多错误。

          • > 一个额外的函数也可以确认进入 f 字符串的值的安全性。

            是的,你可以要求消费者在每个参数进入 f-string 之前对其进行明确的消毒处理,或者,由于它拥有哪些是固定参数、哪些是参数的结构,它可以在获取 t-string 时对所有参数进行消毒处理。

            后者要可靠得多,而你无法用 f 字符串做到这一点,因为创建后的 f 字符串只是一个静态字符串,没有任何关于结构的信息。

      • 如果我向方法传递一个 f 字符串,它只会看到一个字符串。如果我传递一个 t 字符串,方法可以决定如何处理这个 t 字符串。

      • 在这里使用 f-strings 不正好会导致 sql 注入漏洞吗?

      • f-strings 不会对值进行消毒,因此并不安全。文章谈到了这一点。

        • 文章谈到了这一点,但这里的示例只是假设它们会存在。

          • 你说的 “它们 “是什么意思?你是指模板插值函数吗?

            是的,我们的想法是,通过在语言中加入这些功能,库作者就会在适当的用例中编写这些实现。

            • sanitization。在旧的 db.execute 中使用 t 字符串并不意味着比以前更安全。

              • 你的 “旧 “db.execute(可能接受普通的旧字符串)不会接受 t-string,因为它不是字符串。在原始示例中,它是一个新的 db.execute。

              • 在与 t-string 不兼容的 db.execute 中使用 t-string 会导致错误。

                在与 t-string 兼容的 db.execute 中使用 t-string 应该与使用外部参数一样安全。而在这种情况下使用非 t-字符串(最终)应被拒绝。

                • 同样,函数接受 t 字符串并不意味着默认情况下会进行消毒。

                  • 是的,但如果函数接受模板(模板是一种不同于字符串的对象类型!),那么函数要么是在进行消毒,要么是在不进行消毒的情况下明确实现了模板支持–这很难偶然做到!

                    这里的关键是,”t-string “根本不是字符串,而是一种新的字面形式,它重复使用字符串语法来创建模板对象。这就是这项新功能与 f-string 的本质区别。由于它是一种新的对象类型,接受字符串的库要么必须明确处理它,要么在运行时引发 TypeError。

                    • 我不明白你为什么认为在不进行 sanitization 的情况下使用它们会更困难–检查其中的值并没有什么固有的问题,这只是一个不错的用法。

                      您实现 t 字符串的目的可能是为了保存值或更好地记录或其他什么,而根本没想过要检查或转义任何东西,绝对不是所有东西(就像人们在其他地方忘记这样做一样)。

                    • 我真的觉得你误解了这个功能。如果一个方法的签名是

                       class DB:
                       def execute(query: Template):
                                  ...
                      

                      如果实现时只是将模板中的所有内容连接成一个字符串,而不对模板参数进行任何处理,那就太奇怪了。如果你想要一个未经处理的字符串,你可以直接让参数成为一个字符串。

                    • 我没有。同样,您可能会处理变量以用于日志记录、保存或传递到其他地方,或者其他许多与 sanitization 无关的原因。

                    • 将模板参数带入数据库库的 `execute` 方法是一个大而明亮的广告牌级提示,表明该方法将处理模板参数,目的是使查询安全。文档也会描述这种行为。

                      你说得对,这些库的作者可以选择对模板参数做不同的处理。但出于正常界面设计的考虑,他们都不会这么做。

                      库的作者也可以在数值类型上编写一个 `plus` 函数的实现,该函数接收另一个数值类型,并返回一个串联了两个数字的字符串,而不是将它们相加。

                      但没人会这么做,因为像这样行为极其惊人的库不会被任何人使用,而库作者也不想写无用的库。这也是同样的道理。

                    • 原评论说它会替换

                       db.execute("QUERY WHERE name = ?", (name,))
                      

                       db.execute(t "QUERY WHERE name = {name}")
                      

                      理论上,`db.execute`确实可以忽略语义,将模板和变量连接在一起组成字符串,而不进行任何消毒,但它声称要替换的语法不也是如此吗?

                      仅仅因为模板(或以前单独传入变量的语法)

                    • 当然,上行线程中提出的 safe() 函数也可能只是在做日志记录。

          • 因为 t 字符串不会创建字符串,所以如果库不支持 t 字符串,调用就会出错。

      • 这样一来,懒得制作好的类型和类的人就可以在不做正常代码的情况下接近正常代码了……

        想象一下,在编写 SqL 时,你可以直接将用户输入的内容放入查询字符串中。

        记住,现在是 2025 年,躺下吧,别哭了。

    • Python 并不是第一个获得此功能的。它出现在 JS 中已经有一段时间了,在此之前还出现在 C# 中(不知道是源于 C# 还是从其他地方借鉴来的)。Python 采用它的部分原因是基于其他语言的成功经验。

      • 这真的很酷。我不使用 JS 或 C#,所以不知道这个,但这是个好主意。

    • 假设您还需要在 SQL 中格式化非数值(例如列名),那么 `execute` 函数应该如何区分应在字符串中格式化的内容与参数化的数值?

      • 和当前一样:库提供了某种 “标识符 ”包装器,您可以将其应用于这些标识符。

        • 有道理。如果 Python 允许自定义`:`后的格式选项就更好了。

          这样您就可以直接在 t-string 变量中对标识符进行编码,而不是使用一些 “带外 ”逻辑。

          • > 有道理。如果 Python 允许自定义 `:` 后面的格式选项就更好了。

            它允许,`Interpolation` 对象包含一个任意的 `format_spec` 字符串: https://peps.python.org/pep-0750/#the-interpolation-type

            不过,我认为以这种方式使用格式规范既可疑又有风险,因为这会让汇负责白名单值,而这意味着源和汇之间的任何处理都会成为重大风险。这与 HTML 模板提供 “原始 ”输出的问题是一样的,现在你必须知道要对上游值的任何修改进行审计,而这些修改都是在上游值结束时进行的,这比 “原始标记 ”值被重新定义时要难得多。

            > 在这种情况下,我们必须使用 “带外 ”逻辑,而不是 “带内 ”逻辑。

            事实恰恰相反,把它移到格式规范中就是 “带外”,因为它没有附加到值上,它只是说 “这里的任何值都是安全的”,而这通常是不正确的。

            除非你把格式规范作为一种信号,表明一个术语应该使用标识符转义规则而不是值转义规则(这一点只有语汇库知道),而 “标识符 ”包装器仍然是绕过这一点的一种方法。

            • > 除非你使用格式规范作为一种信号,表明一个术语应该使用标识符转义规则而不是值转义规则(这只有数据汇才知道)。

              这在 SQL 应用程序中应该很常见。写下 “select {name:id} from {table:id} where age={age}”(从{table:id}中选择{name:id},其中年龄={age}),并确信 SQL 将被正确格式化,内插默认为(安全的)字面值,这将是一件非常美妙的事情。

          • 这篇文章确实提到,接收模板的函数可以访问每个插值的格式化选项,因此您大概可以为此滥用可用的格式化选项吧?

      • t-strings 错过了 f-strings 提供的 format_spec 等功能吗?

        顺便说一下,format_spec 在模板结构中是可用的,因此函数编写者至少可以进行运行时检查。

      • 所有的 Python 不是都没有(接近)编译时检查吗?

        • Python 在运行代码之前会做一些检查。例如

           print(“hello”)
              def f():
                  非本地 foo
          

          gives:

           语法错误:未找到非本地 'foo' 的绑定项
          

          在打印 hello 之前,注意 f() 甚至没有被调用。

          • 我认为它出错的原因是无法生成有效的 AST,这意味着无法生成有效的字节码。“<word> <word>”只有在其中一个是保留字的情况下才是有效的语法。当然,“nonlocal(foo) ”也没有问题。

            • 不,它出错是因为 `nonlocal foo` 请求在闭包中查找名称 `foo`,但 `f` 没有这样的闭包(函数外定义的 `foo` 是全局的)。nonlocal “是与 ”global “相同的关键字,但用于外层函数而非全局命名空间;另请参阅 https://stackoverflow.com/questions/1261875

            • > “<word> <word>”只有在其中一个是保留字时才是有效的语法。

              非本地 “是一个关键字

    • > 允许库开发人员对 {} 扩展做任何他们想做的事情是件好事,而且可能会产生一些好的用途。

      我完全不同意这种说法。看看 Log4J 在获得类似自由后的下场。

      • 我想这就解决了 log4j 漏洞,不是吗?

        据我所知,log4j 允许在传递给日志函数的任何字符串中恶意扩展 ${}。因此,记录用户生成的代码将是一个安全漏洞。

        但是 Python 的 t 字符串不会扩展用户代码,它们只会扩展字符串字面。

    • 现在,只要有一个不熟悉 t 字符串的人(几乎每个人都会这样做–知道 f 字符串及其格式化功能的人仍然很少)使用 f 来代替 t 字符串,你就会有麻烦了。

      • 任何正常的函数库都会在你将字符串传递给一个需要模板的函数时出错。而且该库也会有类型,所以你的集成开发环境会在你使用之前告诉你。

        • 此类库函数往往也接受字符串作为有效输入。例如,GP 中的 db.execute 通常使用字符串,允许非参数化的 SQL 查询。

          • 库应该拒绝字符串。如果需要非参数化查询,可以要求用户提供不带 {} 的 t 字符串。

        • 这将严重破坏向后兼容性。在许多情况下,这可能不值得。

          • Javascript 在这方面已经有了先有技术。

            一个库可以扩展现有的数据库库,如 “pg”,这样 PgClient#query() 和 PgPool#query() 就需要字符串模板语句。

            这样,’pg’就可以继续使用字符串,而那些想要漂亮的模板字符串的人则可以使用小型扩展库,小型扩展库还可以避免意外将字符串传递到查询函数中。

          • 但现在,至少语言有了必要的绳索(也有机会在文化上推动坚持这一点。)

      • 这是一个问题,但从本质上讲,它归结为不了解情况的人无法摆脱不信任输入的现有风险。解决的办法应该是加强教育和改进工具(linters、SAST),t-strings 可能对这两方面都有帮助。

        • t-strings 允许构建完全不接受字符串(或需要某种选择)的应用程序接口,而且总是会在这种情况下出错。这就是好处。

          不得不写

           cr.execute(t“...”)
          

          即使在没有格式化内容的情况下,也不是什么大问题。

      • 我想,“str ”和 “Template ”之间的 “接口表面”(属性,包括可调用的属性)缺乏重叠,应该可以将这种问题扼杀在萌芽状态–传递一个 “Template ”并需要实际 “实例化 ”它–访问被传递对象上的 “strings ”和 “values ”属性,但在运行时,如果尝试对传递的字符串进行操作(例如,将 “t ”字符串与 “f ”字符串混淆),很可能会失败?

      • 不会,因为它们不返回字符串,所以好的库作者会在发生这种情况时引发类型错误,原因正是如此。

  2. > 此外,我希望工具生态系统能够支持 t 字符串。例如,如果 t 字符串的内容是 HTML 或 SQL 等常见类型,我很希望看到黑色和褶皱格式的 t 字符串内容,以及 vscode 给这些内容着色。

    对 t 字符串的这种处理方式太奇怪了。要推断模板字符串是否会变成有效的 HTML 或 SQL,唯一的方法就是根据字符串中的明显语法,而这只能以临时的方式进行,与模板字符串功能毫无关系。

    根据该功能的设计,字符串本身并不表明它是什么类型的内容,也不表明它最终会被转换成什么类型的内容。所有这些都由转换函数处理。

    正如其他人所补充的那样,类似于 sql “select * from {table}” 这样的语句本可以做到这一点,但在转换之前,甚至无法保证模板中将被转换函数转换为有效 sql 的内容应该是任何类型的有效 sql。据你所知,“给我{table},但只有{columns}”可能会在模板处理后被转换成有效的 sql。

    • 我想你只会看到类似的模式:

       html(t“<h1>Hello</h1>”)
      

      荧光笔和静态分析器将以此为关键。

      JavaScript 的标记模板字面实际上和这个一样灵活,因为您可以动态地选择标记函数,只是很少这样做,所以工具会根据函数名称进行大量假设。Python 工具基本上也可以做同样的事情,只是不支持没有嵌套在名称明确的处理函数中的 t 字符串。

      • 另一种方法是类型提示,例如 `title: HTMLTemplate = t“<h1>Hello</h1>”` 。

    • 最初的 PEP 和最初的讨论中都有这个问题。我们删除了它,让它在以后出现。有不同的方法来表示这种语言–有些对工具更友好,有些更健壮。

    • > 这是对 t 字符串的一种奇怪的处理方式

      我理解为什么一开始看起来很奇怪!

      正如保罗所提到的,在 PEP 750 的开发过程中,我们花了很多时间来考虑这些问题。最后,我们得出的结论是:(a)PEP 为工具提供了许多潜在的采用方法(而不仅仅是您建议的方法,正如其他人所指出的);(b)这最终是需要更广泛的工具社区团结起来共同解决的问题,可能不属于 PEP 本身的范围。

      因此,考虑到这一背景,我确实希望我们能看到生态系统的调整!:-)

    • PyCharm (以及其他 JetBrains IDE)至少在 10 年前就支持在 Python 字符串中使用注释 [0] 进行语言注入。在最坏的情况下,格式化器没有理由不以结构化和确定性的方式应用自动格式化。

      但这也不是唯一的选择!IntelliJ for Java 还支持参数注释 [1],这意味着在使用给定函数的字符串字面意义时,都会获得语法高亮显示:

       public void query(@Language(“SQL”) String sql)
      

      在 Python 中,tying.Annotated 似乎是专门为这样的目的而设计的 [2]:

      > 如果一个库或工具遇到注解 Annotated[T,x],并且对元数据没有特殊的逻辑,它应该忽略元数据,并简单地将注解视为 T。

      因此,像这样的代码应该是完全可行的:

      预] SQL = Annotated[模板, “语言”, “SQL”]

       SQL = Annotated[模板, “语言”, "SQL

      def query(sql_query: SQL):

      # 对模板进行消毒处理

      query(t “SELECT * FROM foo WHERE bar={bar}”)

      你说得没错,我们实际上不应该要求使用模板字符串来实现这一点!如前所述,JetBrains 一直在这么做。但是,也许模板字符串会对这样的目的有足够的用处,以至于工具实际上会发展到在格式化器和其他编辑器中支持它(也许 PyCharm 可以从 JetBrains 获得 Java 所拥有的一些更好的支持)。

      [0] https://www.jetbrains.com/help/pycharm/using-language-inject

      [1] https://www.jetbrains.com/help/idea/using-language-injection

      [2] https://docs.python.org/3/library/typing.html#typing.Annotat

      • 我是 PEP 的作者之一,也是 JetBrains 的一员。我正在与 PyCharm 团队讨论这个问题。

    • 不能用类型注解来实现吗?SQLAlchemy 可以有一个 SQL 类型,这样像 mypy 这样的工具就可以看到一个模板实例,并确认你正在安全地使用它,但 Black、Ruff 或 SQLFluff 可以查找更专业的 Annotated[Template, SQL],以意识到模板可以被格式化为 SQL,而像 Django 这样的工具甚至可以有 Annotated[Template, Email]、Annotated[Template, HTML]或 Annotated[Template, JSX],以指示相同模板语法针对的上下文。

      • 这就是我们在第一次修订 PEP 时讨论的内容(“Annotated ”的使用)。

        我们希望在 PyCon US、EuroPython 等会议上建立一个社区,并解决其中的一些问题。JSX/TSX 世界确实有很好的工具。我们可以为那些需要的人提供这些工具,也许在某些方面会更好。

        • 很有意思,谢谢你的背景介绍。我一直很好奇 Astral 打算在这个领域做些什么,但也担心他们的资金用完后会发生什么。

    • > 任何东西要想推断出模板字符串应该转化为有效的 HTML 或 SQL,唯一的方法就是根据字符串中的明显语法来推断

      不是唯一的。你还可以看看它是如何使用的。你的编辑器可以了解一些流行库如何使用 t-string,跟踪哪些 t-string会被传入这些库的函数中,并以此推测 t-string应遵循哪些语法。

      这算不算作弊?从某种意义上说,是的,但它也很有用,对很多程序员来说可能是值得的。

    • Python 有类型注解已经有十年了,现代集成开发环境可以解释它们。

      编写 `query: SQL = t “SELECT …”` 对于这样的 DX 提升来说,代价实在太小了。

  3. 这会允许像下面这样整洁的 SQL 语法吗?

        city = 'London'
        min_age = 21
        # Find all users in London who are 21 or older:
        users = db.get(t'
            SELECT * FROM users
            WHERE city={city} AND age>{min_age}
        ')
    

    如果 db.get() 函数接受模板,它就应该接受,对吗?

    这将是我所见过的使用 SQL 的最佳方式。

    • 这绝对是实现该功能的初衷之一–https://peps.python.org/pep-0750/#motivation。

      在字符串值的插值方面拥有更多的控制权在我看来是一种胜利。

    • 你需要用三引号来跨多行,但没错,这正是它的工作原理。`db.get` 将接收一个模板实例,其中存储字符串部分,如 `(‘n SELECT * FROM usersn WHERE city=’, ‘ AND age>’, ‘n’)` 和插值部分,如 `(Interpolation(‘London’),Interpolation(21))`。然后,它负责根据这些信息组装并执行查询。

      • 据我所知,这样做的功能没有那么强大,因为你可以这样做:

         t "INSERT INTO mytable VALUES ({s}, {s[::-1]})”
        

        但你不能这样做:

         mydb eval {INSERT INTO mytable VALUES ($s, [string reverse $s])}
        

        相反,您必须写

         设置 t [字符串反向 $s]
            mydb eval {INSERT INTO mytable VALUES ($s, $t)}
        

        在 Tcl 中没有理由不具备这样的功能:只是 SQLite 的作者没有这么做。

    • 使用准备语句的正确方法难道不是这样吗?如果我们这样做是正确的,那么从 Python 中使用 SQL 又能得到什么呢?

      • 因为,正如文章所说,人们并没有使用准备语句。相反,他们传递 f 字符串,因为它们更方便。

        • 除了为了保持向后兼容性,我们很可能会得到只接受模板的新方法,从而完全规避了阻止人们传递字符串的努力。

          15 年前,当我开始学习 PHP 时,预处理语句是运行 SQL 查询的推荐方法,现在,任何编写容易受到 SQL 注入攻击的代码的人都不应该再写代码了。

          • 是的,但另一种选择就是永远不对语言进行改进,因为遗留代码依然存在。

        • 如果字符串是语法而不是类型,那么由此产生的模板就像是指定准备语句的合理方式。

      • 我已经用了很多年了。为了生成模型,您必须使用可视化设计器,该设计器既慢又有漏洞。

        如果你不得不每天打卡,看着用户界面在你点击保存时破坏你的数据库关系,这种体验一般都很烦人。

    • 谢谢,我讨厌它。虽然这是个很好的语法糖,但 SQL 注入漏洞与正确参数化查询之间的区别现在只剩下一个字母,很容易被忽略。

      • t-string 生成的 Template 对象没有 __str__() 方法。你不能错误地使用 f-string 代替它。要么代码期待一个字符串,如果传递给它一个 Template 就会把它炸掉;要么代码期待一个 Template,如果传递给它一个字符串就会把它炸掉。

        • > 或者代码希望使用模板,如果使用字符串就会导致代码崩溃。

          这就是问题所在–在大多数情况下可能不会出错。

          很多 SQL 查询根本不需要任何参数。你只需获取表中的行数或其他信息。原始字符串完全没问题。

          sqlite3 真的不允许使用字符串吗?它是否会强迫你使用模板,即使模板不包含任何参数?

          你可以说它应该这样做,但这对输入并不友好,而且会破坏向后兼容性。也许你可以在模块中设置一个标志来启用这种严格的行为,这样十年后它就会成为默认设置了。

          •  db.execute(t “Select Count(1) from someTable”)
            

            在 “原始字符串 ”的未参数化查询中 “强制 ”使用一个额外的字母。t 字符串本身在没有参数的情况下也能正常工作。

            切换到纯模板 API 肯定会遇到向后兼容性的障碍,但纯模板 API 在输入方面看起来并没有那么 “不友好”,唯一的区别就是每个字符串前都有一个 `t`,而不管参数的数量是多少。

            • 当然,只是我在其他地方不需要这么做。

              如果不在字符串中加入变量,我从来不会在字符串前加 f。

              我通常习惯于 Python 输入的自由性。如果它期望传递一个 tuple,我通常可以传递一个 list;如果它期望传递一个 float,我可以传递一个 int;我通常可以直接传递一个项,而不是一个包含单个项的 tuple。Regex 函数使用常规字符串或 regex 字符串,而不会强制使用 regex 字符串。

              在所有情况下都强制使用单一特定类型的字符串,这与 Python 传统的操作方式大相径庭。

              这更安全,我明白。但这肯定不太友好,所以我很想知道模块维护者决定如何处理这个问题。

              • > 在所有情况下都被迫使用单一特定类型的字符串,这与 Python 传统的操作方式大相径庭。

                也许这就是部分脱节的原因?“t-string “可能是一个令人困惑的俗称,因为它们不是字符串,而是模板。运行时类型就是模板。它是一种与字符串截然不同的 duck 类型。作为一个 duck-typable 对象,它甚至不能隐式或显式地像字符串一样运行,故意没有 `__str__()` 方法,而且 `str(someTemplate)` 也不能像你期望的那样运行。从字符串没有隐式转换,您必须使用它自己的字面语法:它不是字符串类型,而是模板类型。

                在这里,Python 对 Template 还是很宽容的(它仍然是一个 duck 类型)。如果一个函数期望一个 Template,而您不想使用 t“” 速记语法,也不想使用 string.templatelib 中的 Template 构造函数,您只需要一个简单的对象类,它有一个正确形状的 `__iter__()`,和/或有 `strings` 和 `values` 元组。

                当然,对于某些类型的 API 来说,支持 str 和 Template 的联合作为 “自由 ”选项是有意义的,但这与 list 和 tuple 的联合或 int 和 float 的联合是不同类型的自由支持,后者是更接近的类型 “域”。Template 不是字符串,在运行时看起来也不像字符串(尽管在语法上它在 “编译时 ”看起来像字符串)。鉴于 Template 中的 `__iter__()`,将 Template 与 List 或 Tuple 进行联合可能比与单个字符串进行联合更有意义/更 “自然”。

              • 呃……这不对吗?Python 可以更自由,但并不总是如此。这完全取决于工具。库需要时间来跟上,但我可以肯定,人们会创建强制执行 t 字符串的库,即使他们是在引擎盖下为传统库解构 t 字符串。

                • 什么不正确?Python 输入通常是自由的。我没说总是。

                  你是说在 Python 中,严格的输入是传统上常见的,而自由的输入是例外?

                  • Python 让你毫无理由地盲目交换不同类型。根本就没有。

                    是的,当类型不同时,Python 对输入严格要求是很常见的。例如,试试

                    Decimal(‘3.0’) / 1.5

                    你会得到一个错误,而且理由充分。

                    • 但是……它通常会这样做。例如,试试

                       十进制('3.0') / 2
                      

                      效果很好。它对浮点数不起作用,这是有道理的。这就是问题的关键所在–它的总体理念是对类型采取相当宽松的态度,除非有充分的理由不这么做。你甚至可以做一些蠢事,比如

                       4 + True
                      

                      然后得到 5。如果这还不算 “毫无理由地盲目交换不同类型”,那我就不知道什么才算了。你甚至可以把十进制对象乘以假,然后得到答案…

                      或者就像我最初的例子一样 — Regex 模块并不局限于 r 字符串。它很乐意使用正则字符串。Python 的总体思想是自由地处理输入。甚至类型提示也是附加的。现在,它不像 JavaScript 那样允许例如 “4”+1,但它仍然非常自由。我只是不明白你怎么能说不是这样。

                    • 我知道十进制/整数混合的问题,但这是有原因的:在这里混合是没问题的。但浮点数就不行了(精度问题)。bool/int 混合并不 “好”。这是 Python 永远无法摆脱的一个糟糕的实现细节。实际上,我很惊讶你竟然会想到用这个来举例,如果我的团队中有任何程序员这么做,我都会以严重失职为由将其开除。

                      它之所以有效,是因为 Python 函数中没有 bool 类型。True 和 False 只是有名字的整数。这很愚蠢,不应该这样工作,但由于历史原因,它还是这样工作了。

                      你的 regex 例子也没有意义。字符串和 r-strings 没有区别。对于解释器来说,它们简直就是一回事,那么 regex 函数怎么会强制你使用 r-strings 呢?也许它们应该是不同的,但由于历史原因,没有 Python 4.0 它们就不可能是不同的。

                    • > 实际上,我很惊讶你居然会用这个来举例,如果我的团队里有任何程序员这么做,我都会以严重过失为由开除他。

                      你似乎在和我进行不同的对话。

                      我只是在描述 Python 的现状。我不是在为它辩护。我知道为什么可以在数字后面加上 True,否则我也不会想出这个例子。我很清楚 r 字符串就是字符串。Python 完全可以将它们作为一个独立的对象,以防止人们犯反斜杠错误,并限制 Regex 函数使用它们,但它没有这么做。

                      我唯一想说的是,“Pythonic ”的东西在接受什么方面往往很自由。即使存在类型提示,也不会强制执行。你似乎认为不应该这样。好极了!但不管怎么说,声称它不是那样的–Python 是某种严格的语言–只是对它的错误描述。

                    • > 我唯一想说的是,“Pythonic ”的东西在它们接受的东西方面往往是相当自由的。

                      把字符串当字符串用,把 int 当 int 用,这不是 “相当自由的接受方式”,这只是编程语言理论的 101 条!我想你把鸭子打字误认为 “自由接受 ”了,这两者不是一回事。一直以来,人们都希望使用兼容的接口,即使在标准库中也是如此。比如说,我就因为在函数期望使用列表时传递了一个生成器而被咬过好几次。

                    • 我完全没有弄错。是的,duck 类型是非常自由的接受方式,但 Python 代码往往会走得更远。我可以举一百万个例子–比如在 Python 的 isinstance() 中,第二个参数可以是一个类型或一个类型元组,或者在 sqlite3 中,您可以在连接或游标上运行查询,甚至不要让我开始讨论 Matplotlib 或 Numpy。在 Python 中,在可能的情况下,接受多种类型会让程序员更轻松,这只是一种习以为常的做法。如果你不知道这是 Python 的通用模式,我真的不知道还能告诉你什么。

                    • > Python 的一般哲学是对类型相当自由,除非有很好的理由不这么做。

                      而这里就有一个很好的理由。

              •  如果不在字符串中加入变量,我从来不在字符串前加 f。
                

                如果你的 f 字符串中没有变量,Linters 甚至会抱怨。我想对于 t 字符串也是如此。

                • 基于上述原因,我不确定 t 字符串是否也会如此。我认为框架/库还需要一段时间来适应(同时保持向后兼容性),而最佳实践还需要一段时间才能进入我们的 linting 和其他工具。

                  • 如果可以使用字符串的任何地方都可以使用 t-string,那么非参数化 t-string就是一种代码缺陷(衬砌错误)。如果有专门的模板字符串 API,那么停止使用普通字符串就有可能破坏向后兼容性。

                    • > 如果你可以在任何地方使用字符串,那么你也可以使用 t-string。

                      不能,它们是不同的类型。t-string 不是 `str` 。

                      这需要良好的框架/API 设计来利用这一点。

                    • 库的编写者最终必须决定是否接受这两种类型。对于数据库游标,你是接受常规字符串 + 参数参数和模板字符串?还是为此专门开发一个新的 API?

                        cursor.execute(“select * from x where foo=?”, {foo=1})
                        # while also allowing
                        cursor.execute(t“select * from x where foo={foo}”)
                        #Vs 
                        cursor.executetemplate(“select * from x where foo={foo}”)
                      

                      如果 “execute ”需要字符串和 t-string,那么我认为使用不带参数的 t-string是个问题。如果有一个新颖的 API 专门用于 t-字符串,那么你就意味着要进行大范围的破坏性更改,因为在提供参数的两种方式之间存在分裂。

                    • 我的意思是,库作者需要仔细考虑这一点。如果你正在编写一个涉及注入攻击的库,那么–从长远来看–你几乎肯定不想要一个接受`Union[str, Template]`的方法。你可能要么完全避免接受 `str`,要么提供两个单独的方法。一段时间的废弃似乎是不可避免的。

                    • 是的,“t-string ”可能是个误称,因为它们在运行时实际上是一个模板对象(来自 string.templatelib)。

                • Linters 抱怨的原因是,f “hello ”和 “hello ”是完全相同的字符串。

        • 我猜很多代码都希望使用字符串来保持向后兼容性。

          • 我认为现有库更有可能引入使用 t 字符串且类型安全的新方法,而不是完全违背使用 t 字符串 API 的初衷。

          • 我猜现有函数都不会扩展到允许使用 t 字符串,原因就在于此。相反,我们将创建只接受 t 字符串的新函数。

            • 与 strcpy(不,strncpy……不,strlcpy……不,strcpy_s)一样,这里也存在一个显而易见的风险,那就是文档的生命力往往会超过代码的生命力,人们会不断地从 tutorails 和旧代码中粘贴,以至于新的替代品很难穿过这些噪音。

              我认为,尽管有些 w3schools 教程很糟糕,从糟糕的 Stackoverflow 答案中复制代码也很糟糕,但追溯到 MSA 和 90 年代的免费 cgi 档案,只有人工智能式的编码代理才能消除代码片段永存的趋势。

              另一方面,废弃现有方法是语言的必经之路。这是有道理的。我不认为这里有一个简单的答案。但是,语言也是一种文化,关于代码质量的共同信念可以成为尊重传统与构建新语言之间的中间路线。如果静态检查就像 “use strict”(严格使用)这样的指令一样简单,而且 “检查是件好事 ”的观念也能传播开来,那么在代码继续运行的同时,共识也能慢慢发展。

              • Python 库废弃和移除功能是很常见的。这让人们很生气,但由于这个原因,这是件好事。

            • Python 类型检查器/衬砌器/什么的有能力在调用某些函数时发出警告或出错吗?如果能最终强制迁移到只接受 t 字符串模板的新函数,那就更好了。

              • 是啊。不久前,我在浏览一些不熟悉的代码时注意到,我的编辑器将 `datetime.utcnow()` 的使用渲染为 “击穿”。当我用鼠标悬停它时,得到的信息是该函数已被弃用。

                原来,我的编辑器 (vscode) 和类型检查器 (pyright) 发现 `datetime.utcnow()` 被标记为已废弃(我知道可以使用 Python 3.13 中的 `@deprecated` 装饰器或 `__future__` 来做到这一点;我想在这种特殊情况下是用另一种方法),因此将其渲染为已通过。

                它教会了我 A) `utcnow()`已被弃用;以及 B) 如何将我们内部代码库中的一些代码标记为已被弃用,并鼓励我们的开发人员尽可能使用新的、更好的版本。

        • 如果不是专门围绕模板编写的全新库,此类代码目前会接受字符串,而且为了向后兼容,很可能会继续接受字符串。

        • 这完全取决于实现。对于现有的库,我希望能像

              def get(self, query):
                  if isinstance(query, template):
                      self.get_template(query)
                  else:
                      self.get_old(query) #Don't break old code!
          • 接口(接受模板)和不安全接口(接受字符串)可能会更安全。

            现在,无论维护者是引入 `getSafe` 并保持原有的行为不变,还是做出突破性的改变将 `get` 变成 `getUnsafe`,我们都将拭目以待

        • 如果 Python 不是一种可以让你深入内部并为不应该定义 __str__() 的东西定义 __str__() 的语言的话,这将是一个很好的论据。人们肯定会这么做,因为,你知道,他们只是需要一些该死的工作,这样他们就可以关闭任何 ticket,并让一些跟踪关闭时间的指标保持满意。

          • 程序员在工作中的懒惰和糟糕并不能成为不改进语言的理由。

      • 类型检查器来救场了啊哈哈,我觉得如果类型不匹配,db.get 也可以提出问题?

      • 我想这是你对模板工作原理的误解。少一些恨,多一些爱,也许有助于避免这种头脑发热的误解;-)

        你为什么认为更改一个字母会导致漏洞?你指的是哪个字母?

          • 哇,这只比用小写字母 L 与数字 1 或字母 O 与 0 来表达明显的不同要好一点。

          • 这将导致字符串传递给 get(),并引发错误,因为 get() 的操作对象是模板,而不是字符串。

              • 没有一个正常的库会这么做。如果它们确实允许你传递原始字符串,那就应该是一个不同的函数,并明确记录其风险。

                它所取代的是每个库都有自己的定制 API,以在默认/安全路径上创建预处理语句。现在他们可以直接使用模板。

                • 那么每个希望保持向后兼容性的库呢?

                  或者你的意思是,例如,每个数据库模块都需要使用支持模板的新名称实现一组新的查询函数?这可能是正确的做法,但会很难看……

                  所以,现在你必须记住永远不要使用 “execute()”,而要使用 “execute_t() ”之类的。

                  • 你不必记住它,你可以使用弃用警告和 lint 工具来提醒你。(直到最终安全 API 成为唯一的 API,那时你就真的没什么好记的了)。

                  • 我认为他们当前的安全函数并不使用字符串,而是使用某种准备好的语句?因此,他们可以让该函数使用准备语句或模板,并废弃准备语句。

                    如果库中的函数使用字符串并将其作为 SQL 执行,那么他们可能不应该使用模板来代替字符串,但我希望这已经是一个单独的显式不安全函数了。

                    • 对于 sqlite3,它绝对需要一个普通字符串。

                      如果要替换参数,可以在每个参数的字符串中加上’?’,并提供一个包含变量的额外(可选)元组参数。

                      因此,不存在明确的不安全函数。这就是我的观点。

                    • 明白了。我猜他们会想废弃这个函数,然后创建一个只接受模板的新函数,这肯定很烦人!我想他们已经在准备好的字符串和原始字符串之间做了更多区分,这样会更容易些。

      • 我还在想,这样做会不会很容易弄巧成拙?一不小心就可能太快进入琴弦,而无法正确地逃脱。

        • 这是库作者的问题,所以可能性较小,因为库作者往往人数较少,而且对于流行的库来说,这类更改会得到合理数量的关注。

  4. 个人感觉,这个特性太专注于一个问题,不可能成为一个通用特性。Python 越来越庞大了。当人们问我 Python 是否简单易学时,我不得不说:“基础知识是的,但要学习整门语言……就不那么容易了”。

    我觉得从这个意义上说,Go 的有趣之处在于它几乎摒弃了所有功能。老实说,我不确定泛型是否值得,因为它们增加了很多复杂性,虽然它们很好,但我并不太需要它们。在我看来,将语言保持在原有重点的总体思路是正确的。C++ 就是一个最极端的例子,它的语言本身几乎不像它最初的样子。

    • Python 一直以来都是一种包含电池的语言,因此对模板化字符串插值(其他语言几十年来一直拥有的功能)的不满似乎很奇怪。

      它远比 textwrap 这样的小工具或 Python 捆绑的 tkinter 实现这样的巨型软件包更重要。

      • 对我来说,包含电池也意味着一个大型标准库,而不是一种大型语言。

        • C# 有 InterpolatedStringHandler,虽然不完全一样,但(据我所知)试图解决相同的核心问题。

          • C# 的 InterpolatedString 非常接近,其曲折之处在于 C# 可以依赖静态类型来保证安全,因此 “f-string ”和 “t-string ”变体使用相同的字面语法,并取决于它们被传递给哪个函数,而在 Python 和 Javascript 中,它们的字面语法不同。Python 选择使用不同的字面前缀(“f ”与 “t”),而 Javascript 则选择使用函数调用语法作为前缀(`templateString` 与 html`templateString`,其中 html 是作用域中的函数)。

          • 对于像这里这样的情况,它更接近于 FormattableString,这也是 EF Core 的可组合 FromSql 方法的基础。两者都处理自定义插值,但角度不同,适用的场景也不同。

        • 许多语言都有类似的功能。

          例如,Python 有 % 操作符,它是一种模板格式,允许根据模板字符串插值,并具有各种类似 printf 的功能:https://python-reference.readthedocs.io/en/latest/docs/str/f…

          此外,Python 还有字符串的 .format 方法,它是一种模板格式,允许根据模板字符串对数值进行插值:https://www.geeksforgeeks.org/python-string-format-method/。

          再比如,Python 的 f-strings 也是一种模板格式,可以根据模板字符串进行插值:https://www.geeksforgeeks.org/formatted-string-literals-f-st…

          此外,您还可以发现像 Python 这样的语言拥有丰富的第三方模板解决方案生态系统。这些解决方案通常用于渲染整个网页,但其中许多都有相对简单的方法,可以在相当合理的代码量内使用其模板功能,如果你只想拥有一种允许基于模板字符串插值的模板格式的话。

          因此,正如你所看到的,许多其他语言都具有这种功能,你可以从我在这里向你展示的所有示例中了解到这一点。

          (为了让那些可能觉得这太微妙的人明白……不知何故…… 我不喜欢这样做,只是因为 Python 已经从 “应该有一种–最好只有一种–显而易见的方法 ”变成了 “有六七种方法,如果它们都错了,Python 3.x+1 将引入第七种”,而我只是觉得这样做得不偿失)。

          • 我觉得这是一个令人困惑的回复。首先,你似乎混淆了急切字符串插值和这种懒惰/延迟模板功能。我特别问的是具有后者的语言。更令人困惑的是,你把正在讨论的语言作为一个例子……如果它已经有了这个功能,他们就不会再添加新功能了。

            然后,你又提到了第三方模板解决方案,但这已经偏离了主题,因为我们正在讨论语言的内置功能。我很清楚有很多模板解决方案可以解决一般的文本模板问题。

            总之,我特别想知道有哪些语言允许使用_延迟/快速_处理进行字符串插值,这也是我对这一功能的理解。似乎有一些,但在阅读了其他评论之后,似乎并不太常见。

            • 如果我是 Python 的负责人,我可能会冻结它的功能。它已经太大了,但缩小它又不切实际。

              如果做不到这一点,因为它很可能会失败,我就会把新功能添加的门槛抬得很高。

              在过去的几个版本中,该项目似乎实际上降低了标准。它曾经是我最喜欢的语言之一,甚至是我最喜欢的语言之一,但现在却成了我尽量避免使用的语言,甚至是我主动建议不要使用的语言。我自己写的时候还可以,但使用其他人的代码就越来越难了,因为他们一直在使用所有的功能。

        • Scala 自 2014 年开始使用。

          Java 22 在预览版中也有该功能,但在 23 中被移除了,经过改进后会再出现。

    • 这是一个非常简单实用的功能。我不会说它使语言过于臃肿。描述符和元类要复杂得多,影响也大得多,而且已经在语言中存在了很长时间。已经有几十年了吗?

      • 这个功能并不复杂,但我们必须将代码中可能出现的每一个功能都记在脑子里。即使现在已经很熟悉了,但当你只在代码的一小部分中使用了这个功能,而没有在其他地方使用,两年后再读这段代码时会发生什么呢?这就是添加有用功能的问题所在,而这些功能只能在少数关键地方使用。我并不是说 Go 是一种完美的语言,远非如此,但将限制功能数量作为一个总体目标,在我看来是更多语言应该努力实现的。

        • 我并不是反对理想情况下语言应该有更少的功能。我反对的是 “Python 正在变得庞大”,因为它已经庞大了很多年:)

      • 是的,Python 很久以来都不是一门简单的语言,甚至从来都不是。这可能是对这种语言最大的误解–认为它友好的语法意味着语义简单。事实并非如此。

        • 我想说的是,即使不是我所知道的最深奥、最复杂的语言,Python 也是其中之一。C++ 是另一个竞争者。使用元类、多重继承和操作符重载所能做的事情非常惊人。

          我只是很高兴,作为一门语言的普通用户,你在大多数时候甚至根本不需要考虑或使用这些。

    • 我真的很高兴 Go 能为那些喜欢简单甚至简单到令人沮丧的语言的人服务,我希望它永远不会改变。但我也很高兴有其他语言的存在,因为对于我们这些人来说,学习一些语法并不是障碍,而且有便捷的方法来做一些常见的事情也是非常有价值的。

    • 在维护方面有一个有趣的权衡。Python 的 stdlib 意味着跨项目的一致性更强,而且你可以承担错误处理等基本工作,而这些工作你必须在每个 Go 程序中逐一检查,当你有很多小程序时,这些工作就会增加。

      这一点在 AWS Lambda 上尤为明显,在那里,你可以让很多有用的东西运行数年而无需做更多事情,只需每隔一两年更新一下运行时版本即可,但这也是一个非常有主见的架构,因此并非对每个人都是全局最优。

  5. 大讨论(414 点,10 天前,324 条评论)https://news.ycombinator.com/item?id=43647716

    • 也许过几天再回来讨论是件好事,因为这样能获得更多信息,头脑也更冷静。

      😉

  6. 新的 x string 功能只能内置,感觉有点像 “作弊”。如果能做到以下几点就更好了:

     从 foo 导入 bar
        bar “zoop”
    • t-string 是一个模板对象的字面意义,是一个数据持有者,它实际上并不做任何事情,所以你只需调用

      bar(t “zoop”)

      888/1/cb321

    • Nim 就是这样。类似于 f-string 的等价物使用一个名为 “fmt ”的宏,它有一个简短的别名“&”。因此,你可以说

       导入 std/strformat
          让 world = "planet”
          echo &"你好 {world}”
      

      正则表达式模块通过 `re “regular expression”` 语法或 std/pegs 的 peg “解析表达式语法 ”等做类似的事情。其他例子可能不胜枚举。

      总的来说,通过用户定义的运算符、模板和宏,Nim 拥有各种类似 Lisp 的功能来扩展语言,但更注重静态,这有助于提高语言的正确性和性能。

    • 这是 PEP(750)中最初提出的想法,但后来发生了变化。如果您有兴趣,PEP 中有一节解释了为什么改为 t 字符串。

      • PEP 638 在我看来一直是对这一想法的概括。但在我看来,这确实是一个 4.0 功能,或者说是一个从一开始就需要设计的功能。(这就是为什么我在自己的脑海中做了一些这样的设计……)。

    • 没错。那么您可以使用

       sql”...”
        html”...”
      

      来实现运行时的类型安全。

      • 结果几乎没有任何改进。

        如果你向框架传递一个 “t-字符串”,它可以强制转义。

        你的建议是依赖用户(开发人员)的转义,而如果他知道的话,他已经会转义了。

        除非你建议仍然返回一个模板,但标记为一种语言。

        • 我想说的是,JS 的等价物是一个模板,但标记了一种语言。它具有模板的所有优点,但集成开发环境可以轻松地语法高亮显示字符串。这在 Python 模板中似乎更难实现,这让人感到遗憾。

    • 使用函数?

       bar(“zoop”)
      

      这只是语法问题,就像我们以前使用的

       打印 "foo” 
      

      后来变成了

       print(“foo”)
    • 在我自己的语言设计中(尚未公开–目前需要优先考虑更实际的事情),这绝对是菜单上的内容。除了最小的一组关键字外,其他关键字都是以编译时宏的形式实现的;我们的目的是通过编写 AST 操作代码,在 “本机 ”和实现语言中对关键字进行扩展。但算术表达式的处理以及更广泛的行/块结构都是硬编码的。(我从 Python 和 Lisp 系列中汲取了灵感)。

    • bar “zoop ”和 bar(“zoop”) 之间的差别不大,不值得这么做。

      我很喜欢 F 字符串,但在大多数情况下,我认为所有各种 X 字符串都应该是以字符串为参数的类。

  7. 这很酷,如果我们移植的是 JS 的功能,那么下一步我们会得到字典解包/重构吗?

    我太想要这个了,这是我重拾 JS 的主要原因:

     >>> {a, b=45, c=None, **d} = {'a': 234, xzy: 32456}
        >>> print(a, b, c, d)
        234 45 None {'xyz': 32456}
    • 另一种选择:

       >>> a, b, c, d = (lambda a, b=45, c=None, **d: (a, b, c, d))(**{'a': 234, 'xyz': 32456})
      

      诚然,这里的括号有点麻烦。

    • 就用这个非常合理的模式/s:

       >>> 匹配 {'b': 45, 'c': None}|{'a': 234, 'xyz': 32456}:
          >>> case {'a': a, 'b': b, 'c': c, **d}: pass
          >>> print(a, b, c, d)
          234 45 None {'xyz': 32456}
  8. 2025 年的 Python 禅宗:

     应该有一种--最好只有一种--显而易见的方法。
    

    2025 年的 Python 字符串格式化:

    – t-strings

    – f-strings

    – %-operator

    – +-operator

    – str.format()

    • 还有 $-strings (PEP 292) 🙂

      我认为这些都是不同的,但又相互重叠;我正在(慢慢地)编写一份字符串格式化指南,其中会考虑到所有这些,并试图强调在什么情况下我会选择其中一种而不是另一种。(顺便说一句,我个人最近避免使用 % 和 +;$ 在实践中并不常见;f-、t- 和 .format() 似乎都有很好的独特用途)。

    • 后三个一般不应该使用(“+”有时也不错,但并不适合格式化),但我怀疑我们的 py4 是否会去掉它们,因为 py3 已经让我们很头疼了。t 字符串只是一个没有应用 args 的 f 字符串,这确实让人感觉奇怪。

      • 我的理解是,它们仍然被推荐用于日志记录,因为使用 f-string 时,你总是要付出格式化的代价(但使用 str.format 时,代价是延迟的)。

    • t-strings 和其他字符串并不一样。

      这四种字符串格式化技术确实违反了 “一种显而易见的方法 ”的建议。

    • 我几乎不敢相信这个新的 t 字符串不是一个笑话。作为一个偶尔勉为其难的 Python 程序员,我对这些年来字符串格式化工具的增加感到遗憾。我的代码记录了连续出现的、(我)不完全理解的格式化字符串的历史。

      “情况: 有 14 种相互竞争的标准….” https://xkcd.com/927/

  9. 为什么这需要成为一种语言特性?这可以是一个单独的库,我们可以用括号代替字符串前的字母。我担心 Python 会走向 C++ 的老路

    • 作为一种语言特性,它为您提供了对词法范围的可控访问,例如模板字符串可以通过名称来引用变量,而不必将每个值作为参数传递。通过参数来引用变量会造成重复和/或容易出错。

      • 您可以访问函数的调用范围。比如

         current_frame = inspect.currentframe()
            env = current_frame.f_back.f_locals.copy()
        

        我是在 uplaybook 中完成的,这样你就可以做类似的事情:

         for module in ['foo', 'bar', 'baz']:
                ln(path=“/etc/apache2/mods-enabled”, src=“/etc/apache2/mods-available/{{ module }}.load”)
        

        这是一个类似于 ansible 的工具,使用 Python 而非 YAML 语法,因此使用了 Jinja2 模板。

    • 这个功能实际上不能是一个库。它需要在变量传入之前保留字符串,而库无法做到这一点,因为 f-strings 会立即替换所有值。t 字符串的前提是知道哪些值是 “硬编码”,哪些是变量。库不可能知道这一点。函数调用也无法访问局部变量。在没有语言支持的情况下,唯一的办法就是在每次函数调用中传递 `locals()`。

      • 现在你让我想知道,让一个类接受一个字符串并解析全局以找到它被调用的上下文有多难。也许会导致异常并滥用回溯?也许我们只需找到我们自己的 objectid…. 啊哈,我现在必须试试,但我在设置计时器。

        • 这比我想象的要容易,但也比我想的要花更多时间。事实证明,inspect 模块提供了实现这一目标所需的功能。

          这个虚拟示例会分割给定的字符串,如果其中一个值在调用者的上下文中,它就会将这些值保存在 self.context 中,并有一个输出函数将其组合在一起。显然,这个示例并不是非常有用,但它展示了库如何以类或函数的形式实现这一功能,而无需用户传递 locals()。

            import inspect
            class MyXString:
                """ will split on whitespace and replace any string that is a variable name 
              with the result of str(variable)"""
                  def __init__(self, string):
                      self.string = string
                      caller_locals = inspect.currentframe().f_back.f_locals
                      self.context = {}
                      for key in  set(string.split()):
                          if key in caller_locals:
                              self.context[key] = caller_locals[key]
            
                  def output(self):
                      output = self.string
                      for k,v in self.context.items():
                          output = output.replace(k,str(v))
                      return output
          • 这看起来像是一场性能噩梦,而且很可能永远无法集成集成开发环境。所以我想我错了。它可以是一个库。但官方支持会更好。

        • 你不需要查看所有的全局,只需要获取调用者的命名空间,这非常简单。参见 https://stackoverflow.com/a/6618825/27426

          因此,我认为这并不是一个绝对的语言特性,而不是一个库。用纯 Python 写的模板类也可以在它的 __init__ 中进行同样的查找。

    • 这不是 “更多功能”==“更接近 C++”这么简单。就对语言复杂性的影响而言,特性之间并不等同。

      t-strings 不会与语言中的任何其他功能交互;正如你自己指出的,它们几乎可以成为一个孤立的库。这使得它们的影响很小。

      语法上也是如此;它们只是另一种字符串,用 “t ”而不是 “f ”表示。这很容易融入现有的语言模型。

      此外,即使从语义上讲,从大多数语言用户的角度来看,它们在各方面都等同于 f 字符串,所以真的没什么好学的。只有库的编写者才需要了解它们。

      然后,我们必须考虑其优点–消除 SQL 和 HTML 注入攻击的潜力。价值/成本如此之高,该功能自然不在话下。

    • 如果这不是一种语言特性,那么总会有碎片化的风险。有些人不会使用它,因为它增加了另一个依赖项,这意味着熟悉它的程序员会更少。其他人则会提出自己的、略微不兼容的实现方法。例如 Perl 及其各种面向对象框架(Moose、Mouse、Moo、Mo、M、Mojolicious 自带……)。

      其他语言的政策是在核心语言之外建立原型,只有在获得认可后才将其添加到核心语言中。当然,如果该语言有一种机制,可以将语法扩展到核心语言之外,那么这种做法会更有效。

    • 反方观点:为编程语言添加精心设计的实用功能是件好事。

    • 我们现在有很多创建字符串的方法。

      与此同时,pytest 仍然不是标准库的一部分。

  10. 我真正不明白的是,这与在 f-string 变量上应用模板函数有什么区别。所以

       evil = "<script>alert('bad')</script>"
       template = t"{evil}"
       safe = html(template)
    

    Why not just:

        evil = "<script>alert('bad')</script>"
        safe = f"{html(evil)}"
    

    甚至是在创建 f 字符串之前。这是否只是为了不忘记消毒/字符串操作部分,并迫使你经历这一切?

    • > 是否只是为了不忘记消毒/字符串操作部分,而强迫你去做这些工作?

      这是一件大事!这也是为了集中这项工作。现在,消毒工作可以在 t 字符串的消费者(例如,HTML 渲染器的 API)中进行,而不是在每个 f 字符串中进行。

    • 差不多吧。这篇文章强调了人们直接使用 f-string 的情况,他们希望为轻量级模板/插值提供一种替代方案。

      • 我觉得我还是漏掉了什么,因为他们是这么说这个例子的:

        “这两个例子都不可能用 f-strings 实现。通过提供截取和转换插值的机制,模板字符串可以实现广泛的字符串处理用例”。

        在我看来,模板字符串的任何操作都可以在构建 f-string 之前完成,或者像我最初的示例那样内联完成。

        • 使用 f-string 时,您无法编写代码来确定生成的 `str` 中哪些部分是静态的,哪些是动态的;而使用 t-string 时,您可以。*

          (至于您最初的例子:值得考虑的是,当您通过嵌套方式将多个 HTML 位组成一个大型最终页面时,会发生什么情况。开发人员的体验可能会变得……不理想)。

          * 除非使用检查等不可取的黑客手段)。

        • 不过,你并不能真正做到你的例子。如果你使用的是 f 字符串,你就会直接插值,因为这样很方便。你不会使用额外的库来保证安全,否则你就会使用合适的模板库和语言。

          这就提供了一个方便的中间地带,既不需要学习模板库,又能保证安全。我现在想不起代码了,但我觉得这在向 Django 传递一些动态 HTML 代码时很有用,而无需记住关闭该部分的转义。在编写原始 SQL 时,它也很方便,无需使用准备好的字符串。

    • 这就是确切的用例。基本上,这些都是非常相似的函数签名的语法糖。

  11. 嗨!这是我写的。)

    我参与讨论有点晚了(看到 HN 上出现这个话题也有点惊讶),但我很乐意回答任何问题;我会尽量在一天内出现在大家面前。

    • 你好,我来自 JavaScript 背景。

      我想知道不使用与 JavaScript 类似的语法的原因是什么?在我看来更简单。

        # Compare this:
        template = t"<p>{evil}</p>"
        safe = html(template)
        # To this:
        safe = html"<p>{evil}</p>"
      • PEP 最初使用的是类似于 JavaScript 的语法,但随着时间的推移,我们认为这不是在 Python 中揭示这些想法的正确方法。在 PEP 中有更多关于这种方法被拒绝的细节:https://peps.python.org/pep-0750/#arbitrary-string-literal-p…

      • PEP 292 的`string.Template`将继续保留;没有计划废弃它。

        PEP 750 的 `string.templatelib.Template` 是一个独立的、不相关的类型。与 PEP 292 不同的是,`Template` 也有字面形式。

        我希望这种混淆不会太大;在实践中,PEP 292(又名 $-strings)仅在特殊情况下使用,例如 flufl.i18n,一个真正深入的 I18N 框架。

  12. 登陆 3.14?很好,不过这可能一两年内都无法进入我雇主的代码库了。听起来它也能为我们解决一些问题。

    分页 asottile – 有没有计划做一个`future-tstrings`?)

    在我们使用 Python 3.x 之前,`future-fstrings`(https://pypi.org/project/future-fstrings/) 是我们团队在 2019 年前后一两年内的一个真正的 QOL 改进。

    • 一些现有技术:https://pypi.org/project/tagged/

      事实上,在作者的一个同伴项目的 repo 中,有一个 ticket 引发了 t-strings 的工作:https://github.com/jviide/htm.py/issues/11

    • 我曾向 asottile 询问过他对 PEP 750 的看法(或者说,他对之前版本的看法),他并不赞成。所以我认为我们不太可能从他那里看到未来字符串。)

  13. 这些如何与 i18n 交互?我可以从 .po 文件中用 `_(t“”)`加载翻译过的 t 字符串吗?能否在 lambdas 中包含变量名和任意代码?

    • 我建议你阅读 PEP,其中有一节是关于 i18n 的。简而言之,它不是为这种使用情况而设计的。

      至于变量和任意代码/lambdas,是的:t-strings 可以做到这一点,就像 f-strings 一样。

  14. > t-strings 评估到一个新类型,`string.templatelib.Template`。

    > 为了支持处理,“模板 ”为开发人员提供了访问字符串及其插值的权限,然后再*将它们组合成最终字符串。

    在任何用例中,对模板的处理除了(i)处理每个值,然后(ii)按原始顺序重新组合结果和字符串部分以生成一个新字符串之外,还有其他事情吗?换句话说,“process_template ”函数是否会与此有本质区别(基于文章中的 “pig_latin”)?

        def process_template(template: Template) -> str:
            result = []
            for item in template:
                if isinstance(item, str):
                    result.append(item)
                else:
                    result.append(process_value(item.value))
            return "".join(result)
    

    我还没有看到任何函数会有所不同的例子。但如果没有的话,那就奇怪了,设计要求每个模板处理函数都包含这个模板,而不是制作一个接受 `process_value` 函数的 `Template.process` 方法。

    • 模板甚至不必处理成字符串。文章展示了一个将模板处理为 HTML 迷你 DOM 的示例。这可能并不明显,因为 DOM 对象被立即字符串化以显示示例输出,但您可以想象一下,在字符串化 DOM 对象之前,您还可以对其进行几步操作,或者您可以在浏览器中运行 WASM,并直接使用该迷你 DOM 作为虚拟 DOM 传递给 JS 进行处理。

      此外,除了其他 SQL 示例中使用“? ”以 SQL 友好的方式填入参数 “洞 ”之外,一些数据库也支持命名参数,因此字符串形式中的 “洞 ”可以天真地替换为类似 `f“@{item.expression}”` 这样的内容,这样也可以在 dict 中形成关键字,作为参数传递。(您需要确保模板内的表达式可以用作参数名,而不是像 {1 + 3} 或 {thing for thing in some_list}(在这种情况下,您可能会自动指定其他参数名)这样的奇特名称)。

      • 你刚才描述的几乎所有内容都在研究之中。您的描述如此准确,令人惊叹。我们希望在美国 PyCon 上进行演示和解释。

    • 评论中有很多关于 SQL 的例子。在 SQL 的情况下,你需要类似以下的东西:

       def process_template(template: Template) -> tuple[str, tuple]:
          sql_parts = []
          args = []
          for item in template:
            if isinstance(item, str):
              sql_parts.append(item)
            else:
              sql_parts.append(“?”)
              args.append(process_value(item.value))
          return “”.join(sql_parts), tuple(args)
      

      (当然会有更细微的差别,但我希望你能明白我的意思)

      • 是的,有道理,谢谢。

        另外,我的评论是关于所需的模板数量,但如果将 `process_template` 写成功能性更强的样式,而不是文章中使用的高度易变(类似戈兰?第一个 `process_template` 例子就是这样:

         def process_template(template: Template) -> str:
                return ''.join(interleave_longest(template.strings, map(process_value, template.values)))
        

        第二种方法类似于

         def process_template(template: 模板) -> tuple[str, tuple]:
                return (
                    ''.join(interleave_longest(template.strings,['?'] * len(template.values)))、
                    map(process_value, template.values)
                )
    • >但如果没有,那就奇怪了,设计要求每个模板处理功能都包含这个模板。

      其他回复给出了其他用例。但 Python 的妙处在于,在常见的情况下,你并不需要 “包含这个模板”。它可以封装在一个装饰器中(可以包含在 `templatelib` 中)。或者,如你所说,在模板类的一个方法中。

      我想我会将其作为生成器来实现,在 Interpolations 上调用 `process_value`(默认为 `str`),这样调用者仍然可以对结果进行更多处理(或者只是 `”’.join`)。

      但这些都是单独的考虑因素;没有什么能阻止以后实现它们,或者在 3.14 版本发布之前将它们添加到实现中。

  15. 我希望他们能添加与 JS 相同的功能,即 “字符串字面前缀 ”可以由用户自定义。

    html`<p>${value}</p>` 将实际运行函数 html(template)。这意味着您可以用它来 “标记 ”一个函数,以便静态分析可以检测到。例如,许多编辑器会语法高亮显示并检查以这种方式标记的任何 HTML,SQL、GraphQL 和其他一些编辑器可能也是如此。

    • PEP 最初是这样提出的。但由于种种原因,将其作为一个开放的命名空间被认为会使语言过于复杂(解读:阅读时理解咖啡)。另外,使用 t 字符串似乎并不会损失多少能力。库可以要求将模板作为其接受的类型,而不必发明自己的自定义类型和命名模板。

    • 顺便说一下,JS 的解调与 Python 的解调完全相同,因此在 Python 中进行语法高亮与在 JS 中进行语法高亮一样安全。

      • 它的除法类似,但 Python 版本没有名称。任何 t 字符串都是 t 字符串,没有 HTML t 字符串或 SQL t 字符串之类的东西。它只是一个可以传递给函数的 t 字符串:

         html_string = t"<something />”
            sql_string = t "SELECT * FROM something”
        

        在 JS 中,字符串的前缀可能因语言而异,例如

         const htmlString = html`<something />`
            const sqlString = sql`SELECT * FROM something`.
        

        等等。看到区别了吗?

        • 除了你的标签是错误的,因为 `html_string` 和 `sql_string` 都不是字符串,它们都是模板对象,而 sink 函数是处理它的函数。在代码段结束时,除了创建模板对象本身之外,没有对它们进行任何处理。

          • 当然,可以选择不同的变量名,谁在乎呢。本质区别在于,语言是在声明处而不是在使用处被引用的,这使得语法高亮更容易。

            请你理解我的观点,而不是批评一些琐碎的东西。

            • > 请理解我的观点,而不是批评琐碎的东西。

              你完全误解了正在发生的事情,这不是琐事。

              > 本质区别在于,语言是在声明站点而非使用站点被引用的,这使得语法高亮显示变得容易得多。

              除了 `String.raw` 之外,Javascript 没有内置模板标记。如果工具有能力从任意第三方库中推断出嵌入式语言,我希望它们有能力进行完全琐碎的流程分析,并意识到

               html(t“<something />”)
              

              意味着模板字符串很可能是 HTML 内容。

              • 是的,这是一个弱启发式,但它与 JS 应用的弱启发式完全相同!

                换句话说,由于 JS 中的自定义模板标记*实际上只是函数调用*,所以当 JS 环境语法将代码高亮显示为 HTML 时,它是基于一个极弱的启发式(插值函数的标识符被命名为 “html”)。Python 和 JS 都有同样的问题。

              • 我认为,如果我们将重点放在静态键入用法上,他的观点会更清晰。考虑一下 `html(this_is_a_sql_template)` 与 `html “SELECT * FROM …”` 或 `thing_that_consumes_html_template_type(oops_a_sql_template_type)` 的对比。

              • 拜托,你这是无理取闹。变量名选得不好并不能说明 “完全误解”。是的,变量应该命名为 `html_template`,而不是 `html_string` – 我得承认多少次你才能接受?

                而且,当声明站点和使用站点可能被变量赋值等分割开来时,语法高亮显然会更加复杂。是的,在你展示的例子中,语法高亮很容易,但如果 `html` 函数需要更多参数,不把模板作为第一个参数,等等,那该怎么办呢?有很多可能的复杂性都是带标记的模板文字所不具备的。因此,它们更容易进行高亮处理。这是客观事实。

                • JS 中的标记模板字面量具有所有这些复杂性。所有这些。你所信任的工具撒谎并假装事实并非如此,但这并不能说明语言规范有什么不同。

                  • 不,它们确实不支持,因为它们不支持这些特性!

                    你不能把模板字面声明和 “标记 ”割裂开来。标签始终是声明的一部分,但在 Python 中它不一定是,正如我所展示的。

                    您不能给标记函数传递额外的参数,它始终只是模板和值。在 Python 中,您可以向使用站点传递任意多的参数,例如

                     some_value = html(True, t“<something />”, 42)[/ 预]
                    • 抱歉,你就是不明白我在说什么吗?你的例子并没有展示你所说的功能。
                      在 Python 中,你可以这样做

                       bar = "hello world”
                          template = t"<something foo={bar} />”
                          string = html(template)
                      

                      这在 JS 中是根本不可能的,因为模板字面始终必须附加标签。你无法将它们分开。如果您尝试

                       const bar = "hello world”
                          const template = `<something foo=${bar} />`
                      

                      在 `template` 变量中已经有了一个字符串。现在已经无法访问各个值了。已经完成了。这是一个字符串。没有模板。你不能把字面声明和标记分开。

                      我们现在可以结束这场故意误解的荒唐游戏了吗?

                    • 抱歉,由于某些原因,我无法回复更深层次的帖子。我猜是深度限制吧。你问的是一旦标签和需要语法高亮显示的内容分开,哪些编辑器可以支持语法高亮显示。目前还没有,不过我正在写一个可以支持的编辑器。

                      Python 应该能够像 JS 一样检测神奇的语法模式,并将其用于语法高亮。在 JS 中,触发 HTML 语法高亮的神奇语法模式是

                       html`<doc/>`。
                      

                      在 Python 中,神奇的语法模式是

                       html(t“<doc />”)
                      

                      我举这个 JS 反例的目的是想说明,人们不做这种事情的真正原因并不是他们做不到,而是他们喜欢语法高亮。这意味着这种方法在 Python 中应该同样有效。尽管所涉及的启发式非常弱,但情况就是这样。在任何一种语言中,更改标识符的名称或分解为多个表达式都足以破坏启发式,这就是为什么我认为这是一个非常弱的启发式,而且对开发者来说是个危险的陷阱,因为他们可能会误以为着色是运行时如何看待该数据的信息(这通常是语法高亮的目的)。

                    • 请允许我给你一些建议:你本可以不告诉我我不了解 JS,不歪曲我的意思,也不浪费我们双方的时间,直接从一开始就提出这个观点。因为你在技术上是正确的,在模板立即传递给函数的有限情况下,语法高亮可以使用目前在 JS 中使用的相同启发式方法。但是,正如我之前假设的那样,你这样做根本就是在无视我的观点:标记模板字面的语法高亮并不适用于仅有的一部分用途,因为它们在设计上并不那么复杂。你的反例是使用了不同的模板标记,而没有将实际标记用作模板标记,这显然不符合模板标记语法高亮的要求。

                      如果你不在这个技术问题上耍花招,我们都可以节省很多时间。希望你玩得开心。

                    • 我和你的看法不一样。这就是你说的根本不可能:

                       let passthrough = (...args) => args;
                        let bar = “hello world”;
                        let template = passthrough`<something foo=${bar} />`;
                        string = html(...template);
                    • 因此,现在我们已经从 “带标记的模板字面量具有 Python 模板的所有复杂性 ”转变为 “如果定义一个特定的模板标记并使用它,就可以复制 Python 模板的一个特性”。没有提到我提到的其他例子(顺便说一下,这并不是一个详尽的列表)。

                      现在,我将忽略你仍在故意歪曲我的意思,希望能从这次讨论中获得一些实际知识–请告诉我:哪个编辑器支持你刚才提到的特定例子的语法高亮?毕竟,这才是讨论的主题–不是是否可以延迟 “标记”,而是为 JS 模板标记做语法高亮是否更容易。请向我展示一个现有的编辑器、集成开发环境或 Git repo 的工具,它可以根据第 4 行中的 `html(…template)` 调用(因此没有基于第 3 行的启发式)来高亮您的特定示例。你肯定不是在胡乱扔例子,看什么能粘住,对吗?你真的一直在关注讨论,并在讨论的背景下进行争论?

                    • 哦,现在我可以回复了。继续上面的主题:https://news.ycombinator.com/item?id=43754585

            • JS 在使用网站上引用语言,与 Python 完全一样。两种语言的行为方式在这里没有区别。

              • 不,它在声明站点引用语言,因为声明站点始终是使用站点。你不能把它们分开。在 Python 中,您可以将它们拆分开来 – 参见我第一条评论中的示例。

      • 在 JS 中,模板标签接收一个由 n+1 个字符串组成的数组参数,然后接收 n 个插值参数,并在这两个序列上交替迭代。

        在 Python 中,您可以通过访问 “字符串 ”和 “值 ”来做到这一点,但我希望大多数情况下都能简单地遍历模板,从而获得字面字符串和内插值的统一类型视图。

  16. 撇开有用性的争论不谈,是否有针对 f 字符串的警告规则?我可以很容易地看出,如果把一个字符串误认为另一个字符串,就会造成问题。为了便于理解,我特别考虑了 Black 和 MyPy 这样的工具。

    • 要实现这一点并不难(这只是对 t-strings 返回的类型进行检查),所以我相信如果还没有实现的话,一定会实现的。

    • 模板字符串的运行时和静态类型与 fstring 不同(Template vs str)。除非你把它们传递到接受字符串的地方,否则不会出错。

      相比 JS 中的标记字面量,我其实更喜欢这种简单的设计。

  17. TL;DR:与 f-string 一样,t-string 中的所有 {foo} 表达式都会立即进行评估,但 t-string评估不会立即将所有表达式连接成一个结果字符串,而是返回一个模板对象,将插值结果和周围的字符串分开。这样,在将插值结果与周围的字符串连接之前,后续逻辑就可以决定插值结果是否需要任何特殊的转义。

    换句话说,t-字符串基本上就是 f-字符串,其最终连接会被延迟。事实上,你可以通过执行一个简单的非转义连接步骤,用 t-strings 来实现 f-strings :https://peps.python.org/pep-0750/#example-implementing-f-str…

     f'...' -> str
        t'...' -> 模板
        foo(t: Template) -> str[/预]
    • > 这样,在将插值结果与周围的字符串连接之前,后续逻辑就可以决定是否需要对插值结果进行特殊转义处理。
      这听起来像是在本应简单的语言中做了不必要的改动。我担心 Python 会变成 C++42,用 65535 种方法来做一件简单的事。
      为什么不干脆

       f'SELECT * FROM `{esc(table)}` WHERE name = “{esc(name)}”'
      

      简单明了。

      • 很容易忘记 `esc` 功能。收件人如何检查(或类型检查)它是否在所有正确的地方被调用?

        大多数数据库支持参数化查询,可以缓存以提高性能。如何从中挑选出参数,并用数据库的参数占位符替换字符串的这些部分?

        预] t’Select * from {table} where name = {name}’ t’Select * from {table} where name = {name}’

        看起来非常相似,但执行引擎可以访问所有单独的部分,因此可以非常容易地添加占位符,例如

         ('Select * from ? where name = ?`, table, name)
        

        甚至(如果数据库支持)可以访问字符串内的表达式,并使用命名参数:

         ('Select * from @table where name = @name', { “table”: table, “name”: name })
        

        这对调试非常有用,这取决于你的数据库引擎。

        在每个支持参数化 SQL 的 DB 引擎中,参数化 SQL 甚至比转义句法更安全,因为参数是在二进制协议中完全不同的部分传递的,不需要仅仅依靠字符串操作来添加转义序列。

      • 虽然您的代码是实现 @haberman 描述的有效替代方法,但该功能实际上要灵活得多。

        后续逻辑 “可以完全访问插值结果和字符串。它不仅可以转义结果,还可以对结果做任何它想做的事情。它还可以对字符串做任何它想做的事情,然后以任何它喜欢的方式组合所有内容–甚至不需要最终结果是一个字符串。

        • 另一个 PEP 示例显示了从传入的字典中生成 HTML 属性的过程。如果你有原始数据,HTML 有很多地方都可以用到这一点。

  18. 把它变成通用的 `t`,就会失去显式语法高亮。而像 JS template`string` 这样的东西可以根据模板值来决定使用哪种语法。

    我认为当把它赋值给一个变量时: SyntaxRecognizableTemplate 变量时,可以给它必要的提示。

    PEP 中讨论过这个问题吗?

    *edit: reading the PEP-750[1] it doesn’t seem like it.

    [1] https://peps.python.org/pep-0750/#the-interpolation-type

    • 我们将这些问题从 PEP 中删除,以限制其范围。(见 https://peps.python.org/pep-0750/#mechanism-to-describe-the-…)

      没错,PEP 留下了一个重要问题:工具将如何决定如何处理 t 字符串中的常见内容类型,如 HTML 或 SQL?

      短期内可以采取一些简单的方法(内容嗅探),而更强大的方法(也许是类型注释)则需要时间和更广泛的工具社区来开发。

    • 在 PEP 的第一次修订和讨论中已经讨论过这个问题。随着我们对工具需求的进一步了解,我们决定将其移至后续工作中。

      举例来说,我曾对在函数上使用 “注释”(`Annotated`)来表示它所期望的语言感到兴奋。结果发现,很多衬砌对类型系统一无所知。

  19. 如果 Django 的模板引擎开始在内部使用 t 字符串,性能会有提升吗?

      • 不,我的意思是,如果 Django 的模板引擎在内部转换了实现方式,使模板(作为一种功能)能够使用这些新的 t-strings。如果 t 字符串在 Python 中得到了更高的优化,这可能意味着性能的提升。

  20. 我曾使用元编程实现过类似的功能,用于安全地执行 shell 命令:

    name=”A$ron”

    z(“echo Hello {name}”)

    请注意,这不是一个 f 字符串。z 函数通过解析该字符串并访问调用者的局部变量来扩展变量。

    https://github.com/NightMachinery/brish

  21. 希望 Django 能完全使用这些语法来替代大量复杂的模板语法。

  22. 不确定是否要引入另一个字符串前缀。在 f-strings、原始字符串和 i18n 东西之间,它已经变得越来越拥挤了。我很好奇这在大型代码库中的可读性如何。

    • 我的意见恰恰相反。让前缀自由发挥吧!

       从 sql 导入 sql
          query = sql "SELECT user_id, user_name FROM {user_table_versioned} WHERE user_name = {user_name}"

      888/2/masklinnn

      • 已考虑并拒绝:https://peps.python.org/pep-0750/#arbitrary-string-literal-p…

      • 这与对这些新 t 字符串之一进行操作的函数 sql() 有什么不同?
        从 sql(t“…”)改成 sql(t“…”)的语法糖似乎并不特别有价值。t 字符串的新颖之处在于它们在编译时改变了解析。

        • > 从 sql(t“…”)改成 sql(t“…”)的语法糖似乎并不特别有价值。
          它之所以有价值,是因为
          – 集成开发环境可以语法高亮显示 SQL 字符串中的 SQL 和 HTML 字符串中的 HTML
          – 不会意外地将 HTML 字符串传递给 SQL 库

        • 它不同于函数,就像 f“” 不同于 f(“”) 和 t“” 不同于 t(“”) 一样。
          你可以创建一个 Python 函数来解析字符串,查找 {},然后在全局中搜索这些变量。你还可以将其扩展到执行代码和格式化。
          对我来说,f-strings 的真正魅力在于编辑器知道它是一个模板,而不仅仅是一个字符串。将其扩展到在我的 Python 代码中使用 SQL 和 regex 语法高亮、过滤和代码格式化是一个很酷的前景。

        • 不会有什么不同,但会更方便,因为我们不必再计算 %s 的数量,你可以将变量放在内联中。

            • 是的,我就是这个意思。GP 已经在讨论 t 字符串了。

              • dmurray 将假设的 sql“…” 与 sql(t“…”)进行了比较。两种方式都没有 %。

      • JavaScript 就是这样使用带标记的模板文字的。
        您的 sql 将只是一个接收字符串/值数组并返回任何值的函数。

      • 这就是 JavaScript 使用标记模板字面量的方法。https://github.com/dsego/sql_tag

      • 他们说的可能是使用 _ 作为 `translate` 的别名的惯例。

    • “我主要担心的不是 “又一个
      前缀的概念本身感觉有点偏离接近人类语言的可读代码–这正是 Python 的精神所在

      • 此外,它被称为 t-string,但实际上是一个模板对象的构造函数,根本不是字符串,这可能会引起混淆。我宁愿看到一个新的特殊术语 `template` 而不是这个。

      • 单字母 “f ”或 “t ”确实让人读起来不自然,但如果是 “sql”… “或 ”html“…”,我想就会好一些。

      • 应该是一个关键词。

         a = 模板 "foo {bar}”
        

        与 raw 和 format 一样。

  23. 这个 Python 文档示例如何处理 t 字符串?

     cur.executemany(“INSERT INTO movie VALUES(?, ?, ?)”, data)
    

    SQLite3 能否像现在这样缓存查询?

    • 在我看来,将模板的参数解析回……占位符似乎很简单?

      • 不,它甚至会在模板后处理开始之前就在上游失败。

        模板对象本身无法形成,因为每个名称必须是可见变量名。

        这与使用包含未定义变量名的 f-string 时出现的错误相同。

        • 我对你的评论很感兴趣,但也很困惑。我认为可以这样做

          cur.executemany(t “INSERT INTO movie VALUES({data})”)
          

          这样,模板对象就会有一个列表参数,它可以将该参数转化为正确数量的“?”,连同数据一起传递给数据库驱动程序。

          我漏掉了什么?

  24. 那么 f 字符串现在应该消失了吗?它们只是 t 字符串的一种特殊情况。

    另外,别让我开始讨论 g 字符串。

    • > 那么 f 字符串现在应该消失了吗?

      不应该

      > 它们只是 t 字符串的一种特例。

      其实不然,因为它们会立即产生一个字符串,而不是一个模板。

    • F 字符串仍应是默认设置。T 字符串不能直接使用,也没有类似于 `str` 的 API,因此它们甚至不能兼容。

  25. 很好,所以它是 python 中的一种 “有限 DSL”,很容易扩展

  26. 目前看来很不错,但我不明白不允许调用 str(template) 并以普通字符串形式获取模板的动机何在。我可以想象,如果能把模板本身收集到一个字符串中,用它来做一些字符串的事情,那将是非常有用的。

    我唯一能想到的原因是,如果你想保护开发者免受自己的伤害,这有点违背了 “我们都是成年人 ”的思想,而正是这种思想使 Python 如此伟大。我想添加这个功能很容易,但拜托。

    • 在大型企业中,处理类似问题的传统方法是 unsafe_str_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(模板)

  27. 你可以说我是君主主义者,但我认为自从 Guido van Rossum 下台后,Python 就变得更糟了。

    • 如果 Guido 仍然是 BDFL,我不认为事情会有什么不同。他是 t-strings PEP 的第二作者,也是最近发布的大多数其他主要 PEP 的作者(包括 walrus 运算符、PEG 解析器和模式匹配)。

    • 看到这么多人在面对编程语言的一个简单而有用的功能时,却大肆宣扬这将是该语言的世界末日,真是令人着迷。

      老实说,我觉得很多人只是觉得无聊,想找点事来发泄一下。

  28. > 如果你使用过 JavaScript,可能会对 t-strings 感到熟悉。它们是与 JavaScript 标记模板平行的 pythonic 字符串。

    其语法是模板字面量,而不仅仅是 “标记模板”。这是一个巨大的区别:模板字面量仍然是真正的字符串。它们不需要标记前缀就能工作,你可以在需要时选择标记它们。

    据我所知,t-strings 无法做到这一点。它们不是字符串,甚至不能强制将其转换为字符串,必须经过处理器处理后才能成为字符串。因此,它们与 JS 的模板字面意义完全不同,它们只是用于形成 “需要传递到返回字符串的函数中的对象实例 ”的语法糖。

    因此,我不希望人们喜欢使用 f-strings 而不是 t-strings,即使他们真的不应该这样做,仅仅是因为 “必须不断地将它们从非字符串转换为字符串很麻烦”。如果它们能像 JS 模板字面量那样工作就好了……那就太棒了。

  29. 老实说,我认为这个功能比添加的海象运算符更有用,也是更优雅的解决方案。查询字符串的格式一直让人感觉很混乱,尤其是不同的数据库都有自己的非标准方法。

    • .format是一种安全、可注入、可堆肥的方法,生成的对象保留了插值的原始值。这使得它在使用准备参数构建 SQL 查询时非常有用。

    • 在元层面上:它不是格式化字符串,而是返回一个包含格式化字符串及其参数的对象。这样,库作者就可以实现他们想要的任何格式化函数,例如可以转义插值字符串的函数。

    • f-strings 是 .format 的语法糖,例如

       f "foo is {foo} and {bar=}”
          “foo是{},bar={}”.format(foo, bar)
      

      是等价的。

      t-strings 实际上不是字符串,而是模板对象,可以访问模板字符串和处理参数。从这个意义上讲,兄弟姐妹注释将其描述为自定义 .format 实现–它是类似于 f-string 的糖,你也可以控制它作为糖的 .format 函数。

      • f-string 实际上是在字节码层面上转化为格式化和片段连接的;它们不会在引擎盖下调用 `.format`,因此不是我所说的传统意义上的 “语法糖”。不过,等价关系是成立的。

        当然,t-strings(将)在字节码层面上转化为模板对象的实例化(以及就地计算插值所需的任何代码)。

    • 这是自定义 .format 实现。(访问占位符和值并生成字符串)

  30. 如果这只是用于 sql 查询……那就太矫枉过正了,尤其是当你需要比较通常的 PREPARE 语句时,让每个人都使用 3.14 及以上版本就更麻烦了。

    • 这也是为了记录日志:

       log.debug(f “The value of counter was {counter}, the nonce was {nonce}”)
      

      每次解释器运行到这一行时,都会生成一个新字符串。而

       log.debug(t “The value of counter was {counter}, the nonce was {nonce}”)
      

      向 debug() 函数传递一个模板,如果未开启调试模式且未生成字符串,该模板会退出。

    • 它适用于 SQL、HTML 和 shell。但我不知道,在 OWASP 列表中一直名列前茅的解决注入问题怎么会被认为是 “矫枉过正”。

  31. 当然,这可以避免 SQL 注入问题。但是,我很难想象有哪个开发人员会在目前使用 f-strings 时犯这样的基本错误,而且在新版发布时又改用这个选项。

    这似乎是一种自我选择,在某种程度上使其失去了意义。

    • > 然而,我很难想象有哪个开发人员既会在目前使用 f-strings 时犯下如此根本性的错误,又会在新版本发布时改用该选项。

      t-strings 是一种不同的类型(包括静态和动态),而 f-strings 不是。因此,可以在 API 层面强制使用 t-strings,迫使开发人员 “正确 ”使用。

      也就是说,您需要第三方工具来区分

      some_call(“安全字面字符串”)
      

       some_call(“unsafe dynamically created string”)
      

      当涉及到 t 字符串时,情况就不是这样了,`some_call` 可以在内部类型检查它是否得到了一个 t 字符串,而完全拒绝普通字符串。

      尽管可能需要留出一些空间,以便在项目或其数量发生运行时变化的情况下将 t 字符串组合在一起。比如说分面。我不知道这是否是 t 字符串的原生功能。

      • 但这需要你当前使用的任何 SQL 库做出重大改变,不再允许使用字符串。

        • 是的。也许值得这么做。您也可以使用 monkeypatch 方法来 “选择加入 ”这一变更。

        • sqlalchemy 并不真正接受字符串–如果你接受字符串,你需要将它们传递到 “conn.execute(text(…)) ”中,因此最终用户应该不会面临破坏性的改变。

        • 是的,随着时间的推移,这正是将要发生的事情。

    • 最终,你将无法在常用的数据库库中传递字符串。

  32. 我觉得可以用另一种方法来解决这个问题。S=f “my good code #### {potentially_evil_user_input} #### my good code again”,然后绕过 ####。当然,更好的办法是 S=evil_user_input,然后先对 S 进行擦除。

      • 正如他们在 PEP 750 中提到的,“我们希望更多的开发人员使用 t 字符串,而不是编写 t 字符串处理函数”。

  33. 我真的很愿意接受 Python 的新特性,但这越来越荒谬了。让语言变得臃肿是一件毫无意义的事情。在这一点上,我转用 clojure 作为第一行语言的步伐正在加快。

    这属于 “即使是为了特定的超小众事物,我也不想使用的东西”。t string “到底代表什么?因为它显然不是任何类型的字符串,而是一种奇怪的函数调用符号。程序员看到的东西看起来像是字符串格式,但程序执行的却是某个任意的过程,而这个过程返回的可能根本不是字符串。

    • 对我来说,这是 Python 6 年来最好的功能。鉴于 SQL 注入长期以来一直是第一大漏洞,JS 拥有这项功能可以让我的代码完全避免 SQL 注入,这绝对是一项令人难以置信的功能。

      • 感谢尼克的回复和你所花费的时间解释。有趣的是,我回顾了 f-strings 落地前的评论。他们也有类似的关于臃肿的抱怨。然而,我的猜测是:非常受欢迎。

      • 但这并不能防止 SQL 注入,不是吗?它增加了一个语法功能,然后你就可以用它来按照一个新颖的成语构建 SQL 注入防御。仍然需要有人编写实际的 SQL 构建逻辑!

        我不认为这是做这件事的正确习语。坦率地说,我认为 SQL 查询生成完全不应该像字符串模板一样!

        这样做的目的似乎是 “现在你可以编写看起来像 SQL 注入漏洞的代码,但实际上却没有漏洞!”。我宁愿写的代码不是漏洞,看起来也不像漏洞,而且也不必弯曲语言语法。

      • 什么?也许在 2005 年。严格来说,这比参数化更糟糕,因为现在你还得浪费时间 “转义 ”字符串,而这将使数据与查询混合的问题永久化,甚至再也没人想要这样了。

        这就好比即使是确定的一个案例,也没有人想过。现在你的 SQL 库只接受 t 字符串,而我在传递一个简单的静态查询时却出现了一个不起眼的错误。对了,把无用的 t 放上去。这样一来,SQL 库就可以转义所有之前没有转义的参数,然后将最终的唯一查询交给实际的底层 SQL 库,而后者更希望得到参数化的查询,这样它就可以缓存解析了。天哪

    • 臃肿 “是指你现在可以在字符串前面加上字母 ”t“,而不是 ”f “或 ”r”?

      • 我不是原作者,但我猜原作者转用 Clojure 后,臃肿基本上就是任何特殊语法或特殊语言功能,基本上都可以归结为函数调用或 let 语句。很多函数式编程语言都有极简的表达式语法,它们的表现力与 OP 所需要的一样。

      • 这是一种模式,而不是具体的功能。对我来说,库(即使是 stdlib)级别的东西与第一类特性(即需要解析器级别支持的东西)之间有一个重要的定性区别。

        Python 在这方面历来都很保守,但近年来却有了一个又一个有争议的语言扩展,而语言中真正需要关爱的部分却被冷落了。

        我想说得很清楚,这是我改变了主意 — 例如,我曾经(现在仍然)非常赞同极具争议的赋值表达式(“海象操作符”)。

        如果按照目前的变化速度推算 10、15、20 年后的 Python 语言,我对它的未来并没有什么信心。我认为这种新事物是一种活跃的反特性,这对我来说确实没有什么帮助。

        • 我觉得这是在乞求问题… 只有像你这样批评 T 形字符串有争议的评论才是有争议的…

  34. 我喜欢 f 字符串,我想有些人需要它。

    我喜欢 Python,但在经历了 2->3 之后(偶尔还在经历!),每当我看到一种新的语言特性时,我的第一个想法就是 “谢天谢地,它没有破坏之前的一切”。

    • 是的,但已经过去 17 年了,也许是时候把创伤后应激障碍抛在脑后了。我们这一代的程序员在那件事发生时甚至还没开始编程。

      • > 我们几乎到了这一代程序员甚至还没开始编程的时候了

        我从 2006 年就开始用 Python 编程了,我想当时大多数系统都是基于 2.4 的。我是那些很晚才转向 Python 3 的人之一,一直在等待一些主要库发布 Python 3 软件包–celery 和 Twisted 就是其中最 “顽固 ”的两个–所以我记得我的第一个项目的所有依赖库都为 Python 3 做好准备是在 2015 年左右。

        这就是说:即使是那些在迁移问题上持保守态度的资深开发人员,他们使用 Python 3 的时间也比使用 Python 2 的时间长。现在已经没有理由再谈论 Python 2 了。

        • 我最后一次接触大型 Py2 项目是在 2018 年,当时我把它移植到了 Py3。所以,我有 18 年的 Py2,可能有 6 年的重叠,还有 7 年的纯 Py3。这意味着我的 Py2 时间仍然比 Py3 时间多得多。

          不过,我已经过了过渡期。现在它已经过时了,我同意我们可以不用再为它发愁了。

      • Python2 代码并没有因为 Python3 的出现而消失。在我的工作中,我们偶尔还是要帮助人们迁移为 Python2 编写的代码。

        • 这也是我的经验,唉。

          恐怕我们还没有完全摆脱 Python2 的创伤后压力。

          错误的决定会带来长期的影响。

      • 我们这一代的程序员在那件事发生的时候甚至还没有_活着_。

        • 是的,Python 3.0 发布于 17 年前。但从 Python 2.x 开始的过渡是在 5 年前 2.7 生命周期结束时才完成的。

          • “仍受支持 “是一个奇怪的衡量标准。我的意思是,ActiveState 仍然提供 Python 2.7 版本的(付费)支持。

            • Ubuntu ESM 被用作 Python 2 通过 16.04 的借口/“生命支持”,直到最近。(再加上一层 “你仍然可以获得 14.04 版的 ESM,我们并不落后 ”的说法:-)。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号