Python:使用 "..%(var)s.." % locals() 是一个好习惯吗?

Posted

技术标签:

【中文标题】Python:使用 "..%(var)s.." % locals() 是一个好习惯吗?【英文标题】:Python: is using "..%(var)s.." % locals() a good practice? 【发布时间】:2010-12-05 17:57:31 【问题描述】:

我发现了这种模式(或反模式),我对此非常满意。

我觉得它很敏捷:

def example():
    age = ...
    name = ...
    print "hello %(name)s you are %(age)s years old" % locals()

有时我会使用它的表亲:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % obj.__dict__

我不需要创建人工元组和计数参数并将 %s 匹配位置保留在元组内。

你喜欢吗?会/会使用它吗?是/否,请解释一下。

【问题讨论】:

【参考方案1】:

对于小型应用程序和所谓的“一次性”脚本来说是可以的,尤其是@kaizer.se 提到的vars 增强和@RedGlyph 提到的.format 版本。

但是,对于维护寿命长且维护人员众多的大型应用程序,这种做法可能会导致维护问题,我认为这就是 @S.Lott 的答案的来源。让我解释一下其中涉及的一些问题,因为对于没有开发和维护大型应用程序(或此类野兽的可重用组件)的任何人来说,它们可能并不明显。

在“严肃”的应用程序中,您不会对格式字符串进行硬编码——或者,如果有的话,它将采用某种形式,例如 _('Hello name.'),其中 _ 来自 gettext或类似的 i18n / L10n 框架。关键是这样的应用程序(或可能在此类应用程序中使用的可重用模块)必须支持国际化(AKA i18n)和定位(AKA L10n):您希望您的应用程序能够在某些情况下发出“Hello Paul”国家和文化,其他一些中的“Hola Paul”,其他一些中的“Ciao Paul”,等等。因此,格式字符串在运行时或多或少会自动替换为另一个,具体取决于当前的本地化设置;它不是硬编码的,而是存在于某种数据库中。出于所有意图和目的,假设格式字符串始终是一个变量,而不是字符串文字。

所以,你所拥有的本质上是

formatstring.format(**locals())

而且你不能轻易地检查格式将使用的什么本地名称。您必须打开并仔细阅读 L10N 数据库,确定将在不同设置中使用的格式字符串,并验证所有这些。

所以在实践中,您不知道将使用哪些本地名称——这严重影响了函数的维护。你不敢重命名或删除任何局部变量,因为它可能会严重破坏具有某些(对你而言)语言、区域设置和偏好的模糊组合的用户的用户体验

如果您有出色的集成/回归测试,那么问题将在 Beta 版发布之前被发现——但 QA 会向您大喊大叫,发布将被延迟……老实说,同时以 100% 的覆盖率为目标unit 测试是合理的,它真的不是 integration 测试,一旦你考虑设置的组合爆炸 [[for L10N 和更多原因]] 并支持所有依赖项的版本。因此,您只是不要冒着损坏的风险四处走动,因为“他们会在 QA 中被抓住”(如果这样做,您可能不会在开发大型应用程序或可重用组件的环境中持续很长时间;-)。

因此,在实践中,您永远不会删除“name”局部变量,即使用户体验人员早就将该问候语切换为更合适的“欢迎,恐惧霸主!” (以及适当的 L10n'ed 版本)。都是因为你选择了locals()...

因此,由于您削弱了维护和编辑代码的能力,因此您正在积累垃圾——也许“名称”局部变量仅存在,因为它是从数据库等中获取的,所以保持它(或其他一些本地)不仅是杂乱无章的,它也会降低你的表现。 locals() 的表面便利值得那个吗?-)

但是等等,还有更糟的!在许多有用的服务中,类似lint 的程序(例如,pylint)可以为您提供警告,即警告您未使用的局部变量(希望它也可以为未使用的全局变量执行此操作,但是,对于可重复使用的组件,这有点太难了;-)。通过这种方式,您可以非常快速且廉价地发现大多数偶尔出现的拼写错误,例如 if ...: nmae = ...,而不是通过查看单元测试中断并进行侦探工作来找出 为什么 它会中断(您 确实有强迫性的、普遍的单元测试最终捕捉到这一点,对吗?-) -- lint 会告诉你一个未使用的局部变量nmae,你会立即修复它。

