使用“ . .% (var) s. . .”% local()是一种好的做法吗?

我发现了这个模式(或反模式) ,并且我对它非常满意。

我觉得它非常灵活:

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

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

18263 次浏览

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

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

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

  • 比“% 0”更具可读性
  • 独立于论点顺序
  • 不必使用字符串中的所有参数

我倾向于使用 str.format () ,因为它应该是在 Python 3中实现这一点的方法(根据 PEP 3101) ,并且已经在2.6中可用。不过,对于 locals(),你必须这样做:

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

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

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

效果当然是一样的。

绝对不可能。不清楚格式化的上下文是什么: locals几乎可以包含任何变量。self.__dict__没有这么含糊。让未来的开发人员对什么是本地的,什么不是本地的感到摸不着头脑真是太糟糕了。

这是一个有意为之的谜团。为什么要让您的组织承受这样的未来维护头疼问题呢?

对于小型应用程序和所谓的“一次性”脚本,特别是@kaizer 提到的 vars增强,这是可以的。Se 和@RedGlyph 提到的 .format版本。

然而,对于维护生命周期较长的大型应用程序和许多维护人员来说,这种做法可能会导致维护方面的麻烦,我认为这就是@S。洛特的回答来自。让我来解释其中涉及的一些问题,因为对于那些没有开发和维护大型应用程序(或者对于这些野兽来说可重用的组件)创伤的人来说,这些问题可能并不明显。

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

所以,本质上来说

formatstring.format(**locals())

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

所以在实践中,你不能使用 知道中的本地名称——这会严重影响函数的维护。您不敢重命名或删除任何本地变量,因为它可能会严重破坏用户的用户体验,使用一些(对您来说)模糊的语言、区域设置和首选项的组合

如果你有一流的集成/回归测试,那么在 beta 版本发布之前就会出现问题——但是 QA 会对你大吼大叫,而且发布会被推迟... ... 而且,老实说,虽然 单位测试的覆盖率达到100% 是合理的,但是如果你考虑到设置的组合爆炸[对于 L10N 和其他更多的原因]和所有依赖的支持版本,那么对于 整合测试来说就不是这样了。所以,你不能因为“他们会被 QA 抓住”(如果你这样做了,你可能在一个开发大型应用程序或可重用组件的环境中坚持不了多久;。

因此,在实践中,您永远不会删除“ name”本地变量,即使用户体验人员早已将该问候语转换为更合适的“ Welcome,Dread Overlord!”(以及适当的 L10n 版本)。都是因为你选了 locals()

所以你正在积累 cruft,因为你已经限制了你维护和编辑代码的能力——也许那个“ name”局部变量的存在只是因为它是从 DB 或者类似的数据库中获取的,所以保留它(或者其他局部变量)不仅仅是 cruft,它也降低了你的性能。locals()的表面便利性值得 那个吗?-)

等等,还有更糟的!在众多有用的服务中,一个类似 lint的程序(例如,皮林特)可以为你做的就是警告你关于未使用的本地变量(希望它也可以为未使用的全局变量做到这一点,但是,对于可重用的组件来说,这有点太难了; ——)。这样你就可以快速而廉价地捕捉到大多数偶然的拼写错误,比如 if ...: nmae = ...,而不是通过看到单元测试中断,然后做侦查工作来发现 为什么中断了(你 有着强迫性的、普遍的单元测试,而 最终捕捉到了这一点,对吗?-)—— lint 将告诉您一个未使用的局部变量 nmae,您将立即修复它。

但是如果你在你的代码中有一个 blah.format(**locals()),或者相当于一个 blah % locals()... 你是 SOL,伙计!-)可怜的 lint 怎么知道 nmae实际上是一个未使用的变量,还是它确实被你传递 locals()给的外部函数或方法所使用?它不能——要么它无论如何都会发出警告(导致一种“狼来了”的效应,最终导致你忽略或禁用这种警告) ,要么它永远不会发出警告(最终的结果是: 没有警告; ——)。

比较一下“显性比隐性更好”的选择... ... :

blah.format(name=name)

这里——不再需要考虑维护、性能以及是否妨碍了线头的问题; 祝福!您可以立即向所有相关人员(包括 lint; ——)说明使用 什么局部变量的确切目的。

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

So, summarizing: "Γν something θι σα厅 τóν!" Hmm, I mean, "know thyself!". And by "thyself" I actually mean "the purpose and scope of your code". If it's a 1-off-or-thereabouts thingy, never going to be i18n'd and L10n'd, will hardly need future maintenance, will never be reused in a broader context, etc, etc, then go ahead and use locals() for its small but neat convenience; if you know otherwise, or even if you're not entirely certain, err on the side of caution, and make things more explicit -- suffer the small inconvenience of spelling out exactly what you're going, and enjoy all the resulting advantages.

BTW, this is just one of the examples where Python is striving to support both "small, one-off, exploratory, maybe interactive" programming (by allowing and supporting risky conveniences that extend well beyond locals() -- think of import *, eval, exec, and several other ways you can mush up namespaces and risk maintenance impacts for the sake of convenience), as well as "large, reusable, enterprise-y" apps and components. It can do a pretty good job at both, but only if you "know thyself" and avoid using the "convenience" parts except when you're absolutely certain you can in fact afford them. More often than not, the key consideration is, "what does this do to my namespaces, and awareness of their formation and use by the compiler, lint &c, human readers and maintainers, and so on?".

记住,“名称空间是一个非常棒的想法——让我们做更多这样的事情!”但是作为一种“成人协议语言”,Python 让 根据你的开发环境、目标和实践来定义它的边界。负责任地使用这种力量!-)

关于“表亲”,与 obj.__dict__不同,使用新的字符串格式看起来要好得多:

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

我经常在 公司代表方法中使用这种方法,例如。

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

现在有一种正式的方法可以做到这一点,从 Python3.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'

参考文献: