Python中类似于PHP的compact()和extract()函数的等效实现

13

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是否有更好的东西来避免这种需要?


4
好的,我明白让您感到困惑的原因了。看起来您第三行应该是“$a = compact('foo', 'bar');”,而不是之前的内容。 - pantsgolem
糟糕,谢谢。我刚刚纠正了它。 - Turadg
我可以问一下你为什么觉得它们很方便吗?我不明白它们有什么好处,这些好处不能通过使用哈希表更清晰地实现。 - Paul Biggar
哈希表在语义上可能更加简洁,而且可以说是更少出错的,但我发现以上代码更易于阅读和编写。感谢所有的回答! - Turadg
这似乎在运行测试时对Python有用,因为除非我搞错了,否则无法向timeit.Timer提供上下文字典。例如 context = {c.a:1, c.b:2}; timeit.Timer('c.a+c.b', 'import __main__.context as c') 如果可以将 ab 提取到测试上下文中,那将变得更加简单明了。 - intuited
6个回答

13

虽然不太符合Pythonic的风格,但如果你真的必须这样做,可以像这样实现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中有足够的经验,你会知道这样的东西根本不应该出现)。我可以看到它对于调试器或死后分析非常有用,或者对于某种非常通用的框架,需要频繁地创建具有动态选择的名称和值的变量,但这是一种牵强附会的做法。

我有一个日志记录的用例,有时我需要收集x个变量并使用紧凑格式进行记录,而不是重复自己做 {a:a, b:b, c:c}。我记得有一种语言可以说 {a, b, c} 并自动创建带有值的字典,但我记不起来是哪种语言了。 - Luis Mauricio

10

很遗憾,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']

1
我非常支持明确性,但为什么必须是冗余的呢?这样不是更少出错吗?c = compact(a, b) - Turadg

9

值得指出的是,extract()(以及在较小程度上,compact())是PHP中最“邪恶”的功能之一(连同register_globalseval),应该避免使用?

extract使得追踪变量定义的位置变得更加困难。当很难将变量追溯到其定义位置时,检查常见的安全问题就会变得更加困难,例如使用未初始化的变量或来自用户输入的未经过滤的变量。

compact并不像extract那么糟糕,但如果使用不当,仍然会使从变量设置数组成员的位置变得比本来更加困难。

许多其他语言中与extract()等效的关键字是with。Python现在也有with关键字,但它的工作方式有些不同,因此它并不完全像extract()。但是,在其他语言(如Javascript)中,with关键字也有较差的声誉
我认为最好的建议是要有不同的思路——而不是试图模仿PHP的糟糕特性,要想其他使用简洁易读的代码实现你想要的功能。

1
请注意,Python中的“with”语句与JavaScript完全不同,并且不会改变任何“变量作用域”。此外,它是一个相当新的功能,肯定没有不良声誉。不过,你对JavaScript中的“with”是正确的。 - Ferdinand Beyer
我编辑了它,直到它不再有意义,因为它无法适应一个评论,但其中所有内容都很重要。我之后没有回滚它,因为它已被转换为注释。我删除它是因为我觉得它没有回答问题,因为正如你在评论的第一句中所写的那样,你只是指出了一些东西。 - Ry-
有8个赞成票和没有反对票,看起来确实有些人认为它应该在这里? - thomasrutter
有时候,对于一个问题来说,最好的答案并不是直接回答这个问题,而是指出整个问题前提中存在的问题。这就是我为什么会把这个答案放在这里的理由。 - thomasrutter
使用“compact”作为一种非常紧凑的方式来组装字典,以便从特定的控制器/操作(MVC)将其传递到视图中,这是一种非常简单的方法,并且在某些领先的PHP框架(Symfony)中是一种常见做法,因此在适当的情况下使用它并没有什么问题。 - Zippp
@Zippp 它肯定不像extract那么糟糕,所以也许它不属于同一类别。它唯一的缺点是可能会使得从变量设置数组成员的位置更难看到,尽管代码可读性始终取决于您编码风格的其他方面。我愿意稍微缓和对compact()的看法。 - thomasrutter

3

Python中实现PHP的compact函数(适用于2.6版本;不能保证在早期版本的Python中正常工作):

import inspect
def compact(*args):
    return dict([(i, inspect.currentframe().f_back.f_locals.get(i, None)) 
                  for i in args])

我已经更详细地写过这个问题:Python 可以像 PHP 一样丑陋


2

我想相当于extract($x)的是globals().update(x),对于compact()来说,它是vars()的子集。

>>> foo, bar, baz = 1, 2, 3
# extract
>>> globals().update({"foo": 4, "qux": 5})
>>> foo
4
>>> qux
5
# compact
>>> d = dict((k, v) for k, v in vars().iteritems() if k in ["foo", "bar"])
>>> d
{'bar': 2, 'foo': 1}

0

可以使用locals()函数来实现这个功能(尽管我建议只在必要时使用...),该函数返回一个可更新的dict。例如:

$ python
Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
>>> locals().update({'derek':'anderson'})
>>> derek
'anderson'
>>> 

所以locals()就是“压缩所有”,而locals().update()则是提取

祝你好运!


文档清楚地说明:“不应修改此字典的内容”。文档 - martineau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接