Python equivalent of PHP's compact() and extract()

It's not very Pythonic, but if you really must, you can implement compact() like this:

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

It used to be possible to implement extract() like this, but in modern Python interpreters this doesn't appear to work anymore (not that it was ever "supposed" to work, really, but there were quirks of the implementation in 2009 that let you get away with it):

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

If you really feel you have a need to use these functions, you're probably doing something the wrong way. It seems to run against Python's philosophy on at least three counts: "explicit is better than implicit", "simple is better than complex", "if the implementation is hard to explain, it's a bad idea", maybe more (and really, if you have enough experience in Python you know that stuff like this just isn't done). I could see it being useful for a debugger or post-mortem analysis, or perhaps for some sort of very general framework that frequently needs to create variables with dynamically chosen names and values, but it's a stretch.


I'm afraid there are no equivalents in Python. To some extent, you can simulate their effect using (and passing) 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'

Nevertheless, I don't think these functions are "tremendously handy". Dynamic global/local variables are evil and error-prone -- PHP guys learned that when they discouraged register_globals. From my experience, few experienced PHP programmers or major frameworks use compact() or extract().

In Python, explicit is better than implicit:

a = 1
b = 2
# compact
c = dict(a=a, b=b)

# extract
a, b = d['a'], d['b']

Is it worth pointing out that extract() (and to a lesser extent, compact()) is one of the most "evil" features of PHP (along with register_globals and eval), and should be avoided?

extract makes it much harder to determine where a variable was defined. When it is harder to trace a variable back to where it was defined, it's harder to check for common security problems like using uninitialized variables, or unfiltered variables which originated from user input.

compact is not as bad, but if used badly can still make it more difficult than it would otherwise be to see where an array member gets set from a variable.

The equivalent of extract() in many other languages is the with keyword. Python now has a with keyword, though it works a bit differently, making it not quite like extract(). However, in other languages such as Javascript, the with keyword also has a poor reputation.

I think the best advice would be to think differently - instead of trying to emulate a bad feature of PHP, think of other ways to do what you want to do with concise and readable code.


PHP's compact function in Python (works with 2.6; not guaranteed to work with earlier versions of Python):

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

I've written more extensively about this: Python can be just as ugly as PHP.


I guess the equivalent of extract($x) is globals().update(x), as for compact() it's a subset of 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}