Hidden features of Python
PythonHidden FeaturesPython Problem Overview
What are the lesser-known but useful features of the Python programming language?
- Try to limit answers to Python core.
- One feature per answer.
- Give an example and short description of the feature, not just a link to documentation.
- Label the feature using a title as the first line.
Quick links to answers:
- Argument Unpacking
- Braces
- Chaining Comparison Operators
- Decorators
- Default Argument Gotchas / Dangers of Mutable Default arguments
- Descriptors
- Dictionary default
.get
value - Docstring Tests
- Ellipsis Slicing Syntax
- Enumeration
- For/else
- Function as iter() argument
- Generator expressions
import this
- In Place Value Swapping
- List stepping
__missing__
items- Multi-line Regex
- Named string formatting
- Nested list/generator comprehensions
- New types at runtime
.pth
files- ROT13 Encoding
- Regex Debugging
- Sending to Generators
- Tab Completion in Interactive Interpreter
- Ternary Expression
try/except/else
- Unpacking+
print()
function with
statement
Python Solutions
Solution 1 - Python
Chaining comparison operators:
>>> x = 5
>>> 1 < x < 10
True
>>> 10 < x < 20
False
>>> x < 10 < x*10 < 100
True
>>> 10 > x <= 9
True
>>> 5 == x > 4
True
In case you're thinking it's doing 1 < x
, which comes out as True
, and then comparing True < 10
, which is also True
, then no, that's really not what happens (see the last example.) It's really translating into 1 < x and x < 10
, and x < 10 and 10 < x * 10 and x*10 < 100
, but with less typing and each term is only evaluated once.
Solution 2 - Python
Get the python regex parse tree to debug your regex.
Regular expressions are a great feature of python, but debugging them can be a pain, and it's all too easy to get a regex wrong.
Fortunately, python can print the regex parse tree, by passing the undocumented, experimental, hidden flag re.DEBUG
(actually, 128) to re.compile
.
>>> re.compile("^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]",
re.DEBUG)
at at_beginning
literal 91
literal 102
literal 111
literal 110
literal 116
max_repeat 0 1
subpattern None
literal 61
subpattern 1
in
literal 45
literal 43
max_repeat 1 2
in
range (48, 57)
literal 93
subpattern 2
min_repeat 0 65535
any None
in
literal 47
literal 102
literal 111
literal 110
literal 116
Once you understand the syntax, you can spot your errors. There we can see that I forgot to escape the []
in [/font]
.
Of course you can combine it with whatever flags you want, like commented regexes:
>>> re.compile("""
^ # start of a line
\[font # the font tag
(?:=(?P<size> # optional [font=+size]
[-+][0-9]{1,2} # size specification
))?
\] # end of tag
(.*?) # text between the tags
\[/font\] # end of the tag
""", re.DEBUG|re.VERBOSE|re.DOTALL)
Solution 3 - Python
enumerate
Wrap an iterable with enumerate and it will yield the item along with its index.
For example:
>>> a = ['a', 'b', 'c', 'd', 'e']
>>> for index, item in enumerate(a): print index, item
...
0 a
1 b
2 c
3 d
4 e
>>>
References:
Solution 4 - Python
Creating generators objects
If you write
x=(n for n in foo if bar(n))
you can get out the generator and assign it to x. Now it means you can do
for n in x:
The advantage of this is that you don't need intermediate storage, which you would need if you did
x = [n for n in foo if bar(n)]
In some cases this can lead to significant speed up.
You can append many if statements to the end of the generator, basically replicating nested for loops:
>>> n = ((a,b) for a in range(0,2) for b in range(4,6))
>>> for i in n:
... print i
(0, 4)
(0, 5)
(1, 4)
(1, 5)
Solution 5 - Python
iter() can take a callable argument
For instance:
def seek_next_line(f):
for c in iter(lambda: f.read(1),'\n'):
pass
The iter(callable, until_value)
function repeatedly calls callable
and yields its result until until_value
is returned.
Solution 6 - Python
Be careful with mutable default arguments
>>> def foo(x=[]):
... x.append(1)
... print x
...
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]
Instead, you should use a sentinel value denoting "not given" and replace with the mutable you'd like as default:
>>> def foo(x=None):
... if x is None:
... x = []
... x.append(1)
... print x
>>> foo()
[1]
>>> foo()
[1]
Solution 7 - Python
Sending values into generator functions. For example having this function:
def mygen():
"""Yield 5 until something else is passed back via send()"""
a = 5
while True:
f = (yield a) #yield a and possibly get f in return
if f is not None:
a = f #store the new value
You can:
>>> g = mygen()
>>> g.next()
5
>>> g.next()
5
>>> g.send(7) #we send this back to the generator
7
>>> g.next() #now it will yield 7 until we send something else
7
Solution 8 - Python
If you don't like using whitespace to denote scopes, you can use the C-style {} by issuing:
from __future__ import braces
Solution 9 - Python
The step argument in slice operators. For example:
a = [1,2,3,4,5]
>>> a[::2] # iterate over the whole list in 2-increments
[1,3,5]
The special case x[::-1]
is a useful idiom for 'x reversed'.
>>> a[::-1]
[5,4,3,2,1]
Solution 10 - Python
Decorators
Decorators allow to wrap a function or method in another function that can add functionality, modify arguments or results, etc. You write decorators one line above the function definition, beginning with an "at" sign (@).
Example shows a print_args
decorator that prints the decorated function's arguments before calling it:
>>> def print_args(function):
>>> def wrapper(*args, **kwargs):
>>> print 'Arguments:', args, kwargs
>>> return function(*args, **kwargs)
>>> return wrapper
>>> @print_args
>>> def write(text):
>>> print text
>>> write('foo')
Arguments: ('foo',) {}
foo
Solution 11 - Python
The for...else syntax (see http://docs.python.org/ref/for.html )
for i in foo:
if i == 0:
break
else:
print("i was never 0")
The "else" block will be normally executed at the end of the for loop, unless the break is called.
The above code could be emulated as follows:
found = False
for i in foo:
if i == 0:
found = True
break
if not found:
print("i was never 0")
Solution 12 - Python
From 2.5 onwards dicts have a special method __missing__
that is invoked for missing items:
>>> class MyDict(dict):
... def __missing__(self, key):
... self[key] = rv = []
... return rv
...
>>> m = MyDict()
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [1, 2]}
There is also a dict subclass in collections
called defaultdict
that does pretty much the same but calls a function without arguments for not existing items:
>>> from collections import defaultdict
>>> m = defaultdict(list)
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [1, 2]}
I recommend converting such dicts to regular dicts before passing them to functions that don't expect such subclasses. A lot of code uses d[a_key]
and catches KeyErrors to check if an item exists which would add a new item to the dict.
Solution 13 - Python
In-place value swapping
>>> a = 10
>>> b = 5
>>> a, b
(10, 5)
>>> a, b = b, a
>>> a, b
(5, 10)
The right-hand side of the assignment is an expression that creates a new tuple. The left-hand side of the assignment immediately unpacks that (unreferenced) tuple to the names a
and b
.
After the assignment, the new tuple is unreferenced and marked for garbage collection, and the values bound to a
and b
have been swapped.
As noted in the Python tutorial section on data structures,
> Note that multiple assignment is really just a combination of tuple packing and sequence unpacking.
Solution 14 - Python
Readable regular expressions
In Python you can split a regular expression over multiple lines, name your matches and insert comments.
Example verbose syntax (from Dive into Python):
>>> pattern = """
... ^ # beginning of string
... M{0,4} # thousands - 0 to 4 M's
... (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
... # or 500-800 (D, followed by 0 to 3 C's)
... (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
... # or 50-80 (L, followed by 0 to 3 X's)
... (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
... # or 5-8 (V, followed by 0 to 3 I's)
... $ # end of string
... """
>>> re.search(pattern, 'M', re.VERBOSE)
Example naming matches (from Regular Expression HOWTO)
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
You can also verbosely write a regex without using re.VERBOSE
thanks to string literal concatenation.
>>> pattern = (
... "^" # beginning of string
... "M{0,4}" # thousands - 0 to 4 M's
... "(CM|CD|D?C{0,3})" # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
... # or 500-800 (D, followed by 0 to 3 C's)
... "(XC|XL|L?X{0,3})" # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
... # or 50-80 (L, followed by 0 to 3 X's)
... "(IX|IV|V?I{0,3})" # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
... # or 5-8 (V, followed by 0 to 3 I's)
... "$" # end of string
... )
>>> print pattern
"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"
Solution 15 - Python
Function argument unpacking
You can unpack a list or a dictionary as function arguments using *
and **
.
For example:
def draw_point(x, y):
# do some magic
point_foo = (3, 4)
point_bar = {'y': 3, 'x': 2}
draw_point(*point_foo)
draw_point(**point_bar)
Very useful shortcut since lists, tuples and dicts are widely used as containers.
Solution 16 - Python
ROT13 is a valid encoding for source code, when you use the right coding declaration at the top of the code file:
#!/usr/bin/env python
# -*- coding: rot13 -*-
cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13")
Solution 17 - Python
Creating new types in a fully dynamic manner
>>> NewType = type("NewType", (object,), {"x": "hello"})
>>> n = NewType()
>>> n.x
"hello"
which is exactly the same as
>>> class NewType(object):
>>> x = "hello"
>>> n = NewType()
>>> n.x
"hello"
Probably not the most useful thing, but nice to know.
Edit: Fixed name of new type, should be NewType
to be the exact same thing as with class
statement.
Edit: Adjusted the title to more accurately describe the feature.
Solution 18 - Python
Context managers and the "with
" Statement
Introduced in PEP 343, a context manager is an object that acts as a run-time context for a suite of statements.
Since the feature makes use of new keywords, it is introduced gradually: it is available in Python 2.5 via the __future__
directive. Python 2.6 and above (including Python 3) has it available by default.
I have used the "with" statement a lot because I think it's a very useful construct, here is a quick demo:
from __future__ import with_statement
with open('foo.txt', 'w') as f:
f.write('hello!')
What's happening here behind the scenes, is that the "with" statement calls the special __enter__
and __exit__
methods on the file object. Exception details are also passed to __exit__
if any exception was raised from the with statement body, allowing for exception handling to happen there.
What this does for you in this particular case is that it guarantees that the file is closed when execution falls out of scope of the with
suite, regardless if that occurs normally or whether an exception was thrown. It is basically a way of abstracting away common exception-handling code.
Other common use cases for this include locking with threads and database transactions.
Solution 19 - Python
Dictionaries have a get() method
Dictionaries have a 'get()' method. If you do d['key'] and key isn't there, you get an exception. If you do d.get('key'), you get back None if 'key' isn't there. You can add a second argument to get that item back instead of None, eg: d.get('key', 0).
It's great for things like adding up numbers:
sum[value] = sum.get(value, 0) + 1
Solution 20 - Python
Descriptors
They're the magic behind a whole bunch of core Python features.
When you use dotted access to look up a member (eg, x.y), Python first looks for the member in the instance dictionary. If it's not found, it looks for it in the class dictionary. If it finds it in the class dictionary, and the object implements the descriptor protocol, instead of just returning it, Python executes it. A descriptor is any class that implements the __get__
, __set__
, or __delete__
methods.
Here's how you'd implement your own (read-only) version of property using descriptors:
class Property(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, type):
if obj is None:
return self
return self.fget(obj)
and you'd use it just like the built-in property():
class MyClass(object):
@Property
def foo(self):
return "Foo!"
Descriptors are used in Python to implement properties, bound methods, static methods, class methods and slots, amongst other things. Understanding them makes it easy to see why a lot of things that previously looked like Python 'quirks' are the way they are.
Raymond Hettinger has an excellent tutorial that does a much better job of describing them than I do.
Solution 21 - Python
Conditional Assignment
x = 3 if (y == 1) else 2
It does exactly what it sounds like: "assign 3 to x if y is 1, otherwise assign 2 to x". Note that the parens are not necessary, but I like them for readability. You can also chain it if you have something more complicated:
x = 3 if (y == 1) else 2 if (y == -1) else 1
Though at a certain point, it goes a little too far.
Note that you can use if ... else in any expression. For example:
(func1 if y == 1 else func2)(arg1, arg2)
Here func1 will be called if y is 1 and func2, otherwise. In both cases the corresponding function will be called with arguments arg1 and arg2.
Analogously, the following is also valid:
x = (class1 if y == 1 else class2)(arg1, arg2)
where class1 and class2 are two classes.
Solution 22 - Python
Doctest: documentation and unit-testing at the same time.
Example extracted from the Python documentation:
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
If the result is small enough to fit in an int, return an int.
Else return a long.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
def _test():
import doctest
doctest.testmod()
if __name__ == "__main__":
_test()
Solution 23 - Python
Named formatting
% -formatting takes a dictionary (also applies %i/%s etc. validation).
>>> print "The %(foo)s is %(bar)i." % {'foo': 'answer', 'bar':42}
The answer is 42.
>>> foo, bar = 'question', 123
>>> print "The %(foo)s is %(bar)i." % locals()
The question is 123.
And since locals() is also a dictionary, you can simply pass that as a dict and have % -substitions from your local variables. I think this is frowned upon, but simplifies things..
New Style Formatting
>>> print("The {foo} is {bar}".format(foo='answer', bar=42))
Solution 24 - Python
To add more python modules (espcially 3rd party ones), most people seem to use PYTHONPATH environment variables or they add symlinks or directories in their site-packages directories. Another way, is to use *.pth files. Here's the official python doc's explanation:
> "The most convenient way [to modify > python's search path] is to add a path > configuration file to a directory > that's already on Python's path, > usually to the .../site-packages/ > directory. Path configuration files > have an extension of .pth, and each > line must contain a single path that > will be appended to sys.path. (Because > the new paths are appended to > sys.path, modules in the added > directories will not override standard > modules. This means you can't use this > mechanism for installing fixed > versions of standard modules.)"
Solution 25 - Python
Exception else clause:
try:
put_4000000000_volts_through_it(parrot)
except Voom:
print "'E's pining!"
else:
print "This parrot is no more!"
finally:
end_sketch()
The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try ... except statement.
Solution 26 - Python
Re-raising exceptions:
# Python 2 syntax
try:
some_operation()
except SomeError, e:
if is_fatal(e):
raise
handle_nonfatal(e)
# Python 3 syntax
try:
some_operation()
except SomeError as e:
if is_fatal(e):
raise
handle_nonfatal(e)
The 'raise' statement with no arguments inside an error handler tells Python to re-raise the exception with the original traceback intact, allowing you to say "oh, sorry, sorry, I didn't mean to catch that, sorry, sorry."
If you wish to print, store or fiddle with the original traceback, you can get it with sys.exc_info(), and printing it like Python would is done with the 'traceback' module.
Solution 27 - Python
Main messages :)
import this
# btw look at this module's source :)
> The Zen of Python, by Tim Peters
>
> Beautiful is better than ugly.
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
> Flat is better than nested.
> Sparse is better than dense.
> Readability counts.
> Special cases aren't special enough to break the rules.
> Although practicality beats purity.
> Errors should never pass silently.
> Unless explicitly silenced.
> In the face of ambiguity, refuse the temptation to guess.
> There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch.
> Now is better than never.
> Although never is often better than right now.
> If the implementation is hard to explain, it's a bad idea.
> If the implementation is easy to explain, it may be a good idea.
> Namespaces are one honking great idea -- let's do more of those!
Solution 28 - Python
Interactive Interpreter Tab Completion
try:
import readline
except ImportError:
print "Unable to load readline module."
else:
import rlcompleter
readline.parse_and_bind("tab: complete")
>>> class myclass:
... def function(self):
... print "my function"
...
>>> class_instance = myclass()
>>> class_instance.<TAB>
class_instance.__class__ class_instance.__module__
class_instance.__doc__ class_instance.function
>>> class_instance.f<TAB>unction()
You will also have to set a PYTHONSTARTUP environment variable.
Solution 29 - Python
Nested list comprehensions and generator expressions:
[(i,j) for i in range(3) for j in range(i) ]
((i,j) for i in range(4) for j in range(i) )
These can replace huge chunks of nested-loop code.
Solution 30 - Python
Operator overloading for the set
builtin:
>>> a = set([1,2,3,4])
>>> b = set([3,4,5,6])
>>> a | b # Union
{1, 2, 3, 4, 5, 6}
>>> a & b # Intersection
{3, 4}
>>> a < b # Subset
False
>>> a - b # Difference
{1, 2}
>>> a ^ b # Symmetric Difference
{1, 2, 5, 6}
More detail from the standard library reference: Set Types
Solution 31 - Python
Negative round
The round()
function rounds a float number to given precision in decimal digits, but precision can be negative:
>>> str(round(1234.5678, -2))
'1200.0'
>>> str(round(1234.5678, 2))
'1234.57'
Note: round()
always returns a float, str()
used in the above example because floating point math is inexact, and under 2.x the second example can print as 1234.5700000000001
. Also see the decimal
module.
Solution 32 - Python
Multiplying by a boolean
One thing I'm constantly doing in web development is optionally printing HTML parameters. We've all seen code like this in other languages:
class='<% isSelected ? "selected" : "" %>'
In Python, you can multiply by a boolean and it does exactly what you'd expect:
class='<% "selected" * isSelected %>'
This is because multiplication coerces the boolean to an integer (0 for False, 1 for True), and in python multiplying a string by an int repeats the string N times.
Solution 33 - Python
Python's advanced slicing operation has a barely known syntax element, the ellipsis:
>>> class C(object):
... def __getitem__(self, item):
... return item
...
>>> C()[1:2, ..., 3]
(slice(1, 2, None), Ellipsis, 3)
Unfortunately it's barely useful as the ellipsis is only supported if tuples are involved.
Solution 34 - Python
re can call functions!
The fact that you can call a function every time something matches a regular expression is very handy. Here I have a sample of replacing every "Hello" with "Hi," and "there" with "Fred", etc.
import re
def Main(haystack):
# List of from replacements, can be a regex
finds = ('Hello', 'there', 'Bob')
replaces = ('Hi,', 'Fred,', 'how are you?')
def ReplaceFunction(matchobj):
for found, rep in zip(matchobj.groups(), replaces):
if found != None:
return rep
# log error
return matchobj.group(0)
named_groups = [ '(%s)' % find for find in finds ]
ret = re.sub('|'.join(named_groups), ReplaceFunction, haystack)
print ret
if __name__ == '__main__':
str = 'Hello there Bob'
Main(str)
# Prints 'Hi, Fred, how are you?'
Solution 35 - Python
tuple unpacking in python 3
in python 3, you can use a syntax identical to optional arguments in function definition for tuple unpacking:
>>> first,second,*rest = (1,2,3,4,5,6,7,8)
>>> first
1
>>> second
2
>>> rest
[3, 4, 5, 6, 7, 8]
but a feature less known and more powerful allows you to have an unknown number of elements in the middle of the list:
>>> first,*rest,last = (1,2,3,4,5,6,7,8)
>>> first
1
>>> rest
[2, 3, 4, 5, 6, 7]
>>> last
8
Solution 36 - Python
Multi line strings
One approach is to use backslashes:
>>> sql = "select * from some_table \
where id > 10"
>>> print sql
select * from some_table where id > 10
Another is to use the triple-quote:
>>> sql = """select * from some_table
where id > 10"""
>>> print sql
select * from some_table where id > 10
Problem with those is that they are not indented (look poor in your code). If you try to indent, it'll just print the white-spaces you put.
A third solution, which I found about recently, is to divide your string into lines and surround with parentheses:
>>> sql = ("select * from some_table " # <-- no comma, whitespace at end
"where id > 10 "
"order by name")
>>> print sql
select * from some_table where id > 10 order by name
note how there's no comma between lines (this is not a tuple), and you have to account for any trailing/leading white spaces that your string needs to have. All of these work with placeholders, by the way (such as "my name is %s" % name
).
Solution 37 - Python
This answer has been moved into the question itself, as requested by many people.
Solution 38 - Python
-
The underscore, it contains the most recent output value displayed by the interpreter (in an interactive session):
>>> (a for a in xrange(10000)) <generator object at 0x81a8fcc> >>> b = 'blah' >>> _ <generator object at 0x81a8fcc>
-
A convenient Web-browser controller:
>>> import webbrowser >>> webbrowser.open_new_tab('http://www.stackoverflow.com')</pre>
-
A built-in http server. To serve the files in the current directory:
python -m SimpleHTTPServer 8000
-
AtExit
>>> import atexit
Solution 39 - Python
pow() can also calculate (x ** y) % z efficiently.
There is a lesser known third argument of the built-in pow()
function that allows you to calculate xy modulo z more efficiently than simply doing (x ** y) % z
:
>>> x, y, z = 1234567890, 2345678901, 17
>>> pow(x, y, z) # almost instantaneous
6
In comparison, (x ** y) % z
didn't given a result in one minute on my machine for the same values.
Solution 40 - Python
You can easily transpose an array with zip.
a = [(1,2), (3,4), (5,6)]
zip(*a)
# [(1, 3, 5), (2, 4, 6)]
Solution 41 - Python
enumerate with different starting index
enumerate
has partly been covered in this answer, but recently I've found an even more hidden feature of enumerate
that I think deserves its own post instead of just a comment.
Since Python 2.6, you can specify a starting index to enumerate
in its second argument:
>>> l = ["spam", "ham", "eggs"]
>>> list(enumerate(l))
>>> [(0, "spam"), (1, "ham"), (2, "eggs")]
>>> list(enumerate(l, 1))
>>> [(1, "spam"), (2, "ham"), (3, "eggs")]
One place where I've found it utterly useful is when I am enumerating over entries of a symmetric matrix. Since the matrix is symmetric, I can save time by iterating over the upper triangle only, but in that case, I have to use enumerate
with a different starting index in the inner for
loop to keep track of the row and column indices properly:
for ri, row in enumerate(matrix):
for ci, column in enumerate(matrix[ri:], ri):
# ci now refers to the proper column index
Strangely enough, this behaviour of enumerate
is not documented in help(enumerate)
, only in the online documentation.
Solution 42 - Python
You can use property to make your class interfaces more strict.
class C(object):
def __init__(self, foo, bar):
self.foo = foo # read-write property
self.bar = bar # simple attribute
def _set_foo(self, value):
self._foo = value
def _get_foo(self):
return self._foo
def _del_foo(self):
del self._foo
# any of fget, fset, fdel and doc are optional,
# so you can make a write-only and/or delete-only property.
foo = property(fget = _get_foo, fset = _set_foo,
fdel = _del_foo, doc = 'Hello, I am foo!')
class D(C):
def _get_foo(self):
return self._foo * 2
def _set_foo(self, value):
self._foo = value / 2
foo = property(fget = _get_foo, fset = _set_foo,
fdel = C.foo.fdel, doc = C.foo.__doc__)
In Python 2.6 and 3.0:
class C(object):
def __init__(self, foo, bar):
self.foo = foo # read-write property
self.bar = bar # simple attribute
@property
def foo(self):
'''Hello, I am foo!'''
return self._foo
@foo.setter
def foo(self, value):
self._foo = value
@foo.deleter
def foo(self):
del self._foo
class D(C):
@C.foo.getter
def foo(self):
return self._foo * 2
@foo.setter
def foo(self, value):
self._foo = value / 2
To learn more about how property works refer to descriptors.
Solution 43 - Python
Many people don't know about the "dir" function. It's a great way to figure out what an object can do from the interpreter. For example, if you want to see a list of all the string methods:
>>> dir("foo")
['__add__', '__class__', '__contains__', (snipped a bunch), 'title',
'translate', 'upper', 'zfill']
And then if you want more information about a particular method you can call "help" on it.
>>> help("foo".upper)
Help on built-in function upper:
upper(...)
S.upper() -> string
Return a copy of the string S converted to uppercase.
Solution 44 - Python
Probably an easily overlooked python builtin is "set/frozenset".
Useful when you have a list like this, [1,2,1,1,2,3,4] and only want the uniques like this [1,2,3,4].
Using set() that's exactly what you get:
>>> x = [1,2,1,1,2,3,4]
>>>
>>> set(x)
set([1, 2, 3, 4])
>>>
>>> for i in set(x):
... print i
...
1
2
3
4
And of course to get the number of uniques in a list:
>>> len(set([1,2,1,1,2,3,4]))
4
You can also find if a list is a subset of another list using set().issubset():
>>> set([1,2,3,4]).issubset([0,1,2,3,4,5])
True
As of Python 2.7 and 3.0 you can use curly braces to create a set:
myset = {1,2,3,4}
as well as set comprehensions:
{x for x in stuff}
For more details: http://docs.python.org/library/stdtypes.html#set
Solution 45 - Python
Built-in base64, zlib, and rot13 codecs
Strings have encode
and decode
methods. Usually this is used for converting str
to unicode
and vice versa, e.g. with u = s.encode('utf8')
. But there are some other handy builtin codecs. Compression and decompression with zlib (and bz2) is available without an explicit import:
>>> s = 'a' * 100
>>> s.encode('zlib')
'x\x9cKL\xa4=\x00\x00zG%\xe5'
Similarly you can encode and decode base64:
>>> 'Hello world'.encode('base64')
'SGVsbG8gd29ybGQ=\n'
>>> 'SGVsbG8gd29ybGQ=\n'.decode('base64')
'Hello world'
And, of course, you can rot13:
>>> 'Secret message'.encode('rot13')
'Frperg zrffntr'
Solution 46 - Python
An interpreter within the interpreter
The standard library's code module let's you include your own read-eval-print loop inside a program, or run a whole nested interpreter. E.g. (copied my example from here)
$ python
Python 2.5.1 (r251:54863, Jan 17 2008, 19:35:17)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> shared_var = "Set in main console"
>>> import code
>>> ic = code.InteractiveConsole({ 'shared_var': shared_var })
>>> try:
... ic.interact("My custom console banner!")
... except SystemExit, e:
... print "Got SystemExit!"
...
My custom console banner!
>>> shared_var
'Set in main console'
>>> shared_var = "Set in sub-console"
>>> import sys
>>> sys.exit()
Got SystemExit!
>>> shared_var
'Set in main console'
This is extremely useful for situations where you want to accept scripted input from the user, or query the state of the VM in real-time.
TurboGears uses this to great effect by having a WebConsole from which you can query the state of you live web app.
Solution 47 - Python
>>> from functools import partial
>>> bound_func = partial(range, 0, 10)
>>> bound_func()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> bound_func(2)
[0, 2, 4, 6, 8]
not really a hidden feature but partial is extremely useful for having late evaluation of functions.
you can bind as many or as few parameters in the initial call to partial as you want, and call it with any remaining parameters later (in this example i've bound the begin/end args to range, but call it the second time with a step arg)
See the documentation.
Solution 48 - Python
While debugging complex data structures pprint module comes handy.
Quoting from the docs..
>>> import pprint
>>> stuff = sys.path[:]
>>> stuff.insert(0, stuff)
>>> pprint.pprint(stuff)
[<Recursion on list with id=869440>,
'',
'/usr/local/lib/python1.5',
'/usr/local/lib/python1.5/test',
'/usr/local/lib/python1.5/sunos5',
'/usr/local/lib/python1.5/sharedmodules',
'/usr/local/lib/python1.5/tkinter']
Solution 49 - Python
Python has GOTO
...and it's implemented by external pure-Python module :)
from goto import goto, label
for i in range(1, 10):
for j in range(1, 20):
for k in range(1, 30):
print i, j, k
if k == 3:
goto .end # breaking out from a deeply nested loop
label .end
print "Finished"
Solution 50 - Python
dict's constructor accepts keyword arguments:
>>> dict(foo=1, bar=2)
{'foo': 1, 'bar': 2}
Solution 51 - Python
Sequence multiplication and reflected operands
>>> 'xyz' * 3
'xyzxyzxyz'
>>> [1, 2] * 3
[1, 2, 1, 2, 1, 2]
>>> (1, 2) * 3
(1, 2, 1, 2, 1, 2)
We get the same result with reflected (swapped) operands
>>> 3 * 'xyz'
'xyzxyzxyz'
It works like this:
>>> s = 'xyz'
>>> num = 3
To evaluate an expression s * num interpreter calls s.mul(num)
>>> s * num
'xyzxyzxyz'
>>> s.__mul__(num)
'xyzxyzxyz'
To evaluate an expression num * s interpreter calls num.mul(s)
>>> num * s
'xyzxyzxyz'
>>> num.__mul__(s)
NotImplemented
If the call returns NotImplemented then interpreter calls a reflected operation s.rmul(num) if operands have different types
>>> s.__rmul__(num)
'xyzxyzxyz'
See http://docs.python.org/reference/datamodel.html#object.rmul
Solution 52 - Python
Getter functions in module operator
The functions attrgetter()
and itemgetter()
in module operator
can be used to generate fast access functions for use in sorting and search objects and dictionaries
Chapter 6.7 in the Python Library Docs
Solution 53 - Python
Interleaving if
and for
in list comprehensions
>>> [(x, y) for x in range(4) if x % 2 == 1 for y in range(4)]
[(1, 0), (1, 1), (1, 2), (1, 3), (3, 0), (3, 1), (3, 2), (3, 3)]
I never realized this until I learned Haskell.
Solution 54 - Python
Tuple unpacking:
>>> (a, (b, c), d) = [(1, 2), (3, 4), (5, 6)]
>>> a
(1, 2)
>>> b
3
>>> c, d
(4, (5, 6))
More obscurely, you can do this in function arguments (in Python 2.x; Python 3.x will not allow this anymore):
>>> def addpoints((x1, y1), (x2, y2)):
... return (x1+x2, y1+y2)
>>> addpoints((5, 0), (3, 5))
(8, 5)
Solution 55 - Python
Obviously, the antigravity module. xkcd #353
Solution 56 - Python
The Python Interpreter
>>>
Maybe not lesser known, but certainly one of my favorite features of Python.
Solution 57 - Python
Python sort function sorts tuples correctly (i.e. using the familiar lexicographical order):
a = [(2, "b"), (1, "a"), (2, "a"), (3, "c")]
print sorted(a)
#[(1, 'a'), (2, 'a'), (2, 'b'), (3, 'c')]
Useful if you want to sort a list of persons after age and then name.
Solution 58 - Python
Referencing a list comprehension as it is being built...
You can reference a list comprehension as it is being built by the symbol '_[1]'. For example, the following function unique-ifies a list of elements without changing their order by referencing its list comprehension.
def unique(my_list):
return [x for x in my_list if x not in locals()['_[1]']]
Solution 59 - Python
The unpacking syntax has been upgraded in the recent version as can be seen in the example.
>>> a, *b = range(5)
>>> a, b
(0, [1, 2, 3, 4])
>>> *a, b = range(5)
>>> a, b
([0, 1, 2, 3], 4)
>>> a, *b, c = range(5)
>>> a, b, c
(0, [1, 2, 3], 4)
Solution 60 - Python
The simplicity of :
>>> 'str' in 'string'
True
>>> 'no' in 'yes'
False
>>>
is something i love about Python, I have seen a lot of not very pythonic idiom like that instead :
if 'yes'.find("no") == -1:
pass
Solution 61 - Python
Metaclasses
of course :-) https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
Solution 62 - Python
I personally love the 3 different quotes
str = "I'm a string 'but still I can use quotes' inside myself!"
str = """ For some messy multi line strings.
Such as
<html>
<head> ... </head>"""
Also cool: not having to escape regular expressions, avoiding horrible backslash salad by using raw strings:
str2 = r"\n"
print str2
>> \n
Solution 63 - Python
Generators
I think that a lot of beginning Python developers pass over generators without really grasping what they're for or getting any sense of their power. It wasn't until I read David M. Beazley's PyCon presentation on generators (it's available here) that I realized how useful (essential, really) they are. That presentation illuminated what was for me an entirely new way of programming, and I recommend it to anyone who doesn't have a deep understanding of generators.
Solution 64 - Python
Implicit concatenation:
>>> print "Hello " "World"
Hello World
Useful when you want to make a long text fit on several lines in a script:
hello = "Greaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Hello " \
"Word"
or
hello = ("Greaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Hello "
"Word")
Solution 65 - Python
When using the interactive shell, "_" contains the value of the last printed item:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> _
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
Solution 66 - Python
The textwrap.dedent
utility function in python can come quite in handy testing that a multiline string returned is equal to the expected output without breaking the indentation of your unittests:
import unittest, textwrap
class XMLTests(unittest.TestCase):
def test_returned_xml_value(self):
returned_xml = call_to_function_that_returns_xml()
expected_value = textwrap.dedent("""\
<?xml version="1.0" encoding="utf-8"?>
<root_node>
<my_node>my_content</my_node>
</root_node>
""")
self.assertEqual(expected_value, returned_xml)
Solution 67 - Python
Zero-argument and variable-argument lambdas
Lambda functions are usually used for a quick transformation of one value into another, but they can also be used to wrap a value in a function:
>>> f = lambda: 'foo'
>>> f()
'foo'
They can also accept the usual *args
and **kwargs
syntax:
>>> g = lambda *args, **kwargs: args[0], kwargs['thing']
>>> g(1, 2, 3, thing='stuff')
(1, 'stuff')
Solution 68 - Python
Using keyword arguments as assignments
Sometimes one wants to build a range of functions depending on one or more parameters. However this might easily lead to closures all referring to the same object and value:
funcs = []
for k in range(10):
funcs.append( lambda: k)
>>> funcs[0]()
9
>>> funcs[7]()
9
This behaviour can be avoided by turning the lambda expression into a function depending only on its arguments. A keyword parameter stores the current value that is bound to it. The function call doesn't have to be altered:
funcs = []
for k in range(10):
funcs.append( lambda k = k: k)
>>> funcs[0]()
0
>>> funcs[7]()
7
Solution 69 - Python
Mod works correctly with negative numbers
-1 % 5 is 4, as it should be, not -1 as it is in other languages like JavaScript. This makes "wraparound windows" cleaner in Python, you just do this:
index = (index + increment) % WINDOW_SIZE
Solution 70 - Python
First-class functions
It's not really a hidden feature, but the fact that functions are first class objects is simply great. You can pass them around like any other variable.
>>> def jim(phrase):
... return 'Jim says, "%s".' % phrase
>>> def say_something(person, phrase):
... print person(phrase)
>>> say_something(jim, 'hey guys')
'Jim says, "hey guys".'
Solution 71 - Python
Ternary operator
>>> 'ham' if True else 'spam'
'ham'
>>> 'ham' if False else 'spam'
'spam'
This was added in 2.5, prior to that you could use:
>>> True and 'ham' or 'spam'
'ham'
>>> False and 'ham' or 'spam'
'spam'
However, if the values you want to work with would be considered false, there is a difference:
>>> [] if True else 'spam'
[]
>>> True and [] or 'spam'
'spam'
Solution 72 - Python
Assigning and deleting slices:
>>> a = range(10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[:5] = [42]
>>> a
[42, 5, 6, 7, 8, 9]
>>> a[:1] = range(5)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del a[::2]
>>> a
[1, 3, 5, 7, 9]
>>> a[::2] = a[::-2]
>>> a
[9, 3, 5, 7, 1]
Note: when assigning to extended slices (s[start:stop:step]
), the assigned iterable must have the same length as the slice.
Solution 73 - Python
Not very hidden, but functions have attributes:
def doNothing():
pass
doNothing.monkeys = 4
print doNothing.monkeys
4
Solution 74 - Python
Passing tuple to builtin functions
Much Python functions accept tuples, also it doesn't seem like. For example you want to test if your variable is a number, you could do:
if isinstance (number, float) or isinstance (number, int):
print "yaay"
But if you pass us tuple this looks much cleaner:
if isinstance (number, (float, int)):
print "yaay"
Solution 75 - Python
Nice treatment of infinite recursion in dictionaries:
>>> a = {}
>>> b = {}
>>> a['b'] = b
>>> b['a'] = a
>>> print a
{'b': {'a': {...}}}
Solution 76 - Python
reversing an iterable using negative step
>>> s = "Hello World"
>>> s[::-1]
'dlroW olleH'
>>> a = (1,2,3,4,5,6)
>>> a[::-1]
(6, 5, 4, 3, 2, 1)
>>> a = [5,4,3,2,1]
>>> a[::-1]
[1, 2, 3, 4, 5]
Solution 77 - Python
Not "hidden" but quite useful and not commonly used
Creating string joining functions quickly like so
comma_join = ",".join
semi_join = ";".join
print comma_join(["foo","bar","baz"])
'foo,bar,baz
and
Ability to create lists of strings more elegantly than the quote, comma mess.
l = ["item1", "item2", "item3"]
replaced by
l = "item1 item2 item3".split()
Solution 78 - Python
Arguably, this is not a programming feature per se, but so useful that I'll post it nevertheless.
$ python -m http.server
...followed by $ wget http://<ipnumber>:8000/filename
somewhere else.
If you are still running an older (2.x) version of Python:
$ python -m SimpleHTTPServer
You can also specify a port e.g. python -m http.server 80
(so you can omit the port in the url if you have the root on the server side)
Solution 79 - Python
Multiple references to an iterator
You can create multiple references to the same iterator using list multiplication:
>>> i = (1,2,3,4,5,6,7,8,9,10) # or any iterable object
>>> iterators = [iter(i)] * 2
>>> iterators[0].next()
1
>>> iterators[1].next()
2
>>> iterators[0].next()
3
This can be used to group an iterable into chunks, for example, as in this example from the itertools
documentation
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
Solution 80 - Python
From python 3.1 ( 2.7 ) dictionary and set comprehensions are supported :
{ a:a for a in range(10) }
{ a for a in range(10) }
Solution 81 - Python
Python can understand any kind of unicode digits, not just the ASCII kind:
>>> s = u'10585'
>>> s
u'\uff11\uff10\uff15\uff18\uff15'
>>> print s
10585
>>> int(s)
10585
>>> float(s)
10585.0
Solution 82 - Python
__slots__
is a nice way to save memory, but it's very hard to get a dict of the values of the object. Imagine the following object:
class Point(object):
__slots__ = ('x', 'y')
Now that object obviously has two attributes. Now we can create an instance of it and build a dict of it this way:
>>> p = Point()
>>> p.x = 3
>>> p.y = 5
>>> dict((k, getattr(p, k)) for k in p.__slots__)
{'y': 5, 'x': 3}
This however won't work if point was subclassed and new slots were added. However Python automatically implements __reduce_ex__
to help the copy
module. This can be abused to get a dict of values:
>>> p.__reduce_ex__(2)[2][1]
{'y': 5, 'x': 3}
Solution 83 - Python
itertools
This module is often overlooked. The following example uses itertools.chain()
to flatten a list:
>>> from itertools import *
>>> l = [[1, 2], [3, 4]]
>>> list(chain(*l))
[1, 2, 3, 4]
See http://docs.python.org/library/itertools.html#recipes for more applications.
Solution 84 - Python
Manipulating sys.modules
You can manipulate the modules cache directly, making modules available or unavailable as you wish:
>>> import sys
>>> import ham
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named ham
# Make the 'ham' module available -- as a non-module object even!
>>> sys.modules['ham'] = 'ham, eggs, saussages and spam.'
>>> import ham
>>> ham
'ham, eggs, saussages and spam.'
# Now remove it again.
>>> sys.modules['ham'] = None
>>> import ham
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named ham
This works even for modules that are available, and to some extent for modules that already are imported:
>>> import os
# Stop future imports of 'os'.
>>> sys.modules['os'] = None
>>> import os
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named os
# Our old imported module is still available.
>>> os
<module 'os' from '/usr/lib/python2.5/os.pyc'>
As the last line shows, changing sys.modules only affects future import
statements, not past ones, so if you want to affect other modules it's important to make these changes before you give them a chance to try and import the modules -- so before you import them, typically. None
is a special value in sys.modules
, used for negative caching (indicating the module was not found the first time, so there's no point in looking again.) Any other value will be the result of the import
operation -- even when it is not a module object. You can use this to replace modules with objects that behave exactly like you want. Deleting the entry from sys.modules
entirely causes the next import
to do a normal search for the module, even if it was already imported before.
Solution 85 - Python
You can ask any object which module it came from by looking at its __ module__ property. This is useful, for example, if you're experimenting at the command line and have imported a lot of things.
Along the same lines, you can ask a module where it came from by looking at its __ file__ property. This is useful when debugging path issues.
Solution 86 - Python
Some of the builtin favorites, map(), reduce(), and filter(). All extremely fast and powerful.
Solution 87 - Python
One word: IPython
Tab introspection, pretty-printing, %debug
, history management, pylab
, ... well worth the time to learn well.
Solution 88 - Python
Guessing integer base
>>> int('10', 0)
10
>>> int('0x10', 0)
16
>>> int('010', 0) # does not work on Python 3.x
8
>>> int('0o10', 0) # Python >=2.6 and Python 3.x
8
>>> int('0b10', 0) # Python >=2.6 and Python 3.x
2
Solution 89 - Python
You can build up a dictionary from a set of length-2 sequences. Extremely handy when you have a list of values and a list of arrays.
>>> dict([ ('foo','bar'),('a',1),('b',2) ])
{'a': 1, 'b': 2, 'foo': 'bar'}
>>> names = ['Bob', 'Marie', 'Alice']
>>> ages = [23, 27, 36]
>>> dict(zip(names, ages))
{'Alice': 36, 'Bob': 23, 'Marie': 27}
Solution 90 - Python
Extending properties (defined as descriptor) in subclasses
Sometimes it's useful to extent (modify) value "returned" by descriptor in subclass. It can be easily done with super()
:
class A(object):
@property
def prop(self):
return {'a': 1}
class B(A):
@property
def prop(self):
return dict(super(B, self).prop, b=2)
Store this in test.py
and run python -i test.py
(another hidden feature: -i
option executed the script and allow you to continue in interactive mode):
>>> B().prop
{'a': 1, 'b': 2}
Solution 91 - Python
A slight misfeature of python. The normal fast way to join a list of strings together is,
''.join(list_of_strings)
Solution 92 - Python
Creating enums
In Python, you can do this to quickly create an enumeration:
>>> FOO, BAR, BAZ = range(3)
>>> FOO
0
But the "enums" don't have to have integer values. You can even do this:
class Colors(object):
RED, GREEN, BLUE, YELLOW = (255,0,0), (0,255,0), (0,0,255), (0,255,255)
#now Colors.RED is a 3-tuple that returns the 24-bit 8bpp RGB
#value for saturated red
Solution 93 - Python
The Object Data Model
You can override any operator in the language for your own classes. See this page for a complete list. Some examples:
-
You can override any operator (
* + - / // % ^ == < > <= >= .
etc.). All this is done by overriding__mul__
,__add__
, etc. in your objects. You can even override things like__rmul__
to handle separatelyyour_object*something_else
andsomething_else*your_object
..
is attribute access (a.b
), and can be overridden to handle any arbitraryb
by using__getattr__
. Also included here isa(…)
using__call__
. -
You can create your own slice syntax (
a[stuff]
), which can be very complicated and quite different from the standard syntax used in lists (numpy has a good example of the power of this in their arrays) using any combination of,
,:
, and…
that you like, using Slice objects. -
Handle specially what happens with many keywords in the language. Included are
del
,in
,import
, andnot
. -
Handle what happens when many built in functions are called with your object. The standard
__int__
,__str__
, etc. go here, but so do__len__
,__reversed__
,__abs__
, and the three argument__pow__
(for modular exponentiation).
Solution 94 - Python
The re.Scanner
class. http://code.activestate.com/recipes/457664-hidden-scanner-functionality-in-re-module/
Solution 95 - Python
"Unpacking" to function parameters
def foo(a, b, c):
print a, b, c
bar = (3, 14, 15)
foo(*bar)
When executed prints:
3 14 15
Solution 96 - Python
The reversed()
builtin. It makes iterating much cleaner in many cases.
quick example:
for i in reversed([1, 2, 3]):
print(i)
produces:
3
2
1
However, reversed()
also works with arbitrary iterators, such as lines in a file, or generator expressions.
Solution 97 - Python
The Zen of Python
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Solution 98 - Python
Changing function label at run time:
>>> class foo:
... def normal_call(self): print "normal_call"
... def call(self):
... print "first_call"
... self.call = self.normal_call
>>> y = foo()
>>> y.call()
first_call
>>> y.call()
normal_call
>>> y.call()
normal_call
...
Solution 99 - Python
string-escape
and unicode-escape
encodings
Lets say you have a string from outer source, that contains \n
, \t
and so on. How to transform them into new-line or tab? Just decode string using string-escape
encoding!
>>> print s
Hello\nStack\toverflow
>>> print s.decode('string-escape')
Hello
Stack overflow
Another problem. You have normal string with unicode literals like \u01245
. How to make it work? Just decode string using unicode-escape
encoding!
>>> s = '\u041f\u0440\u0438\u0432\u0456\u0442, \u0441\u0432\u0456\u0442!'
>>> print s
\u041f\u0440\u0438\u0432\u0456\u0442, \u0441\u0432\u0456\u0442!
>>> print unicode(s)
\u041f\u0440\u0438\u0432\u0456\u0442, \u0441\u0432\u0456\u0442!
>>> print unicode(s, 'unicode-escape')
Привіт, світ!
Solution 100 - Python
Someone blogged about Python not having an unzip function to go with zip(). unzip is straight-forward to calculate because:
>>> t1 = (0,1,2,3)
>>> t2 = (7,6,5,4)
>>> [t1,t2] == zip(*zip(t1,t2))
True
On reflection though, I'd rather have an explicit unzip().