但是如果你的代码中有一个blah.format(**locals()),或者等效的一个blah % locals()...你是SOL,朋友!-) 可怜的lint 怎么知道nmae 是否实际上是一个未使用的变量,或者实际上它确实被您将locals() 传递给的任何外部函数或方法使用?它不能——要么它无论如何都会发出警告(导致最终导致你忽略或禁用此类警告的“哭狼”效果),或者它永远不会发出警告(具有相同的最终效果:没有警告;-) .

将此与“显式优于隐式”的替代方案进行比较...:

blah.format(name=name)

那里——不再需要维护、性能和 am-I-hampering-lint 问题;幸福!您立即让所有相关人员(包括 lint)清楚地了解正在使用的什么局部变量,以及具体用于什么目的。

我可以继续,但我认为这篇文章已经很长了;-)。

所以,总结一下:“γνῶθι σεαυτόν!”嗯,我的意思是,“认识你自己!”。而“你自己”实际上是指“你的代码的目的和范围”。如果它是 1-off-or-thereabouts,永远不会成为 i18n'd 和 L10n'd,几乎不需要未来维护,永远不会在更广泛的环境中重复使用,等等,然后继续使用 @987654339 @ 小而整洁的便利;如果您不知道,或者即使您不完全确定,请谨慎行事,并让事情更明确 - 忍受一点点的不便,因为您要准确地说明您要做什么,并享受所有由此产生的优势。

顺便说一句,这只是 Python 努力支持“小型、一次性、探索性、也许是交互式”编程的示例之一(通过允许和支持远远超出 locals() 的风险便利——想想import *evalexec,以及其他几种可以混合命名空间和风险维护影响的方法),以及“大型、可重用、企业级”应用程序和组件。它可以在这两个方面做得很好,但前提是您“了解自己”并避免使用“方便”部分,除非您绝对确定您实际上可以负担得起。通常情况下,关键考虑因素是“这对我的命名空间有什么影响,以及编译器、lint &c、人类读者和维护者等对它们的形成和使用的认识?”。

请记住,“命名空间是一个很棒的主意——让我们做更多这样的事!”这就是 Python 之禅的结论......但是 Python 作为“成年人同意的语言”,允许根据您的开发环境、目标和实践。负责任地使用这种力量!-)

【讨论】:

一个很好的答案。我认为大多数程序都没有国际化,因此在很多情况下这不是问题。但在那些情况下,是的,字符串插值是不好的。 @Paul,我不敢苟同:字符串插值 优秀尤其是支持 i18n/L10n ——它只需要显式发生命名变量!问题不在于插值,而在于将locals() 传递给外部函数或方法。顺便说一句,Python 越来越多地支持 Unicode(现在是 3.* 中的默认文本字符串)正是试图帮助改变“大多数程序不是 i18n'd”这一事实——更多应该 , 比目前 ;随着“网络”(通过智能手机、上网本等)在中国等地蓬勃发展,以英语为中心的假设变得越来越奇怪;-)。 我认为有可能使用 locales/gettext 来插入 self,password 或您不希望在格式字符串中显示的其他对象。这可能是一个安全风险。最好是明确的真实代码 @Paul,我称之为“字符串格式”——名称是完全任意的(无论您使用的是特制的 dict 还是现有的,这显然是相同的声明),并且没有涉及插值 - 请参阅 en.wikipedia.org/wiki/Interpolation 的插值定义。合并一个 'a' 和一个 'c' 得到一个 'b',现在 将是字符串的插值。 @Alex:在 php 或 Perl 中,"hello $names you are $age years old" 是字符串插值。这与 Python 中的模式相同。我认为您在对插值定义开玩笑——“字符串插值”长期以来一直是描述这种字符串格式的术语。它甚至是 PEP215 的标题。【参考方案2】:

