问题描述
我发现了这种模式(或反图案),我对此感到非常满意.
我觉得这很敏捷:
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匹配位置保留在元组中.
你喜欢吗?您会使用它吗?是/否,请解释.
推荐答案
对于小型应用程序和据称是"一次性"脚本是可以的,尤其是 @kaizer.se提到的vars增强功能和@redglyph提到的
但是,对于具有较长维护寿命的大型应用程序和许多维护者,这种做法可能会导致维护头痛,我认为这就是 @S.Lott的答案来源.让我解释一些涉及的问题,因为对于没有开发和维护大型应用程序(或此类野兽的可重复使用的组件)的任何人来说,它们可能并不明显.
在"严重"应用程序中,您不会用硬编码格式字符串 - 或者,如果有的话,它会以某种形式(例如_('Hello {name}.')),其中_来自_. =" http://docs.python.org/library/gettext.html" rel =" noreferrer"> getText 或类似的I18N/L10N框架.关键是,这样的应用程序(或可以在此类应用程序中使用的可重复使用的模块)必须支持国际化(又称I18N)和位置化(又称L10N):您希望您的应用程序能够在某些地方发射" Hello Paul"国家和文化,其他一些人的" Hola Paul"," Ciao Paul"在其他情况下,等等.因此,格式字符串或多或少在运行时自动替换为另一个,具体取决于当前的本地化设置;它没有被硬编码,而是生活在某种数据库中.出于所有意图和目的,请想象格式字符串始终是一个变量,而不是字符串文字.
所以,您拥有的本质上是
formatstring.format(**locals())
,您不能微不足道地检查格式将要使用的本地名称.您必须打开并仔细阅读L10N数据库,确定将在不同设置中使用的格式字符串,验证所有这些字符串.
因此,在实践中,您不知道将要使用哪些本地名称 - 这可怕地压接了该功能的维护.您不敢重命名或删除任何局部变量,因为它可能会为用户带来可怕的用户体验(对您来说)晦涩难懂的语言,语言环境和偏好
如果您进行了出色的集成/回归测试,则会在Beta版本发布之前捕获破裂 - 但是QA会向您尖叫,并且发行版将被延迟...而且,老实说,同时,瞄准100%的覆盖范围使用单元测试是合理的,一旦考虑了设置的组合爆炸[[[[出于许多原因,并且出于许多原因)]并支持了支持所有依赖性的版本.因此,您只是不会大而为然地冒着破裂的风险,因为"它们会被抓住QA"(如果您这样做,您可能不会在开发大型应用程序或可重复使用的组件的环境中持续很长时间; - ).
.因此,在实践中,即使用户体验人们长期以来,您也永远不会删除"名称"本地变量,即使人们对更合适的"欢迎,可怕的霸主"切换了这个问候! (并适当的L10N'ED版本).都是因为您去了locals() ...
因此,您由于您的维护和编辑代码的能力而累积了cruft,也许是因为它是从DB或类似的情况下进行的,因此仅存在"名称"本地变量,因此请保持它(或其他某些本地)不仅仅是Cruft,还可以降低您的表现. locals()的表面便利性是否值得? - )
但是,等等,更糟!在许多有用的服务中,lint类似的程序(例如,例如, Pylint )可以做您是要警告您有关未使用的本地变量(希望它也可以为未使用的全球群体做到,但是对于可重复使用的组件来说,这太难了;-).这样,您会非常迅速,便宜地捕获大多数偶尔的拼写错误,而不是看到单位测试中断并进行侦察工作以找出为什么它会破裂(您 you 是否有强迫性,普遍的单位测试, 最终会抓住这一点,对吗? - ) - Lint会告诉您未使用的本地变量nmae,您将立即修复它.<<<<<<<<
但是,如果您在代码中有blah.format(**locals())或等效的blah % locals() ...你是sol,pal! - )糟糕的棉绒如何知道nmae实际上是未使用的变量,或者实际上它确实被您传递locals()的任何外部功能或方法使用?它不能 - 无论如何它都会警告过(引起"哭泣的狼"效应,最终导致您忽略或禁用此类警告),或者永远不会警告过(具有相同的最终效果:没有警告:---) .
将此与"明确比隐式更好"的替代方案进行比较...
blah.format(name=name)
在那里 - 不再适用于维护,性能和AM-i损坏薄弱的烦恼;幸福!您会立即向每个有关的人清楚地清楚(包括lint ;-)恰好使用了 正用于什么目的.
.我可以继续,但是我认为这篇文章已经很长; - ).
因此,总结:" γνῶθισεαυτόν!"嗯,我的意思是,"知道自己!".实际上,我的意思是"您的代码的目的和范围".如果这是一件1次或涉及的事情,永远不会成为I18n'd和l10n'd,几乎不需要以后的维护,永远不会在更广泛的环境中重复使用,等等,然后继续使用
顺便说一句,这只是Python正在努力支持"小型,一次,探索性,也许可以交互式"编程的例子之一(通过允许和支持远远超出locals()的风险便利 - 想到 - 想到 - 想到 - import *,eval,exec以及其他几种方法,您可以为方便起见而掩盖名称空间和风险维护影响)以及"大型,可重复使用的,企业-Y"的应用程序和组件.它可以在两者的两者中都做得很好,但是只有当您"知道自己"并避免使用"便利"部分,除非您绝对确定您实际上可以负担得起.通常的关键考虑因素是:"这对我的名称空间有什么作用,以及编译器,毛刺,人类读者和维护者的形成和使用的认识,等等?".
记住,"名称空间是一个鸣喇叭的好主意 - 让我们做更多的事情!"是Python的禅宗的结论……但是Python作为"同意成年人的语言",让您您定义了由于您的发展环境,目标和目标的结果,这意味着什么暗示的界限实践.负责任地使用此电源! - )
其他推荐答案
从未在一百万年内.目前尚不清楚格式化的上下文是:locals几乎可以包含任何变量. self.__dict__并不那么模糊.让未来的开发人员挠头对本地和不是本地的事物的脑袋很糟糕.
这是一个故意的谜.为什么要用这样的未来维护头痛来骑您的组织?
其他推荐答案
我认为这是一个很好的模式,因为您利用内置功能来减少所需编写的代码.我个人觉得它很完美.
我从来没有编写我不需要编写的代码 - 更少的代码比更多的代码更好,例如,使用locals()的惯例使我可以更少编写代码,并且也很容易阅读和理解./p>
问题描述
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.
推荐答案
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
formatstring.format(**locals())
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 as if ...: 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 variable nmae and you will immediately fix it.
But if you have in your code a blah.format(**locals()), or equivalently a blah % locals()... you're SOL, pal!-) How is poor lint going to know whether nmae is in fact an unused variable, or actually it does get used by whatever external function or method you're passing locals() 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...:
blah.format(name=name)
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 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?".
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!-)
其他推荐答案
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?
其他推荐答案
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.