对于小型应用程序和所谓的“一次性”脚本,使用@kaizer.se提到的vars
增强功能和@RedGlyph提到的.format
版本是可以的。
然而,对于具有长期维护寿命和许多维护者的大型应用程序,这种做法可能会导致维护头痛,我认为这就是@S.Lott的回答的出发点。 让我解释一些涉及的问题,因为对于没有开发和维护大型应用程序(或可重用组件)的人来说,这些问题可能并不明显。
在“严肃”的应用程序中,您不会将格式字符串硬编码 - 或者,如果您已经这样做了,它将以_('Hello {name}。'
之类的形式存在,其中_
来自gettext或类似的国际化/本地化框架。 问题在于这样的应用程序(或可重用模块,可能会在此类应用程序中使用),必须支持国际化(也称为i18n)和本地化(也称为L10n):您希望您的应用程序能够在某些国家和文化中发出“Hello Paul”,在其他一些国家和文化中发出“Hola Paul”,在其他一些国家和文化中发出“Ciao Paul”等等。 因此,格式字符串会在运行时自动用另一个字符串替换,具体取决于当前的本地化设置; 它不是硬编码的,而是存在某种形式的数据库中。 出于所有意图和目的,请将该格式字符串始终视为变量,而不是字符串文字。
因此,您拥有的基本上是
formatstring.format(**locals())
你无法轻易地检查格式化将使用何种本地名称。必须打开并浏览L10N数据库,识别将在不同设置中使用的格式字符串,并验证它们。
因此,在实践中,您不知道将使用哪些本地名称--这极大地限制了函数的维护。您不敢重命名或删除任何本地变量,因为它可能会严重破坏具有某些(对您而言)模糊组合的语言、区域设置和偏好的用户体验。
如果您具有出色的集成/回归测试,则在 beta 版本发布之前将捕获错误--但 QA 将向您尖叫,发布将延迟... 而且,老实说,虽然以 单元测试实现 100% 覆盖率是合理的,但在考虑到 L10N 和其他众多原因的所有依赖项的支持版本的组合爆炸时,使用 集成测试实现 100% 覆盖率确实是不合理的。因此,您不会冒险破坏程序,因为“它们将在 QA 中被捕获”(如果您这样做,那么您可能不会在开发大型应用程序或可重用组件的环境中长久工作;-)。
因此,在实践中,您将永远不会删除“name”本地变量,即使用户体验人员已经将该问候语更改为更合适的“欢迎,可怕的霸主!”(和相应的 L10n 版本)。所有这些都是因为您选择了 locals()
...
因此,由于您限制了编辑和维护代码的能力,所以您正在积累杂物--也许那个“name”本地变量只存在于从数据库或类似地方获取它,因此保留它(或其他本地变量)不仅是杂物,而且还会降低性能。表面上 locals()
的便利真的值得吗?-)
但是,请等一下,更糟糕的是!像 lint
一样的程序(例如 pylint)可以为您提供许多有用的服务之一,即警告您未使用的本地变量(希望它也能为未使用的全局变量做到这一点,但对于可重用组件来说,这太难了;-)。这样,您就可以快速、廉价地捕获大多数偶发的拼写错误,例如 if ...: nmae = ...
,而不是通过查看单元测试断言并进行侦探工作来找出它为什么会出现错误(您确实拥有强迫症、无处不在的单元测试,会最终捕获到这个问题,对吧?-)-- lint 将告诉您有一个未使用的本地变量 nmae
,并且您将立即修复它。
但是,如果你的代码中有一个 blah.format(**locals())
或等效的 blah % locals()
... 你就很遗憾了!-) 究竟是不是未使用变量 nmae
,还是它确实被传递给你正在使用的外部函数或方法呢?可怜的 lint 不知道-- 要么它仍然会发出警告(导致“狼来了”的效果,最终让你忽略或禁用这样的警告),要么就永远不会发出警告(具有相同的最终效果:没有警告;-)。
将此与“显式优于隐式”的替代方案进行比较...:
blah.format(name=name)
现在不再需要担心维护、性能和破坏性问题了;太棒了!你可以立即清楚地告诉每个相关人员(包括lint),确切地使用了哪些本地变量,以及用于什么目的。
我可以继续说下去,但我认为这篇文章已经够长的了;-)。
所以,总结一下:“ γνῶθι σεαυτόν!”嗯,我的意思是“了解你的代码的目的和范围”。如果它是一个一次性或者几乎不需要未来维护、不会在更广泛的上下文中重新使用等等的东西,那么请继续使用locals()
进行小而美的便利;如果你知道其他情况,甚至如果你并不完全确定,就要谨慎行事,让事情更加明确——遭受一点小不便,明确你正在做什么,并享受所有的好处。
顺便说一句,这只是Python努力支持“小型、一次性、探索性、可能交互性”的编程的例子之一(通过允许和支持风险便利的方式,扩展远远超出了locals()
——想想import *
、eval
、exec
以及几种其他方法,可以混合命名空间并冒险影响维护),以及“大型、可重用的企业级”应用和组件。它可以在两者方面做得相当好,但只有当你“了解自己”的时候,才能避免使用“方便性”部分,除非你绝对确定你实际上能够负担得起它们。往往关键考虑因素是“这对我的命名空间、编译器、lint &c、人类读者和维护者的形成和使用意味着什么?”。
请记住,“命名空间是一个伟大的想法——让我们做更多的这样的事情!”这就是Python之禅的结论...但是作为一种“成年人语言”,Python让你定义这意味着什么的边界,作为你的开发环境、目标和实践的结果。要负责任地使用这种力量!-)
locals()
传递给外部函数或方法。顺便说一句,Python对Unicode的不断支持(现在是3.*
中默认的文本字符串)正是为了帮助改变“大多数程序没有进行i18n”的事实 - 应该有更多的程序进行i18n,而不只是现在这样;随着互联网在中国等地迅速发展(通过智能手机、微型电脑等),以英语为中心的假设变得越来越怪异。 - Alex Martelli{self}
,{password}
或其他意外显示的对象到格式字符串中。这可能存在安全风险。最好在真实代码中明确指定。 - John La Rooy"hello {$name}s you are $age years old"
是字符串插值。这也是Python中相同的模式。我认为你是在开玩笑关于插值定义的问题 - “字符串插值”一词早已被用来描述这种类型的字符串格式。它甚至是PEP215的标题。 - Paul Biggar