Some Observations about the
Python Programming Language

This is not a FAQ. It is a personal collection of observations about Python (mostly because they somehow surprised me when getting to know the language, version 2.2.2 to be specific).

Comments

Python has no in-line comments (in contrast to Pascal and C/C++). It only has end-of-line comments.

Python has no multi-line comments (in contrast to Pascal and C/C++). You need to prepend # to every comment line (some editors feature a comment/uncomment-block operation).

You can use triple-quoted strings for multi-line documentation in appropriate places (at the top of a module, function, or class), also known as documentation strings.

Documentation Strings

The Python Reference Manual does not explain the notion of documentation strings, or docstrings for short. (At least I could not find it.)

If the first statement in a program (script), module, function definition, or class definition is an expression statement that yields a string, then that string is assigned to the (attribute) name __doc__ (see The standard type hierarchy for some hints). This docstring can later be extracted in various ways (see e.g. Retrieving source code in module inspect; also see modules pydoc and doctest).

def square(x):
    """Return the square of x."""
    return x * x

import inspect
print inspect.getdoc(square)

By convention, docstrings are written between triple double quotes, as shown above, even if the string fits on one line.

Name-Object-Value and Variables

A general paradigm in programming languages is the notion of a variable having a value. At any moment during the execution of a program, each variable has a (single) value. In computer terms: each memory location holds a bit pattern. In Python, this view is counterproductive when applied naively.

The Python paradigm is:

Unfortunately, the documentation for the assignment statement says "Assignment statements are used to (re)bind names to values" (rather than "to objects").

In the case of immutable objects (like integers, strings, and tuples), the object and its value can be considered a single entity: the Python name serves as a program variable, and the object-with-value as its value.

In the case of mutable objects (like lists and dictionaries), the object acts like the program variable, and the name is a reference (pointer) to that variable.

The execution of Python statements can affect the name binding, as well as the value of (mutable) objects:

s = [1, 2]   # s refers to a list object,
             #   with two elements in its "value"
t = [1, 2]   # t refers to a different list object,
             #   with the same value as (the object of) s
s = t        # s now refers to the same object as t does,
             #   s and t are "aliases"
s.append(3)  # s still refers to the same object,
             #   but its value has changed
print t      # this change is also visible via t

def s(n): return n+1  # now s refers to a function (object)

Note that also function names refer to an object, viz. representing the code of their definition.

Types

Python is not a strongly-typed language (in contrast to Pascal). A name neither has a fixed meta-type (such as constant, variable, function, ...) nor a fixed type (integer, string, ...). At any time, any name can be made to refer to any kind of object having any kind of value in agreement with the object's type.

Objects do have a fixed type. The type restricts

If, during the evaluation of an expression, the operand type(s) are found inappropriate, then the built-in exception TypeError is raised.

The built-in function type() returns the type of its argument object. This type is itself an object of type 'type' (though, surprisingly, the type of type objects is not listed in the Standard Type Hierarchy). These type objects can be compared to write your own type checking code (see e.g. Function Parameters).

Python offers a limited set of types: The Standard Type Hierarchy (also see Built-in Types). Python has no type constructors by which new types can be derived from existing types (in contrast to Pascal and C/C++). For example, you cannot create a Python type for a list consisting just of integers. If an object has type list, then it can contain (references to) objects with any type. You will have to state such restrictions in comments (data invariants), and possibly check them dynamically as preconditions on function parameters.

Even user-defined classes do not introduce a new type, as opposed to languages like Object Pascal and C++.

Constants

Python has no constants (in contrast to Pascal and C++). You can do the following:

import math

def circleArea(r):
    """Return the area of the circle with radius r."""
    return math.pi * r ** 2

print circleArea(1)    # prints '3.14159265359'
math.pi = 'Pi'         # redefine the "constant" pi in math
print circleArea(2)    # prints 'PiPiPiPi'
print circleArea(0.5)  # raises a TypeError

