I'm not sure where I ran across this, but until recently I wasn't aware that you could pre-explode lists and tuples when you declared what parameters a function takes. That sounds stilted, so I'll give an example:
def foo(a, (b, c, d)): print a, b, c, d
This is slightly more compact than the equivalent:
def bar(a, tmp): b, c, d = tmp print a, b, c, d
Contrary to what it might first look like,
foo
's second argument doesn't have to be an explicit list or tuple with three elements. Instead, it can be anything that will yield exactly three values when iterated. Want to pass
xrange(0, 3)
, or even
{1: 10, 2: 20, 3: 30}
? No problem.
(Iterating a dictionary returns the keys (in some random order, so this is not necessarily a useful example).)
This freedom comes with a small drawback. Compare:
>>> foo(10)
TypeError: foo() takes exactly 2 arguments (1 given)
>>> foo(10, (1, 2))
ValueError: unpack tuple of wrong size
(I have elided the tracebacks.)
This is different from a language like Haskell , which has a more pattern-matching approach to this; I believe in Haskell you would get an 'argument mismatch' type of error in both cases. (Haskell looks like the kind of mind-bending neat language that's worth reading about, even if you never write a Haskell program.)
Writing this made me curious enough to look at the actual bytecodes generated for
foo
and
bar
(with the dis module ). It turns out that they're pretty much identical, because
foo
starts with a prequel bit to explicitly unpack an explicit but internal second argument into
b
,
c
, and
d
. So
foo
is pretty much just syntactic sugar, supported by the bytecode compiler, for what
bar
writes out explicitly.
Although I've used this in code recently, I'm not sure it's entirely a good idea. Sometimes I feel that Python is a little bit too clever for my own good, and that I would be better off if I could resist the temptation to use some of these neat features.
(PS: yes, you can nest pre-exploded parameters. Don't go there.)