Python:正在使用“..%(var)s..” % locals() 是一个好习惯吗?
我发现了这种模式(或反模式),我对此非常满意。
我觉得它非常敏捷:
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 匹配位置保留在元组内。
你喜欢它?你会/会使用它吗?是/否,请解释。
I discovered this pattern (or anti-pattern) and I am very happy with it.
I feel it is very agile:
def example():
age = ...
name = ...
print "hello %(name)s you are %(age)s years old" % locals()
Sometimes I use its cousin:
def example2(obj):
print "The file at %(path)s has %(length)s bytes" % obj.__dict__
I don't need to create an artificial tuple and count parameters and keep the %s matching positions inside the tuple.
Do you like it? Do/Would you use it? Yes/No, please explain.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
对于小型应用程序和所谓的“一次性”脚本来说这是可以的,特别是使用 @kaizer.se 提到的
vars
增强功能和 @RedGlyph 提到的.format
版本。然而,对于具有较长维护寿命和许多维护人员的大型应用程序,这种做法可能会导致维护麻烦,我认为这就是 @S.Lott 的答案的来源。让我解释一下所涉及的一些问题,因为对于那些没有开发和维护大型应用程序(或此类野兽的可重用组件)的伤疤的人来说,这些问题可能并不明显。
在“严肃”的应用程序中,您不会对格式字符串进行硬编码 - 或者,如果有的话,它会采用某种形式,例如
_('Hello {name}.')
,其中_
来自 gettext 或类似的 i18n / L10n框架。关键是这样的应用程序(或可能在此类应用程序中碰巧使用的可重用模块)必须支持国际化(AKA i18n)和定位(AKA L10n):您希望您的应用程序能够以某种方式发出“Hello Paul”国家和文化,在某些国家和文化中,“Hola Paul”在其他一些国家和文化中,“Ciao Paul”在其他国家和文化中,等等。因此,格式字符串或多或少会在运行时自动替换为另一个格式字符串,具体取决于当前的本地化设置;它不是被硬编码,而是存在于某种数据库中。出于所有意图和目的,请想象格式字符串始终是变量,而不是字符串文字。因此,您所拥有的本质上是
,您无法简单地检查格式化将使用的本地名称。您必须打开并仔细研究 L10N 数据库,识别将在不同设置中使用的格式字符串,并验证所有这些。
因此,在实践中,您不知道将使用哪些本地名称 - 这严重限制了函数的维护。您不敢重命名或删除任何局部变量,因为它可能会严重破坏用户的用户体验(对您来说)一些模糊的语言、区域设置和偏好组合。
如果您有出色的集成/回归测试,那么在beta 版本——但是 QA 会对你大喊大叫,发布也会被推迟……而且,说实话,虽然单元测试的目标是 100% 覆盖率是合理的,但事实并非如此集成测试,一旦您考虑设置的组合爆炸[[针对 L10N 以及更多原因]] 和所有依赖项的支持版本。因此,您只是不要轻率地冒着损坏的风险,因为“它们会陷入质量检查”(如果您这样做,您可能不会在开发大型应用程序或可重用组件的环境中持续很长时间;-)。
因此,在实践中,你永远不会删除“name”局部变量,即使用户体验人员早已将该问候语切换为更合适的“欢迎,恐惧霸主!” (以及适当的 L10n 版本)。这一切都是因为你选择了 locals() ......
所以你正在积累一些垃圾,因为你限制了你维护和编辑代码的能力——也许还有“名称”局部变量存在只是因为它是从数据库等中获取的,因此保留它(或其他本地的)不仅很麻烦,而且还会降低您的性能。
locals()
表面上的便利值得那样吗?-)但是等等,还有更糟糕的!在众多有用的服务中,有一个类似于
lint
的程序(例如 pylint) 可以为您做的就是警告您未使用的局部变量(希望它也可以对未使用的全局变量执行此操作,但是,对于可重用组件,这有点太难了;-)。通过这种方式,您将非常快速且廉价地发现大多数偶然的拼写错误,例如if ...: nmae = ...
,而不是通过查看单元测试中断并进行侦探工作来找出< em>为什么它坏了(你确实有强迫性的、普遍的单元测试最终会发现这个,对吧?-)——lint会告诉你一个未使用的局部变量nmae
,您将立即修复它。但是,如果您的代码中有
blah.format(**locals())
,或者等效的blah % locals()
...那么您就是 SOL,朋友!-) 糟糕的 lint 如何知道 nmae 实际上是否是一个未使用的变量,或者实际上它是否被您传递的任何外部函数或方法使用了 locals() 到?它不能——要么它无论如何都会发出警告(导致“狼来了”效果,最终导致你忽略或禁用此类警告),要么它永远不会发出警告(具有相同的最终效果:没有警告;-) 。将此与“显式优于隐式”替代方案进行比较...:
维护、性能和 am-I-hampering-lint 担忧不再适用;幸福!您立即让所有相关人员(包括 lint;-)清楚地了解正在使用什么局部变量以及究竟用于什么目的。
我可以继续说下去,但我认为这篇文章已经很长了;-)。
所以,总结一下:“γνῶθι σεαυτόν!”嗯,我的意思是,“认识你自己!”。我所说的“你自己”实际上是指“你的代码的目的和范围”。如果它是一个 1-off-or-thereabouts 的东西,永远不会被国际化和本地化,几乎不需要未来的维护,永远不会在更广泛的上下文中重用,等等,然后继续使用
locals()
因其小而简洁的便利性;如果你不知道,或者即使你不完全确定,宁可谨慎行事,也要把事情说得更明确——忍受因准确说出你要做什么而带来的小不便,并享受由此带来的所有好处。顺便说一句,这只是 Python 努力支持“小型、一次性、探索性、也许是交互式”编程的示例之一(通过允许和支持远远超出
locals()
范围的有风险的便利性) -- 为了方便起见,考虑import *
、eval
、exec
以及其他几种可以混淆命名空间和风险维护影响的方法),以及“大型、可重用、企业级”应用程序和组件。它在这两方面都可以做得很好,但前提是你“了解自己”并避免使用“方便”的部件,除非你绝对确定你实际上负担得起它们。通常,关键的考虑因素是“这对我的命名空间有何影响,以及编译器、lint &c、人类读者和维护人员等对它们的形成和使用的认识?”。请记住,“命名空间是一个非常棒的想法——让我们多做一些这样的事情吧!”这就是 Python 之禅的结论……但是 Python 作为一种“同意成年人的语言”,让您根据您的开发环境、目标和目标来定义其含义的边界。做法。负责任地使用此权力!-)
It's OK for small applications and allegedly "one-off" scripts, especially with the
vars
enhancement mentioned by @kaizer.se and the.format
version mentioned by @RedGlyph.However, for large applications with a long maintenance life and many maintainers this practice can lead to maintenance headaches, and I think that's where @S.Lott's answer is coming from. Let me explain some of the issues involved, as they may not be obvious to anybody who doesn't have the scars from developing and maintaining large applications (or reusable components for such beasts).
In a "serious" application, you would not have your format string hard-coded -- or, if you had, it would be in some form such as
_('Hello {name}.')
, where the_
comes from gettext or similar i18n / L10n frameworks. The point is that such an application (or reusable modules that can happen to be used in such applications) must support internationalization (AKA i18n) and locatization (AKA L10n): you want your application to be able to emit "Hello Paul" in certain countries and cultures, "Hola Paul" in some others, "Ciao Paul" in others yet, and so forth. So, the format string gets more or less automatically substituted with another at runtime, depending on the current localization settings; instead of being hardcoded, it lives in some sort of database. For all intents and purposes, imagine that format string always being a variable, not a string literal.So, what you have is essentially
and you can't trivially check exactly what local names the formatting is going to be using. You'd have to open and peruse the L10N database, identify the format strings that are going to be used here in different settings, verify all of them.
So in practice you don't know what local names are going to get used -- which horribly crimps the maintenance of the function. You dare not rename or remove any local variable, as it might horribly break the user experience for users with some (to you) obscure combinaton of language, locale and preferences
If you have superb integration / regression testing, the breakage will be caught before the beta release -- but QA will scream at you and the release will be delayed... and, let's be honest, while aiming for 100% coverage with unit tests is reasonable, it really isn't with integration tests, once you consider the combinatorial explosion of settings [[for L10N and for many more reasons]] and supported versions of all dependencies. So, you just don't blithely go around risking breakages because "they'll be caught in QA" (if you do, you may not last long in an environment that develops large apps or reusable components;-).
So, in practice, you'll never remove the "name" local variable even though the User Experience folks have long switched that greeting to a more appropriate "Welcome, Dread Overlord!" (and suitably L10n'ed versions thereof). All because you went for
locals()
...So you're accumulating cruft because of the way you've crimped your ability to maintain and edit your code -- and maybe that "name" local variable only exists because it's been fetched from a DB or the like, so keeping it (or some other local) around is not just cruft, it's reducing your performance too. Is the surface convenience of
locals()
worth that?-)But wait, there's worse! Among the many useful services a
lint
-like program (like, for example, pylint) can do for you, is to warn you about unused local variables (wish it could do it for unused globals as well, but, for reusable components, that's just a tad too hard;-). This way you'll catch most occasional misspellings such asif ...: nmae = ...
very rapidly and cheaply, rather than by seeing a unit-test break and doing sleuth work to find out why it broke (you do have obsessive, pervasive unit tests that would catch this eventually, right?-) -- lint will tell you about an unused local variablenmae
and you will immediately fix it.But if you have in your code a
blah.format(**locals())
, or equivalently ablah % locals()
... you're SOL, pal!-) How is poor lint going to know whethernmae
is in fact an unused variable, or actually it does get used by whatever external function or method you're passinglocals()
to? It can't -- either it's going to warn anyway (causing a "cry wolf" effect that eventually leads you to ignore or disable such warnings), or it's never going to warn (with the same final effect: no warnings;-).Compare this to the "explicit is better than implicit" alternative...:
There -- none of the maintenance, performance, and am-I-hampering-lint worries, applies any more; bliss! You make it immediately clear to everybody concerned (lint included;-) exactly what local variables are being used, and exactly for what purposes.
I could go on, but I think this post is already pretty long;-).
So, summarizing: "γνῶθι σεαυτόν!" 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 ofimport *
,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?".Remember, "Namespaces are one honking great idea -- let's do more of those!" is how the Zen of Python concludes... but Python, as a "language for consenting adults", lets you define the boundaries of what that implies, as a consequence of your development environment, targets, and practices. Use this power responsibly!-)
一百万年里都不会。目前还不清楚格式化的上下文是什么:
locals
可以包含几乎任何变量。self.__dict__
并不那么含糊。让未来的开发人员对本地和非本地的内容摸不着头脑,这真是太糟糕了。这是一个故意的谜团。为什么要让您的组织承受这样的未来维护难题呢?
Never in a million years. It's unclear what the context for formatting is:
locals
could include almost any variable.self.__dict__
is not as vague. Perfectly awful to leave future developers scratching their heads over what's local and what's not local.It's an intentional mystery. Why saddle your organization with future maintenance headaches like that?
我认为这是一个很棒的模式,因为您正在利用内置功能来减少需要编写的代码。我个人觉得它很Pythonic。
我从不编写不需要编写的代码 - 更少的代码比更多的代码更好,例如使用
locals()
的这种做法允许我编写更少的代码,并且也很容易阅读并理解。I think it is a great pattern because you are leveraging built-in functionality to reduce the code you need to write. I personally find it quite Pythonic.
I never write code that I don't need to write - less code is better than more code and this practice of using
locals()
for example allows me to write less code and is also very easy to read and understand.关于“cousin”,而不是
obj.__dict__
,它在新的字符串格式下看起来好多了:我经常将其用于 repr 方法,例如
Regarding the "cousin", instead of
obj.__dict__
, it looks a lot better with new string formatting:I use this a lot for repr methods, e.g.
"%(name)s" %
甚至更好,"{name}".format()
的优点我倾向于使用 str.format(),因为它应该是在 Python 3 中执行此操作的方法(根据PEP 3101),并且已从 2.6 版本开始提供。但使用
locals()
时,您必须这样做:The
"%(name)s" % <dictionary>
or even better, the"{name}".format(<parameters>)
have the merit ofI would tend to favour the str.format(), since it should be the way to do that in Python 3 (as per PEP 3101), and is already available from 2.6. With
locals()
though, you would have to do this:使用内置的
vars([object])
(文档)可能会让第二个看起来更好:效果当然是一样的。
Using the built-in
vars([object])
(documentation) might make the second look better to you:The effect is of course just the same.
从 Python 3.6.0 开始,现在有一种官方方法可以做到这一点: 格式化字符串文字。
它的工作原理如下:
例如,而不是这些:
只需这样做:这
是官方示例:
参考:
There is now an official way to do this, as of Python 3.6.0: formatted string literals.
It works like this:
E.g. instead of these:
just do this:
Here's the official example:
Reference: