Python:使用“ ..%(var)s ..”%locals()是一种好习惯吗?


71

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

我觉得它非常敏捷:

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

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

Answers:


90

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

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

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

因此,您所拥有的基本上是

formatstring.format(**locals())

你不能平凡检查究竟是什么地方的名字的格式将被使用。您必须打开并仔细阅读L10N数据库,在不同的设置中标识将在此处使用的格式字符串,并验证所有这些格式字符串。

因此,在实践中,您不知道将使用哪些本地名称-这严重限制了该功能的维护。您不敢重命名或删除任何局部变量,因为它可能会严重破坏某些(对您)语言,区域设置和首选项组合的用户的用户体验

如果您进行了出色的集成/回归测试,那么在Beta版本发布之前就会发现破损-但是质量检查将对您大喊大叫,并且版本发布会延迟...而且,老实说,我们的目标是100%覆盖单元测试是合理的,一旦考虑了设置的组合爆炸([出于L10N和更多原因])以及所有依赖项的受支持版本,集成测试实际上就不对了。因此,您只是不会轻易地冒着遭受破坏的风险,因为“它们会陷入质量检查中”(如果这样做,您可能不会在开发大型应用程序或可重用组件的环境中持续很长时间;-)。

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

因此,由于您削弱了维护和编辑代码的能力的方式,您正在积累工作量-也许“名称”局部变量仅存在是因为它是从数据库等中获取的,因此请保留它(或其他一些本地人不仅在工作,而且也在降低您的表现。是表面上的便利locals()价值- ?)

但是,等等,更糟的是!在许多有用的服务中,lint类似程序(例如pylint)可以为您提供警告,警告您未使用的局部变量(希望它也可以对未使用的全局变量执行此操作,但是对于可重用的组件,这仅仅是有点太难了;-)。这样一来,您将if ...: nmae = ...迅速而便宜地捕获大多数偶然的拼写错误,而不是看到单元测试中断并进行侦查工作以找出其为何会破译(您确实有强迫症,普遍存在的单元测试最终抓住这种情况,对吗?-)-lint会告诉您一个未使用的局部变量nmae,您将立即对其进行修复。

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

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

blah.format(name=name)

那里-维护,性能和烦人的皮毛担心都不再适用;极乐!您立即向所有相关人员(包括棉绒;-)清楚地说明了正在使用什么局部变量,以及目的是什么。

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

因此,总结一下:“ γνῶθισεαυτόν!” 嗯,我的意思是,“知道自己!”。我所说的“自己”实际上是“代码的目的和范围”。如果它是“按价出售”或“按价出售”之类的东西,那么永远都不会成为i18n'和L10n'd,几乎不需要将来的维护,也永远不会在更广泛的上下文中重复使用,等等,等等,然后继续使用locals()它小而整洁的便利;如果您知道其他情况,或者即使您不确定,也请谨慎行事,并让事情变得更明确-麻烦一点,因为您很难清楚地说明要走的路,并享受由此带来的所有优势。

顺便说一句,这仅仅是在哪里Python是努力支持“小,一次性的,探索性的,也许互动”节目(通过允许和支持是远远超出危险便利的一个例子locals()-想到import *evalexec,和其他几个为了方便起见,可以使用多种方法来命名空间和风险维护影响),以及“大型,可重用的企业级”应用程序和组件。它在两者上都可以做得很好,但前提是您“了解自己”并且避免使用“便利”部分,除非您绝对确定可以负担得起。通常,关键的考虑因素是:“这对我的命名空间有什么作用,以及编译器lint&c对它们的形成和使用的认识,

请记住,“命名空间是一个很棒的主意-让我们做更多的事!” 这就是Python的Zen的结论……但是Python作为“成年人同意的语言”,可让定义由于开发环境,目标和实践而导致的隐含边界。负责任地使用此电源!-)


1
一个很好的答案。我认为大多数程序都没有国际化,因此在很多情况下这不是问题。但是在那种情况下,是的,字符串插值是不好的。
Paul Biggar

5
@Paul,我希望与众不同:字符串插值非常出色尤其是对于i18n / L10n支持-仅需要在显式命名的变量上发生!问题不在于插值,而locals()在于传递给外部函数或方法。BTW,Python的对Unicode(现在是默认的文本字符串中越来越多的支持3.*)正是试图帮助改变的事实,“大多数程序不i18n'd” -更多的应该是,比目前; 随着“互联网(通过智能手机,上网本等)的蓬勃发展,以英语为中心的假设变得越来越诡异;-)。
Alex Martelli,2009年

2
我认为有可能摆弄locales / gettext来插入{self}{password}或其他您不希望显示在格式字符串中的对象。这可能是安全风险。最好是明确真正的代码
约翰·拉ROOY

2
@Paul,我称这种方式为“字符串格式”-名称完全是任意的(无论您使用的是特制的dict还是现有的dict,这显然是同一条语句),并且不涉及插值-请参阅插值网址en.wikipedia.org/wiki/Interpolation。合并“ a”和“ c”以获得“ b”,现在将是字符串的插值。
Alex Martelli,2009年

4
@Alex:在PHP或Perl中,"hello {$name}s you are $age years old"是字符串插值。这与Python中的模式相同。我想您是在对插值定义开玩笑-“字符串插值”长期以来就是描述这种类型的字符串格式的术语。它甚至是PEP215的标题。
Paul Biggar

10

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

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


当我需要从输入参数中构建字典时,我喜欢在函数顶部使用它。它非常有效,我也感觉到它的Python性。我可以理解,有时可能会滥用它。
radtek

10

在一百万年里都不会发生。目前尚不清楚格式化的上下文是什么:locals几乎可以包含任何变量。self.__dict__并不那么模糊。让未来的开发人员在本地和非本地问题上纠缠不清是非常糟糕的。

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


上下文是其中显示locals()的函数。如果它是一个不错的短函数,则可以只查看var。如果它的功能很长,则应该对其进行重构。self .__ dict__怎么更清晰?
愚蠢的

6
我不明白为什么在格式字符串中引用本地变量名称比在代码中引用本地变量名称更加不清楚。
罗伯特·罗斯尼

self.__dict__基于类定义-通常与__init__()方法绑定在一起,并仔细记录在doc字符串中。 locals()通常是一个相当随机的名称集合。
S.Lott

我也不特别喜欢它,但不是因为不清楚。Python的作用域比许多其他语言要简单,但是像这样的设计模式似乎仍然会带来麻烦,因为您会发现自己变得懒惰并在特定定义的函数之外使用,然后会遇到作用域问题。
Paul McMillan,2009年

@Robert Rossney:我从未说过变量不清楚。我说locals()不清楚,因为很难找到变量名。它们被埋在格式字符串中。
S.Lott

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)

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()))

6

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

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

效果当然是一样的。


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'

参考:


1
这就是我的一天。我将对所有不受支持2.x限制的python代码使用此代码,或者在这种情况下<3.6!
svenevs
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.