views:

697

answers:

5

compact() and extract() are functions in PHP I find tremendously handy. compact() takes a list of names in the symbol table and creates a hashtable with just their values. extract does the opposite. e.g.,

$foo = 'what';
$bar = 'ever';
$a = compact('foo', 'bar');
$a['foo']
# what
$a['baz'] = 'another'
extract(a)
$baz
# another

Is there a way to do the same in Python? I've looked all around and the closest I've come is this thread, which seems to frown on it.

I know about locals(), globals() and vars(), but how can I handily select just a subset of their values?

Does Python have something even better that obviates the need for this?

+2  A: 

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}
Adam
+5  A: 

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']
Ferdinand Beyer
I'm all for explicit, but why must it be redundant?wouldn't this be less error-prone?c = compact(a, b)
Turadg
Don't modify locals()! http://docs.python.org/library/functions.html#locals
gnibbler
+3  A: 

It's not very Pythonic, but if you really must:

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

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 ;-)

You could put that in a module if you want and call the functions. But seriously, if you really feel you have a need to use these functions, you're probably doing something the wrong way - this is hard-cord Python voodoo ;-) I've done a lot of Python programming and I've never once run into a situation where these functions would be useful (well, maybe for debugging/post-mortem analysis, but nothing normal).

David Zaslavsky
So actually I lied ;-) I just realized that in a project I'm currently working on, I've been doing basically exactly what these functions accomplish, but I've been manually building the dictionary every time. So I guess I have run into a situation where these functions are useful... but for what it's worth, I always thought it was not the best way to be coding (I just didn't have time to do it properly).
David Zaslavsky
+1 for actually answering the question in a way that, I think, works.
intuited
Thanks... I've actually been using this implementation of `compact` a fair amount since I posted it here, and it always seems to work fine.
David Zaslavsky
+5  A: 

Is it worth pointing out that extract() and compact() are some of the most evil features of PHP (along with register_globals), and should be avoided?

extract, especially, 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 it 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" statement. Python now has a with statement, though it works a bit differently, making it not quite like extract(). However, in other languages such as JavaScript, the "with" statement also has a poor reputation.

thomasrutter
Please note that the 'with' statement in Python has completely different semantics than in JavaScript and does not change any "variable scope". Also it is a pretty new feature and certainly has no poor reputation. You are right with the JavaScript-"with", though.
Ferdinand Beyer
Thanks Ferdinand, I've edited the answer a bit
thomasrutter
+1  A: 

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.