A name-object binding can always be changed by name-binding constructs, such as an assignment, function definition, or class definition. Python gives you all the freedom, including the freedom to mess things up accidentally. Often --but not always-- the dynamic type checking will offer some protection.

Booleans

Python (2.2) has no "true" boolean type (like C, but in contrast to Pascal and C++). For example, comparisons yield integer values: 1 for true, 0 for false.

However, there exists a notion of boolean context:

In such a boolean context, every value has a boolean interpretation. The name False is built-in (although the Python Library Reference calls False a value; see Truth Value Testing). When I tried print False, True in the Python interpreter, it printed 0 1, but I have not found the corresponding documentation.

As of Python 2.3, the situation has improved (somewhat), by the introduction of the type bool. The documentation is still rather vague. The only new thing I could find there is about boolean values, not a boolean type.

Characters

Python has no character type (in contrast to Pascal and C/C++). Although a string is a sequence type, the elements of a string are not "true" objectes by themselves.

Strings of length one are used as characters, e.g. in the built-in functions chr() and ord().

Expression Semantics

In Python, the evaluation of an expression yields an object, rather than a value (at least on the right-hand side of Assignment Statements; it is not addressed in Expressions).

In a boolean context (see above), it is the expression's value that is interpreted.

Division

In Python, some operators are overloaded and their meaning depends on the types of the operands. (Also important is the notion of arithmetic conversion.) Things are further obscured by the fact that a type is not associated with a name but with the object that the name refers to.

In the case of division (/) you may be in for a surprise (also see PEP 238: Changing the Division Operator). For example, x**(1/2) does not yield the square root of x, because 1/2 equals 0, whereas 1.0/2 equals 0.5. This can be less obvious when expressions involve names:

# not recommended
s = 1  # distance [m]
t = 2  # time [s]

v = s / t  # determine velocity [m/s]

# recommended
s = 1.0  # distance [m]
t = 2.0  # time [s]

v = s / t  # determine velocity [m/s]

Statement Sequencing

A suite and file input (a program or module) consists of a sequence of statements.

Statements in such a sequence are executed from top to bottom, but the occurrence of exceptions and some statements (may) alter control flow: return, yield, raise, break, continue.

Assignment (versus equality relation)

Python falls in the same trap as C/C++ by using = for assignment, and not for the equality relation. The latter is denoted by == (like in C/C++ and Java). Now, you face the dilemma of writing the ugly == in comments as well, or sticking to the natural =.

Fortunately, assignment statements are not considered expressions in Python, and code like if i=0: raises a syntax error.

A nice Python feature is tuple assignment, as in a,b=b,a.

Exit Execution

Python has no statement to exit execution of a program or module before "running off the end". You could use sys.exit(), but this requires an initial import sys.

Actually, the Python documentation mentions nothing about when the execution of a program or module ends normally, and what happens then. Is normal termination by running off the end equivalent to sys.exit()?

An exit statement might be useful for testing purposes. As an alternative, one can use assert False (or sys.exit("message)).

Function Parameters

Python has no result parameters (in contrast to Pascal and C/C++). In order to get any result out of a function it must be returned by a return statement (or go through a mutable object passed as parameter or via a global name). In case of multiple results, they can be returned as a tuple:

def f(x, y):
    """Return sum and difference."""
    return x-y, x+y

Function parameters are not restricted by type. If you insist on restricting the type of function arguments, then you have to include checking code explicitly:

def f(x, y):
    """Return sum and difference."""
    if not (type(x) == type(y) == type(0)):
        raise TypeError, "f only applies to objects of type 'int'"
    return x-y, x+y

Print a function definition interactively

When working interactively, I have not been able to get a listing of the functions that I defined in the session.

Formatted Input

Python has no convenient facility for formatted input (in contrast to Pascal, C and C++). Assume you want to read a text file, whose line contain an integer, a floating-point number, and string (without quotes), seperated by spaces. In Python, you must read entire lines into a string through raw_input(), and subsequently split the string at the spaces and convert each part according to the appropriate type.


Copyright © 2002-2006, Tom Verhoeff
Validate HTML