72

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

我觉得它很敏捷:

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 匹配位置保留在元组内。

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

4

7 回答 7

90

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

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

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

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

formatstring.format(**locals())

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

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

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

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

因此,由于您限制了维护和编辑代码的能力,因此您正在积累垃圾-也许该“名称”局部变量仅存在,因为它是从数据库等中获取的,因此请保留它(或其他一些当地人)周围不仅是杂乱无章的,它也会降低你的表现。表面上的便利locals()值得?-)

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

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

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

blah.format(name=name)

那里 - 不再适用于维护、性能和 am-I-hampering-lint 问题;幸福!您立即让所有相关人员(包括 lint)清楚地知道正在使用哪些局部变量,以及确切用于什么目的。

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

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

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

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

于 2009-10-11T17:14:09.967 回答
11

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

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

于 2009-10-11T13:22:53.223 回答
10

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

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

于 2009-10-11T11:28:56.373 回答
10

关于“表亲”,而不是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)
于 2011-05-24T06:32:44.943 回答
8

"%(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()))
于 2009-10-11T11:54:55.743 回答
6

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

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

效果当然是一样的。

于 2009-10-11T13:15:38.983 回答
1

从 Python 3.6.0 开始,现在有一种官方方法可以做到这一点:格式化字符串文字

它是这样工作的:

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'

参考:

于 2017-06-08T17:38:54.810 回答