一百万年来从未有过。尚不清楚格式化的上下文是什么:locals 几乎可以包含任何变量。 self.__dict__ 没有那么模糊。让未来的开发人员为什么是本地的,什么不是本地的而摸不着头脑,真是太糟糕了。

这是一个故意的谜。为什么要让您的组织面临这样的未来维护难题?

【讨论】:

上下文是locals()出现的函数。如果它是一个不错的简短函数,您可以只查看 vars。如果它是一个很长的函数,它应该被重构。 self.__dict__ 如何更清晰? 我不明白为什么在格式字符串中引用局部变量名比在代码中引用局部变量名更不清楚。 self.__dict__ 基于类定义——通常与__init__() 方法相关联,并在文档字符串中仔细记录。 locals() 通常是一个相当随机的名称集合。 我也不是特别喜欢,但不是因为不清楚。 Python 的作用域比许多其他语言更简单,但像这样的设计模式似乎仍然在自找麻烦,因为你会发现自己变得懒惰并在特定定义的函数之外使用,然后你会遇到作用域问题。跨度> @Robert Rossney:我从来没有说过变量 name 不清楚。我说locals() 不清楚,因为变量名更难找到。它们隐藏在格式字符串中。【参考方案3】:

我认为这是一个很好的模式,因为您正在利用内置功能来减少您需要编写的代码。我个人觉得它很 Pythonic。

我从不编写我不需要编写的代码 - 更少的代码比更多的代码更好,例如这种使用 locals() 的做法使我可以编写更少的代码,并且也非常易于阅读和理解。

【讨论】:

我喜欢在函数顶部使用它,当我需要一个由输入参数构建的字典时。它非常有效,我也觉得它像蟒蛇一样。它有时会被滥用,我可以理解。【参考方案4】:

关于“表亲”,而不是obj.__dict__,使用新的字符串格式看起来好多了:

def example2(obj):
    print "The file at o.path has o.length bytes".format(o=obj)

我经常将它用于 repr 方法,例如

def __repr__(self):
    return "s.time/s.place/s.warning".format(s=self)

【讨论】:

【参考方案5】:

"%(name)s" % <dictionary> 甚至更好,"name".format(<parameters>) 的优点是

比“%0s”更易读 独立于参数顺序 不强制使用字符串中的所有参数

我倾向于使用 str.format(),因为它应该是在 Python 3 中执行此操作的方式(根据 PEP 3101),并且已经从 2.6 开始提供。但是使用locals(),您必须这样做:

print("hello name you are age years old".format(**locals()))

【讨论】:

【参考方案6】:

使用内置的vars([object]) (documentation) 可能会使第二个看起来更好:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % vars(obj)

效果当然是一样的。

【讨论】:

【参考方案7】:

现在有一种官方方法可以做到这一点,从 Python 3.6.0 开始:formatted string literals。

它是这样工作的:

f'normal string text local_variable_name'

例如而不是这些:

"hello %(name)s you are %(age)s years old" % locals()
"hello name you are age years old".format(**locals())
"hello  you are  years old".format(name, age)

这样做:

f"hello name you are age years old"

这是官方的例子:

>>> name = "Fred"
>>> f"He said his name is name."
'He said his name is Fred.'
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: value:width.precision"  # nested fields
'result:      12.35'

参考:

Python 3.6 What's New PEP 498 Lexical analysis description

【讨论】:

这让我很开心。我将把它用于我所有不受支持 2.x 限制的 python 代码,或者在这种情况下

以上是关于Python:使用 "..%(var)s.." % locals() 是一个好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

python tkinter 用Toplevel建立子窗口,Entry.get返回为空?

javascrip中,数字转化为字符串,怎么转?例如 var s="12"+34,则s的值为多少?

javascript var s="a":"b","c":"d" 如何遍历?

Python基础语法

使用GD&GET vars的图像缩略图

C#的一些小知识