This series of posts is for those who have used a programming language before but are not familiar with Python. This fifth post continues the introduction to the language.
In the last post we explored the if-elif-else statement as well as the while and for loop statements. In this post we start exploring functions.
We’ve seen examples of Python functions in previous posts. The basic syntax is:
-- Define a function...
def function-name ( parameters ):
code-block
-- Call a function...
function-name ( arguments )
Python function definitions begin with the keyword def, followed by a name for the function, followed by parentheses (possibly with parameters enclosed), and ending with a colon. The indented code block defines what the function does.
To call (aka invoke or execute) a Python function, we use the function name followed by parentheses (possibly with arguments enclosed).
Here’s a simple example:
002|
003| def spam_and_eggs ():
004| ”’My Spam & Eggs function.”’
005| …
006| …
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs()
012|
Lines #3 to #7 define a function called spam_and_eggs (Python’s version of “foo” and “bar” is “spam” and “eggs” because the language is named after the British comedy group, not the snake.)
The function defines no parameters (line #3), so calls to the function (line #11) take no arguments. The body of the function (lines #5 to #7) has placeholder Ellipsis object that does nothing but indicates that “code goes here.”
Line #4 is a “doc string” — a special case where a string as the first code line of the function body is code-accessible text for documenting the function. We’ll see it in action in the next example.
The parentheses are a critical part of the syntax. They are required in the definition to define input parameters for the function. Or be empty (as above) if the function takes no arguments. Parentheses in a function call enclose the arguments being passed to the function. Or are empty if there are none.
A function name without the parentheses refers to the function object itself:
002|
003| def spam_and_eggs ():
004| “””My Spam & Eggs function.”””
005| …
006| …
007| …
008|
009|
010| # Refer to the function…
011| print(“Function…”)
012| print(“Object:”, spam_and_eggs)
013|
014| # Print the function’s name…
015| print(“Name:”, spam_and_eggs.__name__)
016|
017| # Print the function’s docstring…
018| print(“Docs:”, spam_and_eggs.__doc__)
019| print()
020|
Line #12 prints the function object itself. Line #15 prints its __name__ property, and line #18 prints its __doc__ property. These properties (and many others) are included with function objects.
When run, this prints:
Function... Object: <function spam_and_eggs at 0x0000023E2202D9E0> Name: spam_and_eggs Docs: My Spam & Eggs function.
Printing the name of a function using the function name may seem redundant, but consider a more complex example:
002|
003| def spam ():
004| ”’My delicious Spam function.”’
005| …
006|
007| def eggs ():
008| ”’My wonderful Eggs function.”’
009| …
010|
011| def bacon ():
012| ”’My tasty Bacon function.”’
013| …
014|
015|
016| def whats_my_name (function):
017| ”’Print function’s name and docstring.”’
018| print(f’Function: {function.__name__}‘)
019| print(function.__doc__)
020| print()
021|
022|
023| # Direct function references…
024| print(‘Direct references…’)
025| print(‘====================’)
026| whats_my_name(spam)
027| whats_my_name(eggs)
028| whats_my_name(bacon)
029| print()
030|
031| # Create function aliases…
032| ftn1 = spam
033| ftn2 = eggs
034| ftn3 = bacon
035|
036| # Use the aliases; same thing…
037| print(‘Alias references…’)
038| print(‘===================’)
039| whats_my_name(ftn1)
040| whats_my_name(ftn2)
041| whats_my_name(ftn3)
042| print()
043|
When run, this prints:
Direct references... ==================== Function: spam My delicious Spam function. Function: eggs My wonderful Eggs function. Function: bacon My tasty Bacon function. Alias references... =================== Function: spam My delicious Spam function. Function: eggs My wonderful Eggs function. Function: bacon My tasty Bacon function.
But let’s get back to defining functions.
Functions can have a return value:
002|
003| def spam_and_eggs ():
004| ”’My Spam & Eggs function.”’
005| …
006| …
007| return 42
008|
009|
010| # Call the function…
011| retval = spam_and_eggs()
012| print(f’The function returned: {retval}‘)
013| print()
014|
Functions always return something. If the function body doesn’t specify a return value, the function returns the built-in object None.
When run, this prints:
The function returned: 42
Functions can return any Python object, for instance a tuple of other objects:
002|
003| def spam_and_eggs ():
004| ”’My Spam & Eggs function.”’
005| …
006| …
007| return (42, “The Answer!”, 0.0, [1,2,3])
008|
009|
010| # Call the function…
011| retval = spam_and_eggs()
012| print(f’Return type: {type(retval)}‘)
013| print(f’Return length: {len(retval)}‘)
014| print()
015|
016| print(‘Returned items:’)
017| # Iterate over returned tuple…
018| for ix,val in enumerate(retval):
019| print(f’Item[{ix}]: {val}‘)
020| print()
021|
When run, this prints:
Return type: <class 'tuple'> Return length: 4 Returned items: Item[0]: 42 Item[1]: The Answer! Item[2]: 0.0 Item[3]: [1, 2, 3]
The built-in type function (line #12) returns an object’s data type (aka class) as a Type object. The built-in len function (line #13), of course, returns its length. The for loop with enumerate should be familiar from last week’s post.
Function definitions can include a list of parameters:
002|
003| def spam_and_eggs (x, y, z):
004| ”’Function with three required arguments.”’
005| …
006| print(x,y,z, sep=“, “)
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs(2.1, 4.2, 6.3)
012| spam_and_eggs(2.1, 4.2, z=0.0)
013| spam_and_eggs(2.1, y=4.2, z=0.0)
014| spam_and_eggs(x=2.1, y=4.2, z=6.3)
015| spam_and_eggs(y=4.2, z=6.3, x=2.1)
016| #spam_and_eggs(X=2.1, Y=4.2, Z=6.3) # error!
017| #spam_and_eggs(a=2.1, b=4.2, c=6.3) # error!
018| print()
019|
This function defines three parameters, named x, y, and z. These are required, so calls to the function must provide three, and only three, arguments. (Note they are called parameters in function definitions and arguments in function calls.)
Note the built-in print function in line #6. The function can take multiple arguments and, by default, print spaces between them. The optional sep argument allows specifying a different delimiter. When run, this prints:
2.1, 4.2, 6.3 2.1, 4.2, 0.0 2.1, 4.2, 0.0 2.1, 4.2, 6.3 2.1, 4.2, 6.3
Lines #11 to #17 call the function in various ways. When arguments are not labeled with names, they are positional and occupy function parameters in order. But we can provide parameter names as in all lines except #11. When named like this, they are keyword arguments and can occur in any order (as in line #15).
Note that lines #16 and #17 would raise an Exception if allowed to run. In the first case because the names are uppercase — names in Python are case-sensitive. In the second case, because the names don’t match the defined parameters. If we provide names, they must match exactly.
In the first post we saw that variable names can have annotations declaring their intended type. Python ignores these “type-hints”, but various Python tools use them to validate code correctness. We can use these type-hints in function definitions to indicate the expected types of arguments and return value:
002|
003| def spam_and_eggs (x:float, y:float, z:float) -> None:
004| ”’Spam, spam, spam, spam, spam…”’
005| …
006| print(x,y,z, sep=“, “)
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs(2.1, 4.2, 6.3) # expected
012| spam_and_eggs(210, 421, 631) # not expected but okay
013| spam_and_eggs(“0”, “X”, [5,6,7]) # unexpected
014|
Note the -> syntax for specifying the function’s return type. This function has no explicit return statement and therefore returns None. More importantly, note that although the argument types are unexpected, they do not cause a type error. They may cause a code error, though, because they’re unexpected (and presumably incorrect) types.
In production environments, type-hints can be invaluable, especially when collaborating in a team of coders. For this introduction and tutorial, we’ll ignore them just like Python does.
Function arguments can be made optional by providing a default value:
002|
003| def spam_and_eggs (x, y, z=0.0):
004| ”’The Z parameter is optional.”’
005| …
006| print(x,y,z, sep=“, “)
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs(2.7, 1.6, 3.1)
012| spam_and_eggs(2.7, 1.6)
013| spam_and_eggs(y=1.6, x=2.7)
014|
For this function, callers need not provide the z value — it defaults to 0.0. Note that x and y are still positional parameters (and required) but now z is a keyword parameter and optional.
When run, this prints:
2.7, 1.6, 3.1 2.7, 1.6, 0.0 2.7, 1.6, 0.0
We can make all arguments option by using all keyword parameters:
002|
003| def spam_and_eggs (x=0.0, y=0.0, z=0.0):
004| ”’All parameters are optional.”’
005| …
006| print(x,y,z, sep=“, “)
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs(2.7, 1.6)
012| spam_and_eggs(y=1.6, x=2.7)
013| spam_and_eggs(y=1.6)
014| spam_and_eggs(z=3.14)
015| spam_and_eggs()
016|
Now we can provide no arguments or any arguments in any order. Note that positional parameters must come before keyword parameters. Once we assign a default value to a parameter, all remaining parameters must also have default values.
Python has a special syntax for function parameters.
Firstly, for positional parameters:
002|
003| def spam_and_eggs (*args):
004| ”’Accepts zero or more positional arguments.”’
005|
006| print(args)
007| print(*args, sep=“, “)
008| print()
009| …
010| …
011|
012|
013| # Call the function…
014| spam_and_eggs()
015| spam_and_eggs(2.7, 1.6)
016| spam_and_eggs(2.7, 1.6, 3.6, 4.2, 6.1)
017| #spam_and_eggs(a=2.7, b=1.6) # error!
018|
The special *args parameter (line #3) lets the function take zero or more arguments, all of which come bundled in a tuple named args (the name is canonical, but you can use any name you like; just prepend an asterisk — that’s what makes it special).
Note that you cannot use keywords in this case; line #17 would cause an error if not commented out.
The first print statement (line #6) prints args as is — a tuple. The second print statement (line #7) prepends an asterisk to args which unpacks its elements. It’s the equivalent of listing each member of the tuple in order, but without having to know how many there are. It’s effectively like writing:
For however many members args happens to have.
When run, this prints:
() (blank line) (2.7, 1.6) 2.7, 1.6 (2.7, 1.6, 3.6, 4.2, 6.1) 2.7, 1.6, 3.6, 4.2, 6.1
In the first call (line #14), no arguments were passed, so the tuple is empty and there are no arguments to print (the second line of the output).
Secondly, for keyword parameters:
002|
003| def spam_and_eggs (**kwargs):
004| ”’Accepts zero or more keyword arguments.”’
005|
006| print(kwargs)
007| for kw in kwargs: print(f’{kw}: {kwargs[kw]}‘)
008| print()
009| …
010| …
011|
012|
013| # Call the function…
014| spam_and_eggs()
015| spam_and_eggs(x=2.7, y=1.6, z=4.2)
016| spam_and_eggs(foo=21, bar=42, nop=63)
017|
The special **kwargs parameter (line #3) lets the function take zero or more keyword arguments. These come bundled in a dict named kwargs (the name again is canonical; it’s the double-asterisk that makes it special).
The first print statement (line #6) again just prints the kwargs object. The one-line for loop on line #7 iterates through the keywords (remember that a dict object in a list context returns its keys) and prints the keyword and dictionary value (the argument value).
When run, this prints:
{}
{'x': 2.7, 'y': 1.6, 'z': 4.2}
x: 2.7
y: 1.6
z: 4.2
{'foo': 21, 'bar': 42, 'nop': 63}
foo: 21
bar: 42
nop: 63
In this case, the empty dictionary doesn’t result in a blank line because the for loop never executes — there are no keywords to iterate.
Note that this version of the function cannot take positional parameters. The kwargs dictionary requires a keyword — the name of the parameter. Positional arguments have no name, just a position. This is in contrast to explicit keyword parameters that can be filled in by positional arguments:
002|
003| def spam_and_eggs (x=0.0, y=0.0, z=0.0):
004| ”’Function with keyword parameters.”’
005| …
006| print(f’{x:.2f}, {y:.2f}, {z:.2f}‘)
007| …
008|
009|
010| # Call the function…
011| spam_and_eggs()
012| spam_and_eggs(2.71)
013| spam_and_eggs(2.71, 1.61)
014| spam_and_eggs(2.71, 1.61, 3.14)
015| print()
016|
When run, this prints:
0.00, 0.00, 0.00 2.71, 0.00, 0.00 2.71, 1.61, 0.00 2.71, 1.61, 3.14
Note the use of an f-string in line #6 to print the parameters. It’s a topic for later, but f-string code bits can contain formatting information: {code:formatting-info} (the code to print followed by a colon and appropriate formatting info). In this case, the formatting info is .2f, which means “print only and always two decimal points of a floating-point value”. The effect is apparent in the output.
Below we’ll see how to use both *args and **kwargs, but let’s return to the first one to see how we actually use it in a function:
002|
003| def spam_and_eggs (*args):
004| ”’Accepts zero or more positional arguments.”’
005| x = args[0] if 0 < len(args) else 0.0
006| y = args[1] if 1 < len(args) else 0.0
007| z = args[2] if 2 < len(args) else 0.0
008|
009| print(*args, sep=“, “)
010| print(x,y,z, sep=“, “)
011| print()
012| …
013| …
014|
015|
016| # Call the function…
017| spam_and_eggs()
018| spam_and_eggs(2.7, 1.6)
019| spam_and_eggs(2.7, 1.6, 3.6, 4.2, 6.1)
020|
Lines #5 through #7 each test to see if args is long enough to extract a positional argument. Note the special one-line if-else syntax:
true-value if condition else false-value
When run, this prints:
(blank line) 0.0, 0.0, 0.0 2.7, 1.6 2.7, 1.6, 0.0 2.7, 1.6, 3.6, 4.2, 6.1 2.7, 1.6, 3.6
Note that extra arguments passed in *args are ignored (line #19 passes five arguments — the last two are ignored).
Given the default values for x, y, and z above, the function obviously expects to receive float objects. We saw above that parameter type-hints can help the coder (and various Python tools) know the argument types, but the *args keyword doesn’t allow for that.
Instead, we can write the above example this way (assuming we want floats):
002|
003| def spam_and_eggs (*args):
004| ”’Accepts zero or more positional arguments.”’
005| x = float(args[0]) if 0 < len(args) else 0.0
006| y = float(args[1]) if 1 < len(args) else 0.0
007| z = float(args[2]) if 2 < len(args) else 0.0
008|
009| print(*args, sep=“, “)
010| print(x,y,z, sep=“, “)
011| print()
012| …
013| …
014|
015|
016| # Call the function…
017| spam_and_eggs()
018| spam_and_eggs(2.7, 1.6, 3.2)
019| spam_and_eggs(“2.1”, 21, “21”)
020| #spam_and_eggs([1,2,3]) # error!
021|
When run, this prints:
(blank line) 0.0, 0.0, 0.0 2.7, 1.6, 3.2 2.7, 1.6, 3.2 2.1, 21, 21 2.1, 21.0, 21.0
Note the blank first line again.
We can do likewise with **kwargs:
002|
003| def spam_and_eggs (**kwargs):
004| ”’Accepts zero or more keyword arguments.”’
005| x = float(kwargs[‘x’]) if ‘x’ in kwargs else 0.0
006| y = float(kwargs[‘y’]) if ‘y’ in kwargs else 0.0
007| z = float(kwargs[‘z’]) if ‘z’ in kwargs else 0.0
008|
009| print(kwargs)
010| print(x,y,z, sep=“, “)
011| print()
012| …
013| …
014|
015|
016| # Call the function…
017| spam_and_eggs()
018| spam_and_eggs(x=0.51, y=1.61)
019| spam_and_eggs(z=3.14, w=2.71)
020|
In this case the one-line if-else statements use the in operator to determine if the expected keyword is in the dictionary. If so, it’s extracted from kwargs, otherwise the variable gets the default value.
When run, this prints:
{}
0.0, 0.0, 0.0
{'x': 0.51, 'y': 1.61}
0.51, 1.61, 0.0
{'z': 3.14, 'w': 2.71}
0.0, 0.0, 3.14
Here again, if unexpected keywords are provided, they are ignored.
With both these *args and **kwargs examples, if unexpected arguments are a concern, they must be explicitly checked for. This is easy with *args:
002| # Note: THIS CODE RAISES AN EXCEPTION!
003|
004| def spam_and_eggs (*args):
005| ”’Accepts zero or more positional arguments.”’
006| x = float(args[0]) if 0 < len(args) else 0.0
007| y = float(args[1]) if 1 < len(args) else 0.0
008| z = float(args[2]) if 2 < len(args) else 0.0
009|
010| if 3 < len(args):
011| raise ValueError(‘Only three arguments expected.’)
012| …
013| …
014|
015|
016| # Call the function…
017| spam_and_eggs()
018| spam_and_eggs(2.7, 1.6)
019| spam_and_eggs(2.7, 1.6, 3.6)
020| spam_and_eggs(2.7, 1.6, 3.6, 4.2)
021|
Line #10 checks the length and, if it’s greater than the expected three, raises an Exception (line #11). One can likewise check if it’s too short.
When run, this code generates an error:
ValueError: Only three arguments expected.
Checking **kwargs is a tiny bit harder (but kinda fun):
002| # Note: THIS CODE RAISES AN EXCEPTION!
003|
004| def spam_and_eggs (**kwargs):
005| ”’Accepts zero or more keyword arguments.”’
006| x = float(kwargs[‘x’]) if ‘x’ in kwargs else 0.0
007| y = float(kwargs[‘y’]) if ‘y’ in kwargs else 0.0
008| z = float(kwargs[‘z’]) if ‘z’ in kwargs else 0.0
009|
010| keywords = set(list(kwargs))
011| extra = keywords – {‘x’, ‘y’, ‘z’}
012| if extra:
013| raise ValueError(f’Unexpected arguments: {extra}.‘)
014| …
015| …
016|
017|
018| # Call the function…
019| spam_and_eggs()
020| spam_and_eggs(x=0.51, y=1.61)
021| spam_and_eggs(z=3.14, w=2.71, a=“foo”)
022|
Line #10 calls the list class with kwargs as a parameter. This creates a list of keywords (which are always strings). These are passed to the set class to create a set of keywords. [The set data type was introduced in part 2.]
Python set objects can do math and logical operations. Line #11 subtracts a hard-coded set comprised of the strings “x”, “y”, and “z” from keywords. This removes “x”, “y” and “z” from keywords (if they exist there). The result assigned to extra then is a set of any leftover keywords.
Line #12 checks to see if extra contains any items (remember, list-like objects are True if they have at least one member but False if they are empty). If it does, line #13 raises an Exception.
When this code is run, line #21 raises an Exception.
We can use *args and **kwargs together:
002|
003| def spam_and_eggs (*args, **kwargs):
004| ”’Accepts both types of arguments!”’
005| x = float(args[0]) if 0 < len(args) else 0.0
006| y = float(args[1]) if 1 < len(args) else 0.0
007| z = float(args[2]) if 2 < len(args) else 0.0
008| a = float(kwargs[‘a’]) if ‘a’ in kwargs else 0.0
009| b = float(kwargs[‘b’]) if ‘b’ in kwargs else 0.0
010|
011| print(*args, sep=“, “)
012| print(kwargs)
013| print(x,y,z, a,b, sep=“, “)
014| print()
015| …
016| …
017|
018|
019| # Call the function…
020| spam_and_eggs()
021| spam_and_eggs(a=3.14159)
022| spam_and_eggs(x=0.42)
023| spam_and_eggs(2.7, 1.6, b=0.25)
024| spam_and_eggs(2.71, 1.62, 0.33, 3.14, 6.16, b=0.25)
025|
When we do, *args must come first (just as positional parameters must come before any keyword parameters). Note that naming positional arguments (as in line #22) turns them into keyword arguments. Since no keyword named “x” is expected, the passed value is ignored. Since line #24 passes five positional arguments, the last two are ignored.
When run, this prints:
(blank line)
{}
0.0, 0.0, 0.0, 0.0, 0.0
(blank line)
{'a': 3.14159}
0.0, 0.0, 0.0, 3.14159, 0.0
(blank line)
{'x': 0.42}
0.0, 0.0, 0.0, 0.0, 0.0
2.7, 1.6
{'b': 0.25}
2.7, 1.6, 0.0, 0.0, 0.25
2.71, 1.62, 0.33, 3.14, 6.16
{'b': 0.25}
2.71, 1.62, 0.33, 0.0, 0.25
Note the blank lines in the first three outputs because no positional arguments are passed.
Two last interesting bits about Python functions.
Firstly, functions can contain their own private functions:
002|
003| def spam (x, y, z):
004| ”’A function…”’
005| …
006|
007| def eggs (a, b):
008| ”’…can have functions.”’
009| …
010| …
011| return (x+y+z)+(a*b)
012|
013| …
014|
015| # Use the local eggs function…
016| foo = (x*x) / 2
017| bar = (y*y) / 2
018| retval = eggs(foo, bar)
019| …
020|
021| # Return a value…
022| return retval
023|
024|
025| # Call the function…
026| print(f’{spam(11,22,33) = }‘)
027| print()
028|
The eggs function is only visible inside the spam function. Any object declared inside a function is local (aka private) to that function. So, any number of functions could have their own eggs function without collision.
And, of course, a function can have any number of private functions, and those functions can have functions of their own (and so on). Generally speaking, functions can be defined almost anywhere.
When run, this prints:
spam(11,22,33) = 14707.0
Note how we can put a function call into an f-string. We include an equals-sign to get the debug version that includes the code and equals-sign along with the value.
Secondly, function objects can be passed to other functions. This allows functions that change their behavior depending on what function is passed in:
002|
003| def add_function (a, b):
004| “Add two items (a+b).”
005| return float(a+b)
006|
007| def subtract_function (a, b):
008| “Subtract two items (a-b).”
009| return float(a–b)
010|
011| def multiply_function (a, b):
012| “Multiply two items (a*b).”
013| return float(a*b)
014|
015| def divide_function (a, b):
016| “Divide two items (a/b).”
017| if b==0: return 0.0
018| return float(a/b)
019|
020| def math_operation (op, a, b):
021| “Do a given math operation.”
022| return op(a, b)
023|
024|
025| # Do the math…
026| x, y = 21, 42
027|
028| result = math_operation(add_function, x, y)
029| print(f’A+B = {result:+6.1f}‘)
030|
031| result = math_operation(subtract_function, x, y)
032| print(f’A+B = {result:+6.1f}‘)
033|
034| result = math_operation(multiply_function, x, y)
035| print(f’A*B = {result:+6.1f}‘)
036|
037| result = math_operation(divide_function, x, y)
038| print(f’A/B = {result:+6.1f}‘)
039|
040| print()
041|
Lines #3 to #18 define four functions that each take two arguments and return a float value. Their names reflect the simple math operation they perform on the values passed to them: “add”, “subtract”, “multiply”, and “divide”.
(Note how the first-line documentation string can be any string. We normally use the triple-quotes type because those allow multi-line documentation, but any string works.)
Lines #20 to #22 define math_operation, a function that expects three arguments, a function and two values. It invokes the function, passes it the two values, and returns what it returns (line #22).
Lines #28 to #38 exercise math_operation by passing value arguments (x and y) along with one of the “operation” functions defined in lines #3 to #18. Note that when we print the returned result, we provide format information (:+6.1f) that specifies a six-character field with one decimal digit and a leading plus or minus.
When run, this prints:
A+B = +63.0 A+B = -21.0 A*B = +882.0 A/B = +0.5
Because they are formatted, the values line up nicely. Note that the six-character width includes the plus/minus sign and decimal point.
To end, a style note about writing functions:
002|
003| def my_function (a=0, b=“”, c=None):
004| “My amazing and unique function.”
005| pass
006|
007|
008| # Call the function…
009| my_function()
010| my_function(42)
011| my_function(b=“hello”)
012| my_function(’21’, “eggs”, “spam”)
013| my_function(None, None, None)
014|
The space (or any number of spaces) between the function name and the opening parenthesis is optional in all cases. Many coders use no space between the name and parameters as I did in line #3 (and all other function definitions here).
I prefer a space in function definitions, but many programmers do not. It’s purely a style decision. Most coders put no space between the name and arguments in function calls (as in lines #9 to #13). But I prefer a lexical difference between the function definition and the function calls.
Note the pass statement in line #5 — it plays the same “do nothing” role as do the Ellipsis. Both act as placeholders. The pass statement differs slightly in that it’s preferred in cases where a code body is necessary syntactically but not programmatically — where the object being defined is a dummy or proxy that requires no code. We’ll examples of this down the road.
There’s one more post in the tour where I’ll introduce some Python features I left out of previous posts. Some you may not have encountered the like of in other languages.
After that, we begin digging deeper into Python.
∅
ATTENTION: The WordPress Reader strips the style information from posts, which can destroy certain important formatting elements. If you’re reading this in the Reader, I highly recommend (and urge) you to [A] stop using the Reader and [B] always read blog posts on their website.
This post is: This is Python! (part 5)