Python中PHP的compact()和extract()的等价实现
compact() 和 extract() 是 PHP 里的两个非常实用的函数。compact() 函数可以从当前的变量列表中提取出变量名,并把它们的值放到一个哈希表里。extract() 则是做相反的事情。
$foo = 'what';
$bar = 'ever';
$a = compact('foo', 'bar');
$a['foo']
# what
$a['baz'] = 'another'
extract(a)
$baz
# another
有没有办法在 Python 中做到同样的事情呢?我查了很多资料,最接近的就是这个讨论,但看起来大家对这个不太赞同。
我知道有 locals()、globals() 和 vars() 这些函数,但我该怎么方便地选择它们的某一部分值呢?
Python 有没有更好的方法,可以省去这个需求呢?
6 个回答
值得一提的是,extract()
(还有稍微不那么糟糕的compact()
)是PHP中最“邪恶”的特性之一(还有register_globals
和eval
),应该尽量避免使用。
extract
会让你很难确定一个变量是在哪里定义的。当你很难追踪一个变量的来源时,就更难检查一些常见的安全问题,比如使用未初始化的变量,或者来自用户输入的未经过滤的变量。
compact
虽然没有那么糟糕,但如果使用不当,仍然会让你更难看出一个数组成员是从哪个变量设置的。
在许多其他编程语言中,extract()
的类似功能是with
关键字。Python现在也有with
关键字,虽然它的工作方式有点不同,所以和extract()
不完全一样。不过在其他语言,比如JavaScript,with
关键字的名声也不太好。
我认为最好的建议是换个思路——与其试图模仿PHP的这个糟糕特性,不如想想其他方法,用简洁易读的代码实现你想要的功能。
我觉得在Python里没有类似的东西。虽然你可以通过使用并传递locals
来模拟它们的效果:
>>> def compact(locals, *keys):
... return dict((k, locals[k]) for k in keys)
...
>>> a = 10
>>> b = 2
>>> compact(locals(), 'a', 'b')
{'a': 10, 'b': 2}
>>> def extract(locals, d):
... for k, v in d.items():
... locals[k] = v
...
>>> extract(locals(), {'a': 'foo', 'b': 'bar'}
>>> a
'foo'
>>> b
'bar'
不过,我认为这些函数并不是“特别好用”。动态的全局或局部变量其实是有问题的,容易出错——PHP的开发者们在不鼓励使用register_globals时就已经意识到了这一点。根据我的经验,很少有经验丰富的PHP程序员或大型框架会使用compact()
或extract()
。
在Python中,明确的比隐含的要好:
a = 1
b = 2
# compact
c = dict(a=a, b=b)
# extract
a, b = d['a'], d['b']
这段代码虽然不是很符合Python的风格,但如果你真的需要的话,可以这样实现compact()
:
import inspect
def compact(*names):
caller = inspect.stack()[1][0] # caller of compact()
vars = {}
for n in names:
if n in caller.f_locals:
vars[n] = caller.f_locals[n]
elif n in caller.f_globals:
vars[n] = caller.f_globals[n]
return vars
以前可以这样实现extract()
,但在现代的Python解释器中,这种方法似乎不再有效(其实它本来就不应该工作,只是2009年的实现有些奇怪,让你可以这样做):
def extract(vars):
caller = inspect.stack()[1][0] # caller of extract()
for n, v in vars.items():
caller.f_locals[n] = v # NEVER DO THIS - not guaranteed to work
如果你真的觉得需要使用这些函数,那你可能是在做一些不太对的事情。这似乎和Python的哲学相悖,至少有三点:“显式优于隐式”,“简单优于复杂”,“如果实现很难解释,那就是个坏主意”,可能还有更多(其实,如果你在Python方面有足够的经验,你会知道这样的做法是不被推荐的)。我能理解在调试或事后分析中可能会用到这些,或者在某种非常通用的框架中,频繁需要创建动态命名和赋值的变量,但这确实有点牵强。