Python:使用"..%(var)s .."%locals()是一个好习惯吗?

fly*_*ire 70 python string design-patterns anti-patterns

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

我觉得它非常敏捷:

def example():
    age = ...
    name = ...
    print "hello %(name)s you are %(age)s years old" % locals()
Run Code Online (Sandbox Code Playgroud)

有时候我会用它的堂兄:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % obj.__dict__
Run Code Online (Sandbox Code Playgroud)

我不需要创建一个人工元组并计算参数并保持元组内的%s匹配位置.

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

Ale*_*lli 89

它适用于小型应用程序和所谓的"一次性"脚本,特别是vars@ .formatkaizer.se提到的增强功能和@RedGlyph提到的版本.

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

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

所以,你拥有的本质上是

formatstring.format(**locals())
Run Code Online (Sandbox Code Playgroud)

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

因此,在实践中,您不知道将使用哪些本地名称 - 这会严重影响功能的维护.你不敢重命名或删除任何局部变量,因为它可能会严重破坏用户的一些(对你而言)模糊的语言,语言环境和偏好组合的用户体验

如果你有极好的集成/回归测试,那么破坏将在测试版发布之前被捕获 - 但QA会尖叫你并且发布将被延迟......而且,说实话,同时旨在100%覆盖单元测试一旦你考虑到设置的组合爆炸[[对于L10N和更多原因]]和所有依赖项的支持版本,它确实是合理的,它不是集成测试.所以,你只是不要随便四处寻找破损的风险因为"它们会被QA捕获"(如果你这样做,你可能不会在开发大型应用程序或可重用组件的环境中持续很长时间;-).

所以,在实践中,你永远不会删除"名称"局部变量,即使用户体验人员长时间将这种问候转换为更合适的"欢迎,恐惧霸主!" (并且适当地为其L10n'ed版本).一切都因为你去locals()...

因为你压缩了维护和编辑代码的能力,所以你正在累积残余 - 也许这个"名称"局部变量只存在,因为它是从数据库等中提取的,所以保留它(或者其他一些本地人)不仅仅是残酷的,它也会降低你的表现.是表面上的便利locals()价值 - ?)

但等等,情况更糟!在许多有用的服务中,lint类似程序(例如,pylint)可以为你做的,是警告你未使用的局部变量(希望它也可以为未使用的全局变量做,但是,对于可重用的组件,这只是有点太难了;-).通过这种方式,您可以if ...: nmae = ...非常快速和廉价地捕获大多数偶然的拼写错误,而不是通过查看单元测试中断和做侦查工作来找出它为什么会破坏(您确实有过强迫,无处不在的单元测试,最终抓住这个,对吗? - ) - lint会告诉你一个未使用的局部变量nmae,你会立即修复它.

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

将此与"明确胜于隐性"的替代方案相比......:

blah.format(name=name)
Run Code Online (Sandbox Code Playgroud)

在那里 - 没有任何维护,性能和我受到妨碍的担忧,不再适用; 幸福!你可以立即向所有相关人员(包括lint ;-)明确说明正在使用的局部变量,以及用于何种目的.

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

所以,总结:" γνῶθισεαυτόν!" 嗯,我的意思是,"认识你自己!".而对于"你自己",我实际上是指"你的代码的目的和范围".如果它是1-off-or-about的东西,永远不会是i18n'd和L10n'd,几乎不需要将来的维护,永远不会在更广泛的环境中重复使用等等,然后继续使用locals()它小而整洁的便利; 如果你不知道,或者即使你不完全确定,也要小心谨慎,并使事情变得更加明确 - 承受一点点不方便拼写出你正在发生的事情,并享受所有由此带来的好处.

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

请记住,"命名空间是一个很好的主意 - 让我们做更多的事情吧!" 是Python的禅宗如何结束......但是,Python作为"同意成人的语言",允许定义由于您的开发环境,目标和实践而隐含的界限.负责任地使用这个电源! - )

  • @Paul,我不同意:字符串插值是_excellent_,_esonal_对于i18n/L10n支持 - 它只需要在明确命名的变量上发生!问题不在于插值,而是将`locals()`传递给外部函数或方法.顺便说一下,Python对Unicode的支持越来越多(现在是`3.*`中的默认文本字符串)正是为了帮助改变"大多数程序都不是i18n"这一事实的尝试 - 比当前_are_更多_should_.随着中国和网络的"网络(通过智能手机,上网本和c)"的兴起,以角度为中心的假设变得更加奇怪和怪异;-). (5认同)
  • @Alex:在PHP或Perl中,`"hello {$ name} s你已经年满岁了"``是字符串插值.这与Python中的模式相同.我认为你正在开玩笑说插值定义 - "字符串插值"一直是描述这种字符串格式的术语.它甚至是PEP215的称号. (4认同)
  • 我认为有可能摆弄locales/gettext来插入`{self}`,`{password}`或其他你不希望在格式字符串中显示的对象.这可能存在安全风险.最好明确的是真正的代码 (2认同)
  • @Paul,我称之为"字符串格式化" - 名称完全是任意的(无论你是使用特制的字典还是现有的字典,显然都是相同的声明),并且没有涉及插值 - 请参阅定义在http://en.wikipedia.org/wiki/Interpolation上插值.合并'a'和'c'得到'b',现在****将是字符串的插值. (2认同)

And*_*are 10

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

我从不编写我不需要编写的代码 - 更少的代码比更多的代码更好,这种使用的实践locals()允许我编写更少的代码并且也非常容易阅读和理解.


S.L*_*ott 10

在一百万年里都不会发生.目前还不清楚格式化的上下文是什么:locals几乎可以包含任何变量.self.__dict__不是那么模糊.非常糟糕的是让未来的开发人员对本地和非本地的东西感到头疼.

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

  • 我不明白为什么在格式字符串中引用局部变量名比在代码中引用局部变量名更不清楚. (6认同)
  • 为什么不清楚?它看起来非常清楚. (2认同)

pfc*_*ise 10

关于"表兄"而不是obj.__dict__,新的字符串格式看起来好多了:

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

我对repr方法使用了很多,例如

def __repr__(self):
    return "{s.time}/{s.place}/{s.warning}".format(s=self)
Run Code Online (Sandbox Code Playgroud)


Red*_*yph 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()))
Run Code Online (Sandbox Code Playgroud)


u0b*_*6ae 6

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

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % vars(obj)
Run Code Online (Sandbox Code Playgroud)

效果当然是一样的.