Basic Python Elements (Python on Symbian)
From Symbian Developer Community
Original Author: Mike Jipping
This chapter introduces the basics of the Python language. In the last chapter, we saw how to set up the Python environment for your PC and your Symbian device. In this chapter, we will begin to write code that will run in both environments.
Note that this chapter is not meant as a definitive guide to Python. Instead, we overview the basic elements of the language and provide some simple examples to get you started programming. Where useful, comparative examples are provided explaining how Python behaviour differs from that of C++ and Java.
Note also that this chapter is applicable to all versions of Python. We are covering the basics of the Python language here; specifics of the Symbian implementation of Python will be covered in later chapters.
Contents |
Introduction
Python was designed as a reaction to, and an improvement on, compiled languages such as C++ and Java, and scripting languages such as Perl. It has a mixture of features from several languages — even functional languages — and was designed to put the best features into a language that could support the type of rapid development that scripting languages were able to support. Its design philosophy focused on code readability.
This means that Python supports multiple programming styles or paradigms. It is primarily an imperative language and programs could be written completely from an imperative perspective. Python supports object-orientation at its core and supports structured programming. However, you will find support for functional programming; Lisp programmers will be comfortable with its use of lambda calculus. Even pieces of aspect programming can be found in Python's design (for example, meta-programming).
So Python combines the best parts of programming languages into a highly readable language that executes in a scripting environment. By "scripting", we mean that Python programs are executed directly by an execution engine without being translated into some native machine code. This execution engine is often referred to as an interpreter; it interprets Python statements by reading and parsing them and executes that interpretation by running machine code on the host computer's hardware. When compared to languages that compile to machine language — like C++ — the Python interpreter represents an extra layer of execution, which can be a program performance issue. Interpreted Python programs tend to run slower than similar programs written in compiled languages. However, much work has been done on Python interpreters to minimize this performance issue.
The scripting nature of Python has many benefits. The interpreter can take many forms, which means it can be used in many places. It can become part of other software systems — such as Web browsers or system controllers — and can be ported to different architectural platforms with ease. Python supports experimentation with language features because its environment can be extended with new plugin programs.
All of Python's many design aspects will become more apparent as we introduce Python programming.
Variables
A strength of Python is the ability to abstract that concept of computer memory into something that is easy to use: the idea of variables. In Python, variables hold objects and objects represent data. Variables have identifiers or names in Python; names must begin with a letter, but can have any combination of letters, numbers, and/or the "_" character. Python uses assignment to give a variable a value and references the value of the variable when the variable is used by name.
Python does not require variables to be declared and provides no mechanism to do so — simply assigning a value to a variable makes it exist (this is a significant difference between Python and more structured, compiled languages like C++ or Java). Thus, we can assign values like this:
>>> hours = 42.5
>>> hourly_wage = 12
>>> payment = hours * hourly_wage
>>> payment
510.0
Using a variable without first assigning it a value, however, is an error. Consider the attempt below to using overtime without assigning a value:
>>> payment = hours * hourly_wage + overtime * hourly_wage * 1.5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'overtime' is not defined
Basic Types
Another strength of Python is its ability to view data in many different ways — as numbers or strings of characters or pixels in an image. As with other programming languages, variables in Python are typed, that is, variables are classified by the values they can take on and the operations that may be performed on them. Types that are so basic to Python programming that they are built into the language are referred to as core types. Python has 11 built-in types, summarized in Table 2-1.
| Object Type | Description | Examples |
|---|---|---|
| int | A whole number with fixed precision of unlimited magnitude | 19,72,-12 |
| float | A floating point number with a fractional part of system-defined precision | 3.14159,-50.309,100.2 |
| complex | A number with real and imaginary parts | 7+4.5j |
| boolean | A truth value | true,false |
| string | A sequence of Unicode characters | 'mobile',"icon's" |
| byte | A sequence of 8-bit bytes | b'one',b"characters" |
| list | A sequence of objects, which can be of mixed types | [7,"lowest",False] |
| tuple | A sequence of objects, which can be of mixed types | (7,"lowest",False) |
| dictionary | A group of key/value pairs, which can be of mixed types | {"key1":17,
"key2":True} |
| set | An unordered sequence of objects, which can be of mixed types, with no duplicates | {7,"lowest",False} |
| file | The representation of data kept in storage | myFile = open("README",'r') |
Several of these types will look familiar: int, float, boolean, string, and even complex are represented in many programming languages. Note that boolean true and false values are capitalized. There are a few types, however, that require more explanation:
- The byte type is a representation of 8-bit quantities. Characters fit neatly into 8-bit chunks (well, ASCII characters do; UNICODE characters do not) and Python represents bytes as the character 'b' followed by a string. In the examples in Table 2-1, b'one' is a sequence of three bytes: 01100010, 01101110, and 01100101.
- The list and tuple types can be viewed as collections, like arrays or vectors in other languages. Actually, these are like a mixture of structs and arrays from C and C++. These collections may have any type of data in them. In Table 2-1, we can see an integer, a string, and a boolean in each collection. Lists are represented using square brackets; tuples are represented using parentheses.
- The difference between lists and tuples is an idea called immutability. Immutable objects in Python cannot be changed. Most sequences and collections in Python are immutable — such as strings or tuples. This means you cannot change a tuple once it is set. Lists, on the other hand, can be changed.
- For example, we can assign a list of values to a variable, change the list, then print it back out:
>>> thelist = ['trees', 'birds', 'flowers']
>>> thelist
['trees', 'birds', 'flowers']
>>> thelist[1] = 'cows'
>>> thelist
['trees', 'cows', 'flowers']
- Here, we were able to change items in the list using array-like notation. If we tried this with a tuple, however, we would get an error:
>>> thetuple = ('trees', 'birds', 'flowers')
>>> thetuple
('trees', 'birds', 'flowers')
>>> thetuple[1] = 'cow'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
- The idea of immutability can be used to guarantee that objects remain constant throughout a program. Immutability forces programmers to always make new values assigned to new variables, rather than changing values in place.
- A dictionary in Python is a mapping — like hash table types in Java. Items in a dictionary are stored in key/value pairs. Variables that hold dictionary objects can be references with the key and the value is returned. Consider these examples:
>>> thedictionary = {"item": "coffee", "onshelf": True, "quantity": 10}
>>> thedictionary["item"]
'coffee'
>>> thedictionary["quantity"] += 1
>>> thedictionary["quantity"]
11
- Note how the variable references were in terms of the key of each item, but the value was returned. Note also that, different from array references, referencing dictionaries can be done with any type.
- Sets are collections of objects that support standard mathematical set manipulation operations: union, intersection, and difference. Consider the examples below:
>>> theset = set(['t', 'r', 'e', 'e'])
>>> theset
set(['r', 'e', 't'])
>>> anotherset = set('flower')
>>> theset & anotherset
set(['r', 'e'])
>>> theset | anotherset
set(['e', 'f', 'l', 'o', 'r', 't', 'w'])
>>> theset - anotherset
set(['t'])
>>> 't' in theset
True
- Intersection is represented by the '&' operator; union is represented by the '|' operator; and difference is represented by the '-' operator. The operator 'in' is used to test set membership. Note how sets do not contain duplicate items.
One final word about data types in Python. Python is dynamically typed, that is, the language will keep track of types of variables and those types can change when the values assigned to variables change. Many other languages are statically typed, that is, the types are set when a variable is declared. You might think that dynamic typing can get a little confusing, but each type in Python has a unique set of notation. For examples, lists are specified with square brackets, tuples with parentheses, and sets with the 'set' function.
Operators
Each data type in Python has two properties: the values used for the type and the operators that may be used with values of that type. Below is a brief overview of the operators that can be used with number types, strings, and sequences.
Integers and floating point numbers use the standard arithmetic operators. Python includes a '**' operator, which is for exponentiation.
Strings and other sequences support concatenation (the '+' operator) and repetition (the '*' operator). Consider these examples:
>>> listing = [1, 2, 3, 4, "DONE"]
>>> listing + listing
[1, 2, 3, 4, 'DONE', 1, 2, 3, 4, 'DONE']
>>> listing * 3
[1, 2, 3, 4, 'DONE', 1, 2, 3, 4, 'DONE', 1, 2, 3, 4, 'DONE']
The types in Python that involve sequences are characterized by sequential ordering. Sequences are referenced using square brackets to examine individual items in each sequence. Sequences are organized from left to right, as you might expect. Reference notations are offsets from the leftmost item. Python allows some special notation that are shortcuts, as demonstrated below:
>>> drsuess = 'The Cat in the Hat'
>>> animal = drsuess[4:7] # take out the cat
>>> animal
'Cat'
>>> onmyhead = drsuess[15:] # get the Hat
>>> onmyhead
'Hat'
>>> lastone = drsuess[-1] # last character
>>> lastone
't'
Extractions of portions of a sequence are called slices (for strings, you might know these as substrings). Negative index references refer to the end of the sequence.
Sequences can also hold other sequences, as shown below.
>>> drsuess = ["Horton Hears a Who", "Green Eggs and Ham"]
>>> drsuess[1][0:5]
'Green'
>>> drsuess[1][0:5][2]
'e'
It is important to remember that strings are just sequences. In this example, then, the variable drsuess holds a sequence of two items, each of which are sequences. The double reference drsuess[1][0:5] pulls the second item in the drsuess sequence, which is a sequence, then pulls characters 0 through 4 from that sequence, giving the value Green.
Finally, it is important to remember that, while Python is dynamically typed, it is also strongly typed. This means that the Python enforces the rule that only the operators that are defined for a data type may be used on that data type. Python does not try to figure out how to apply invalid operators; it simply throws an error.
Statements
In Python, as in other languages, a statement is an executable element in the language. It is the element of the language that drives a program's actions.
In terms of syntax, the form of statements in Python is a bit different from other languages. Consider the following statement in Java:
while (x < y) {
x += 2;
y = y - 2;
}
The above while loop statement has a block of statements — surrounded by "{" and "}" — that is it's loop body. The statements are terminated by a semicolon. The entire statement set is flexible; that is, it could be on one line or many, which is irrelevant to the Java compiler.
In Python, the syntax is cleaner, but more restrictive. Consider an equivalent while loop below:
while x < y:
x += 2
y = y - 2
The code is cleaner: the semicolons and brackets of Java are not present here. The parentheses from the Java while statement are not needed in Python. However, the code is also more restrictive. The block of statements formed by Java's use of brackets are now formed by indentation. Python relies on indentation to form statement blocks. In addition, individual statement must appear on individual lines, if semicolons are not used. Finally, note the colon that serves as a terminator to the while statement; colons are used when blocks of code are expected.
You can be more flexible with Python syntax if you want and use semicolons to separate statements from each other. The statement below is identical to the while loop above, albeit the code does not look so clean any more:
while x < y: x += 2; y = y - 2
Consider the simplest statement in Python. It is the "pass" statement. It does nothing, but is used often as filler for an empty statement block. In a language like Java, an empty block is simply an empty set of brackets: "{}". However, in Python, an empty block is indented empty line, which is really not a statement. So "pass" is used as an empty statement. For example, an empty while loop might look like
while x<y:
pass
In summary, it is best to say that Python syntax is different but not necessarily better. Python enthusiasts will get very eloquent on how the language supports all the same constructs as other languages, albeit with cleaner code. It is probably safer to say that Python's syntax serves it purpose well and leave language comparisons to linguists.
Expressions and Assignments
An expression is a combination of object references put together with operators. Expressions create and manipulate objects. Consider the expression below:
x + 15 * L[2]
The variable x is referenced and that reference produces an object whose value and data type match the data contained in x. Using the number 15 in the expression creates an integer object whose value is 15. The list reference L[2] retrieves the third item in the collection named L, creating an object along the way. Note that these objects are nameless.
In Python, the above expression is indeed a valid statement: you can use expressions as statements. All expressions return values; unless the expression causes a side effect, the value is thrown away. In the above expression, no side effects are produced (because no functions are called) a value is computed and discarded.
As long as they contain operators valid for the data types they are working with, expressions can be any combination of objects and operators. Table 2-2 holds some common expressions and their interpretation:
| Expression | Interpretation |
|---|---|
| x + 15 * L[2] | arithmetic expression |
| a < b | boolean expression |
| x + 15 * L[2] < 100 and a < b | compound boolean expression |
| a < b < c | combination of boolean expressions |
| f(x,y) | function call |
These should look familiar if you are familiar with how other languages deal with expressions. Note that precedence rules apply in Python just as they do in other languages. In the first expression, for example, the * operator will be executed before the + operator. Also note the boolean combinational operators; they are spelled out (and , or, and not) rather than specified by symbols as in Java or C++.
Assignment is the operation of changing the object value of a variable. The syntax look just like it does in other languages:
- variable = expression
The expression is computed first, then the resulting object is assigned to the variable specified.
It is useful to note here a common mistake that happens in Python programs. This involves function calls, which are valid statements. Consider the following code:
>>> aList = [10,20,30]
>>> aList.append(40)
>>> print aList
[10, 20, 30, 40]
This works as you might expect. Note that the statement aList.append(40) is an expression, but a valid statement nonetheless. It simply returns the value None, which is discarded.
A common mistake for new programmers is to think this expression can be assigned to a new list, like below:
>>> aList = [10,20,30]
>>> aNewList = aList.append(40)
>>> print aNewList
None
Instead, the new list gets the value None, which is the return value from the function call in the expression.
Finally, note that we have used a useful statement in the examples above. The print statement can be used to generate output based on the expression given to it. The expression is computed and its result is printed. If you are familiar with C or C++, this is the same as using stdout. However, we will also use the print statement to work with files later.
Conditional Statements
All programming languages feature the conditional execution of statements; Python is no exception. Python's if statement features multiway branching.
The general format of the if statement is below:
if <condition1>:
<statementset1>
elif <condition2>:
<statementset2>
else:
<statementset3>
When viewed through what we know as Python syntax, this looks very close to an if statement for many other languages. Here, condition1 is an expression that produces a boolean result. If the resulting value is True, then the statements in the block called statementset1 will be executed. If the resulting value is False, then the next condition is evaluated, and so on. If all the conditions evaluate to False, the default statement set, after the else keyword (here, it is statementset3) is executed. Note the use of the colon after each condition and the indentation of the statement sets. These are Python standards.
Let's take an example in the code below:
theString = "The Mad Hatter"
length = len(theString)
if length < 10:
category = "short"
elif length < 20:
category = "medium"
elif length < 30:
category = "kind of long"
else:
category = "long"
In the example above, the first condition fails because theString has a length of 14, but the second condition is True. Therefore, the variable category takes the value medium.
Boolean expressions in Python resemble those from other languages. They use the standard relational operators (for example, "<" and ">=") as well as boolean operators and , or, and not. Boolean variables may also feature in boolean expressions. For example, the code below is valid:
theString = "The Mad Hatter"
length = len(theString)
toolong = length > 40
printable = not toolong and length > 10
if printable:
print "The string is OK"
Values other than booleans are also valid as boolean expressions. As in other languages (pioneering in C, continued in C++ and Java), you may use numeric quantities as boolean expressions. When a number is used alone, Python translates it into the expression number != 0. So the code below is valid:
theString = "The Mad Hatter"
length = len(theString)
if length:
print "The string has characters"
The numeric value may be an integer or a floating point number. In fact, this idea of non-booleans being considered True is extended to other cases:
- Nonempty objects are considered True.
- Zero numeric quantities and empty objects are considered False.
- The special value None is considered False.
Things get a little complicated when combining non-boolean values with boolean operators. Python uses short-circuiting to evaluate boolean expressions. In other words, when the result can be predicted, Python stops evaluating. For example, the boolean expression False and x == 2 can stop at False, because False and anything is False. So Python stops evaluating and returns the first object it sees that can predict the result of the expression.
Consider this code as an example:
theString = "The Mad Hatter"
length = len(theString)
value1 = length or theString == "The Mad Hatter"
value2 = theString == "The Mad Hatter" or length
value3 = 0.0 or theString == "Mad Hatter"
In this code, value1 takes the value 14. This happens because length is non-zero and thus true, making the boolean expression true. Python stops evaluting and returns that first object that made the expression true. value2 takes the value True, because the comparison is the first true object in the expression and it makes the whole expression have the value true. The variable value3 takes the value False.
Finally, as in C++ and Java, inline if statements are possible in Python. Their form is a little different. Let's take this simple if statement:
if x < 10:
x = 1
else:
x = -1
The inline version of this would be
x = 1 if x < 10 else -1
This functions like a normal if statement: all the rules apply to the boolean expression and the value assigned depends on the boolean value. Note that the entire right-hand side of the "=" operator is an expression; the entire string x = 1 if x < 10 else -1 returns a single value.
| Can You Do This? Using inline if statments, can you write a statement that increments or decrements a variable (say "a") based on another value? If "b == 10" then increment by 2 else decrement by 2. Do this in a single statement. |
Loops
Iterative algorithms are handled by two main looping constructs in Python: while loops and for loops. We will examine these in this section. Because of its use of lists and sequences, Python also expands on looping by using an iteration protocol designed for these constructs. We will look at these as well.
| Where is the goto? It has been shown that all programs can be written with sequential execution, an if statement, and a goto statement. The goto allows execution to shift around through a program's code. It is available in many languages (For example, C++ has a goto) and it usually takes the form of goto label, where the label is located somewhere in a program. Execution of the program jumps to the statement with the specified label. Python does not have a goto statement. Goto statements can result in very poor, very unstructured programming. While some people will argue that goto statements are very handy to use — or even essential — most programmers will say the structured programming is best and that goto statements are not needed if a rich set of looping constructs are available in a language. Python provides such a rich set of looping constructs. For more on the goto statements, see the original work on this subject and a retrospective on the original that is a great review of the subject. |
While Loops
The while loop in Python mimics the while loop in other languages with Python's special spin on boolean conditions. The general format of the syntax looks like:
while <condition>:
<statementset1>
else:
<statementset2>
The basic while loop behavior is to execute <statementset1> while <condition> is true. Using this basic construct, however, many different looping forms are possible. Infinite loops are easy:
while True:
print "Help! I'm running away..."
Most loops will depend on relational operators:
while x < y + 2:
x += 1
y -= 1
while a**2 < b**2 and c+d == e:
a = c - d
b += 1
c -= 2
You may have seen break and continue statements in other languages. C++ and Java both have these. break forces a loop in Python to stop execution at the break statement and to shutdown the loop execution entirely. The continue statement stops the current iteration only and allow the loop to continue execution. Consider the following examples:
a = 20
while a:
a -= 1
if a % 2 == 0: continue
print a
This code will print all the odd numbers from 20 down to 0. The continue statement stops the iteration so that the print statement does not execute.
while a**2 < b**2:
if c+d != e: break
a = c - d
b += 1
c -= 2
This code works just like the simple example above: when c+d != e, the loop shuts down.
| Note a subtle difference Note the subtle difference between the two coding styles. Because Python uses short-circuiting to evaluate boolean expressions, the while loop that starts with
will possibly never execute the right-hand side of the "and" operator. If the the left-hand side evaluates to False, short-circuiting kicks in. However, if the form
is used, then the "c+d != e" is guaranteed to be evaluated. This may not make a difference not, when we are using simple variable relations, but when we start using functions and class methods with side effects, this is distinction will become very important. |
The else part of a while loop is rare in Python and a feature that few other languages have. This part is optional; if included, it will execute when the loop condition evaluates to False and the loop is about to terminate. It only executes when a while loop is terminated in the normal fashion; a break statement will not cause the else part to execute. Consider this example:
number = int(raw_input("Enter number: "))
count = 0
while number:
count += 1
total = total + number
number = int(raw_input("Enter number: "))
else:
print "The average is ", total/count
This code reads numbers until a 0 is entered, then prints the average and exits.
For Loops
for loops are useful when you are iterating over a specific set of objects — for example, a range of integers or a list. A for loop's syntax looks generally like this:
for <index> in <objectsequence>:
<statementset1>
else:
<statementset2>
As in other languages, a for loop executes by assigning the <index> an object from <objectsequence> for each iteration of the loop. For example:
thesum = 0
for x in [1, 2, 3, 4, 5, 6]:
thesum += x
print "Average is ",thesum/6
The output is "Average is 3" as you might expect.
We could write the above using tuples instead of a list:
thesum = 0
for x in (1, 2, 3, 4, 5, 6):
thesum += x
print "Average is ",thesum/6
Any sequence works in a for loop. Below shows a for loops working with sequences of tuples and tuple assignment.:
thetuple = [(10,3.14), (20, 14.5), (30, 17.9), (55, 2.31)]
thesum = 0
for (left,right) in thetuple:
print "Reading ", left
thesum += right
print "Average of lefts is ",thesum/len(thetuple)
This produces the following result:
Reading 10
Reading 20
Reading 30
Reading 55
Average of lefts is 9.4625
So, let's assume that you are a seasoned Java programmer and you need the for loop you are used to. for loops in C++ and Java have a slightly obtuse syntax and are usually used to step through sequences by providing integers in a range used to index arrays or lists. Python does not support that syntax - the example above is the only way to use for loops. However there is a way to generate ranges of integers in lists easily.
Consider this example. In Java, you might have an list of objects that you want to process:
for (int i=0; i < objectlist.length; i++)
process(objectlist.get(i));
To make a similar loop in Python, we need to use the range() function. The Java syntax gives us a counted iteration, where the loop index variable takes on values from 0 to objectlist.length. The range() function will create a list with a sequence of integers in the range given by the function parameters. A loop in Python similar to the Java loop above might look like this:
for i in range(len(objectlist)):
process(objectlist[i])
The range() function here produces a list of all integers in sequence from 0 to objectlist.length. The loop variable takes on all these values as the loop iterates, acting just like the Java for loop.
You can optionally specify a starting number and a step as below:
for x in range(10,100,5):
...
This will iterate for values of x starting at 10 in steps of 5 through 100.
| for loops as while loops It is always useful to remember that for loops are really while loops. Anything you can do in for loop can be done in a while loop. Loops can be rewritten and this actions helps to understand what for loops do. |
As with while loops, the "else" part executes when the loop terminates normally. Also, break and continue statements work with for loops as they do with while loops.
Other Python Iterators
for loops in Python are very general statements. They work with sequences, but that is just a portion of what they do. They can be used with any iterable data type, that is, any data type that can generate a sequence of its values.
The notion of an "iterable" data type capitalizes on the idea that if you have one object of a data type, you can get to the next. For integers, this is easy: if you have an integer "x", the next value is "x+1". For a list, if you are at the fifth value in a list, the next one is the sixth (i.e., "list[5]"). Python uses these ideas for all iterable data types and specifies a syntax for iteration.
Consider the example below:
countries = ["France", "Germany", "United States", "Austria", "Norway"]
iterator = iter(countries)
print iterator.next()
This code creates an iterator for the countries list and begins to step through the values in the list using that iterator. The result of the print statement is "France". Another call to iter(countries) will produce "Germany".
Some data types have iterators built into the type. For examples, file types have iterators:
fd = open("data.txt")
print fd.next()
print fd.next()
print fd.next()
This snippet will print the first three lines in the file "data.txt" (the function fd.readline() would also have worked, but we are not discussing files here).
The enumerate() function is a variation on range generation and is useful for iteration in for loops. The enumerate() function takes a list and generates an iterator over list a tuples: the first item in each tuple is an integer representing the place of the second item in the original list. Consider the example below:
countries = ["France", "Germany", "United States", "Austria", "Norway"]
for (offset, country) in enumerate(countries):
print country, 'is the', offset, 'item in the list'
Again, the use of the enumerate() function produces an iterator, not a list. We could have simply used "iter(countries)", but we would not have the offset included in a tuple. This fragment will produce the result:
France is the 0 item in the list
Germany is the 1 item in the list
United States is the 2 item in the list
Austria is the 3 item in the list
Norway is the 4 item in the list
Here, we should make a note: for loops work with sequences as well as with iterators. Working with sequences may seem to be their primary duty, but the fact that they work with iterators means they are very flexible.
There are some variations on for loops and iteration that might look a little strange at first. For example, if you wanted to generate a list of the powers of 2 between 1 and 5, you might try:
powers = [2**1, 2**2, 2**3, 2**4, 2**5]
This is fine for short lists. However, if you wanted to generate a list of the powers of 2 between 1 and 32, you might do this:
powers = [2 ** x for x in range(32)]
This type of inline for loop is much easier to use and more convenient.
Functions
A function is a collection of statements, treated as a special unit, that can be used many times in a program. Using the group of statements under a function is known as calling the function. When one part of a program calls a function, it can pass data to the function for the function to use; these data are known as parameters. As part of their execution, functions can return values back to the calling statement (but are not required to do so).
We have already seen functions at work. When we have used the range() function in previous examples, we called a function, passed it an integer parameter, and used the list of integers that it returned. Other examples include the next() function for iterators and the enumerate() function. If you have used other programming languages, calling functions should be very familiar.
In this section, we will focus on writing user-defined functions and explaining how the various semantics of functions affect this type of function.
Writing User-Defined Functions
User-defined functions are written using the def statement. The general form of this statement is below:
def <function name>(<parameter list>):
<statementset>
The "<function name>" should take the form of all other names in Python: it must start with a letter and can be made up of letters and numbers after that. The "<parameter list>" is optional and is a list of names that will be used to reference the parameters that are sent during a function call. The "<statementset>" is the block of statements, appropriately indented, that executes when the function is called.
Consider the simple example below:
def listaverage(aList):
thesum = 0
for x in aList:
thesum += x
print "Average is ",thesum/len(aList)
This is a repackaging of an earlier example. We can call this function by executing the statement
listaverage([1,2,3,4,5])
and we would get the output
Average is 2
In the definition, the list "[1,2,3,4,5]" is assigned to the parameter aList and that parameter is used as a variable in the defining code.
| Coding Habits and Discipline It is natural for programmers to develop habits when writing programs. Some like to use a certain style of naming for functions and parameters. Language designers might even use a certain style in designing a language to help programmers write better code. For example, the "declaration before use" rules of C++ and Java are designed to force programmers to be intentional about using variables in those languages. It is important in a language like Python to develop good coding habits and to use discipline as you code to enforce these habits. The language design is loose and will rarely enforce a specific kind of coding practice. Therefore, it is up to you to use good practices. For example, because you may use any names as parameters and variables, it is easy to get these mixed up in a function definition. However, it is good practice to be able to tell them apart. Notice that the name of the parameter in the above example begins with the letter "a" followed by a capital letter. This is to distinguish it as an argument to the function. |
Function definitions are executable and are created at program runtime. This happens because of the interpreted nature of Python. This means that the def statement can occur anywhere any other statement can occur. You can also treat the function name just like a variable and assign it to other names. Consider this example:
if x < y:
def announcement():
print "x is indeed less than y!"
else:
def announcement():
print "Sorry, x is not less than y!"
shout = announcement
shout()
The result of this code fragment, when x is "1" and y is "2" will be
x is indeed less than y!
This might be obvious, but the way the definitions worked is unique to Python. Definitions can be nested inside an "if" statement because they are executable and function objects were assigned to other names because executing a function definition produces a function object. This may be a little contrived for this simple an example, but these kinds of mechanisms will be useful for more complex programs.
Parameters to Functions
While parameters to functions may seem just like variables, they are quite different and deserve some special attention.
When a function call is executed, the values in the call are immediate assigned to the parameters in the function definition, working from left to right in the parameter list. Python passes parameters to functions using two different methods:
- Immutable objects are passed by value. This means the value of the object is assigned to the parameter in the function definition. Any change to the parameter does not affect the original value in the call. Passing by value is a "copy in, do not copy out" type of semantics.
- Mutable objects are passed by reference. This means that the value is not assigned to the parameter, but a reference to the object itself is assigned. This is like a pointer in C++. The difference here is that when a change to the parameter is made, that change is also made — immediately — to the object in the calling statements. Passing by reference also follows "copy in, but do not copy out" semantics.
This example will show the difference between these two.
def swapper(aItem1, aItem2):
temp = aItem1
aItem1 = aItem2
aItem2 = temp
x = "Number 1"
y = "Number 2"
swapper(x,y)
print "x = ", x, " and y = ", y
The results of this code might be surprising. The result of executing this code is
x = Number 1 and y = Number 2
In other words, the values of the parameters did not change.
We can see why the code worked this way by working through the parameter passing. When the call was made, variables — mutable objects — were used. References to these objects were passed. The function did not change the objects themselves, but exchanged the references to the objects and kept that exchange within the function, not passing the exchange back. So the local copy of the references were swapped, but because the values are not copied back out to the corresponding parameters in the call, the swap was effectively ignored.
Because references are passed, functions can indeed change the value of objects. Consider this example:
def swapper2(aItem1, aItem2):
aItem1[0] = 'Hello'
aItem2[0] = 'there'
x = ['xitem0', 'xitem1', 'xitem2']
y = ['yitem0', 'yitem1', 'yitem2']
swapper2(x,y)
print "x = ", x, " and y = ", y
The definition above is different in the fact that it changes the object itself through the reference — not just the references assigned to the parameters. The results of running this code is
x = ['Hello', 'xitem1', 'xitem2'] and y = ['there', 'yitem1', 'yitem2']
Usually parameters are matched from the call to the definition in a left-to-right manner. However, parameters can also be matched by name. For example, if we wanted to use names in the call to swapper2() above, we could use the following call:
swapper(aItem2=y, aItem1=x)
The parameters are actually given in backwards order, but by using names, the Python interpreter can correctly assign values to the parameters in the definition of the function.
You can also mix positional assignment and assignment by name. This can get confusing, so it is best to use one method or the other consistently.
A final note should be made about default parameters. Python allows parameters to be omitted from a function call when the definition of the function allows it. This is specified in the definition of a function by providing parameter definitions with default values. Consider this example:
def sum(a, b=2, c=10):
return (a+b+c)
We could call this function in the normal way: sum(1,2,3) and we would get "6" as a result. However, because default values are specified for b and c, we could also call this function as sum(1) or sum(1,5). In each case, the omitted parameter would be assigned the values given in the definition.
Returning a Value
When using functions, the best way to send values back to the caller is by returning a value. Returning a value is done by executing the "return" statement, like this:
return <expressions>
The <expressions> are computed, the function execution is terminated, and the value is returned to the caller. That value can then be used as any other value or variable reference.
Consider the code below, a proper rewriting of the listaverage() function:
def listaverage(aList):
thesum = 0
for x in aList:
thesum += x
return thesum/len(aList)
Now, a call like listaverage([1,2,3,4,5]) prints nothing but returns the average and can be used in an expression or other statement.
Functions in Python can return multiple values. For example:
def listaverage2(aList):
thesum = 0
for x in aList:
thesum += x
return len(aList),thesum/len(aList)
Here, we return both the length of the list and the average of the numbers. We can use this function like below:
l,a = listaverage2(range(32))
print "l is ", l, " and a is ", a
The result is
l is 32 and a is 15
Scope Rules
As we have seen, Python has the notion of statement blocks — sets of statements, grouped by indentation — used for many statements. Identifier names for variables and other objects can be used in these blocks without declaration. Blocks can be nested inside each other and it can get confusing which identifier is being used at certain times.
These namespaces are called scope and the definitions that define the visibility of identifiers are called scope rules.
Consider an example.
a = 100
def f():
a = 99
f()
print a
The question is: what is the value of a that gets printed?
In order to define scope rules for Python, we need to make some definitions. We first need to make the distinction between global scope and local scope. Global scope is the space of identifiers for an entire Python module or program. All blocks at all levels in a Python program can see and use global names. Local scope is the space for identifiers in the most inner function definition. Between global and local, there is something called the enclosing scope; this is the space of identifiers defined outside of a statement block. And there is one more scope: all built-in Python identifiers are available to all parts of a Python program.
Figure 2-1 shows the relationships between these scope areas. This set of rules is often called the "LEGB scope rule".
Let's make a couple of observations on this LEGB rule before we get into an example.
- Since we do not declare variables in Python, the operation that establishes the block where a name is defined is assignment.
- When a name is used, the LEGB rule defines the lookup sequence that Python will use. Python starts looking for an assignment in the local block, then in the enclosing blocks, then in the global block, then finally in the set of built-in Python definitions.
- A name may be declared global, which places it into the global scope area.
Let us work through a couple of examples.
a = 100
def f():
a = 99
f()
print a
This is the example the opened this section. The question is: what is the value of a that gets printed? To figure this out, we need to know which a the print statement refers to. There are two a definitions, one in the f() function block and one in the global program block. The lookup rule starts in the current block and looks "outward" to outer blocks, so the a is from the global block, which has the value 100. Note that the function changes its own value of a, so it does not affect the global definition of a.
a = 100
def f():
print a
f()
print a
In this example, two values are printed, both refer to global name a. The first print statement needs to find an a to print. The search begins in the local scope, but no assignment defines an a in that scope. So the search moves to the enclosing scope (which happens to be the global scope as well) and there is an a defined there with the value 100. Both print statements will reference this definition of a.
a = 100
def f():
print a
a = a+100
print a
f()
print a
This code actually produces an error. The inner scope of the function needs a definition of a to print. The Python interpreter will look for the definition in the function's scope area and will find it in the assignment statement after the print statement. This will produce an error because the variable is referenced before it has a value. Notice that the interpreter might have used the name from the enclosing scope, but the rule is to use the local scope first.
a = 100
def f():
global a
print a
a = a+100
print a
f()
print a
This code is identical to the previous code except for the global declaration. Now this code works and there is only one version of a used. Because of the global declaration, the assignment statement in the function does not create a new definition, but uses the global definition of a.
We should make one final note about parameters to functions. Parameters are passed to functions as if they were assigned to the local function names of the parameters. This means that parameters are in the local scope area of function. And this means that changing the values of parameters has no effect on definitions in enclosing scope.
| Ramifications of Scope Rules: Redefinition of Names There are many ramifications of scope rules; some are more obvious than others. One ramification is that you can redefine names from an outer scope in an inner scope. You should avoid such redefinitions. The biggest reason is that you will hide the outer definition by the inner definition, making the outer definition inaccessible. For example:
This is the wrong definition of a min() function; it is actually a max() function. However, once this is defined, it will be used in place of the built-in min() function and always return the wrong result. Another reason why redefinition is not useful is that it can get confusing. Constantly redefining the variable mileage, for example, might result in some very complex code, especially if you need to track down a bug that sets the variable incorrectly. |
lambda Functions
There is a way to define a function in Python without giving that function a name through the def statement. This type of anonymous function is defined using a lambda expression.
A lambda expression allows you to express parameters and an expression that defines its return value. The general form looks like this:
lambda <parameterlist>: <expression>
For example, we could use a lambda function like this:
func = lambda a, b, c: (a + b + c) / 3
x = func(1,2,3)
In the code above, we define an anonymous function that returns the average of three numbers. That definition is assigned to a variable and that variable is used to invoke the function. At no time did that function get a name, so if the variable func were to be reassigned, the definition of that function would be lost.
We should note some properties of lambda functions. First, the use of the lambda function is an expression, not a statement. This means that the definition of the lambda function returns a value — a function value — that can be assigned to a variable as we did above. Second, the body of a lambda definition is an expression, not a set of statements. This means that is not as flexible as a def definition.
However, lambda functions can be used conveniently in several situations. They are simpler code constructs than using a def statement. We will see them in use in later chapters as callback functions. One common use of lambda functions is as a jump table. A jump table is typically a dictionary construct with lambda functions as values. Consider this example:
table = {'green': (lambda: x * y),
'orange': (lambda: x + y),
'blue': (lambda: x - y + 2),
'yellow': (lambda: x / y -1)
}
Now we have a table of code. Assume we have a color in the variable color. To reference the function that corresponds with that color, we would reference
table[color]()
This is much more convenient that a series of if statements.
Classes and Objects
We have talked about the various components of object-oriented programming and now it is time to define how Python embraces it.
Object-oriented programming has several components:
- Objects: An object is the centerpiece of object-oriented programming. An object wraps a data component with operations on that data as defined by a class.
- Classes: A class is a template for an object. It consists of descriptions of the operations an object can perform and the definition of the structure of the object's data component. Classes exist in a hierarchical structure to permit inheritance to take place. If one class is a subclass of another, it inherits the data structures and operations from the parent.
- Methods: Methods are the operations that can be performed on a data component in an object.
So an object is a combination of data structures and methods that work on those data structures, as defined by a class. Python permits each of these components to be defined and used. In Python, objects that are used for execution are called instances and are built from class definitions.
Defining Classes
The general form of class definitions in Python looks like this:
class <name>(<superclass>,...):
<shared data section>
<method section>
Since that looks very generic, we should look at an example.
class Account():
def __init__(self, aStartingAmount):
self.amount = aStartingAmount
def deposit(self, aDepositAmount):
self.amount += aDepositAmount
return self.amount
def withdraw(self, aWithdrawlAmount):
self.amount -= aWithdrawlAmount
return self.amount
def balance(self):
print self.amount
This is a class called Account that is meant to describe a bank account. It has a data object called amount that is initialized in this class to have the value aStartingAmount in the __init__() method. When an instance object is created, the __init__() method — called a contructor — is called if it exists. The creation of an instance must contain the parameters specified by this constructor.
Method definitions are just function definitions. Since we have already reviewed functions, you know about methods!
Notice that each method in the above definition has at least one parameter. This parameter refers to the instance of the class that has been created. We use this instance above in references to data; when we use self.amount, for example, we are referring to the amount variable that exists in a specific instance of this class, referred to by self. Programmers in C++ and Java will recognize the self variable as the this object.
We can use this class as follows:
bankaccount = Account(1000)
bankaccount.balance()
bankaccount.deposit(250)
bankaccount.withdraw(100)
bankaccount.balance()
The first line of the code creates an instance object called bankaccount from the class object called Account. We initialize the amount of the bankaccount object to 1000 by including it in the instance creation statement. This calls the constructor and passes it the value 1000.
The remainder of the example above uses methods from the class. The results of running the above code is below:
1000
1150
Inheritance
Inheritance occurs when classes are defined in terms of other classes and, in doing so, take their data components and methods. This establishes a relationship between classes that can be exploited and allows for software reuse. Inheritance is one of the most useful and important properties of object-orientation and Python supports it fully.
Let's say that we want to define two more banking classes: CheckingAccount and SavingsAccount. We could go through the same definitions as we did with the Account class and each of three classes would have their own definitions. However, it would be advantageous of these classes had a relationship: if we could use one of these new classes in places where Account used. In fact, a CheckingAccount is simply an Account; a SavingsAccount is an Account with a special definition for withdrawl that you cannot go below $100.
We can define these subclasses like this:
class CheckingAccount(Account):
pass
class SavingsAccount(Account):
def withdraw(self, aWithdrawlAmount):
if (self.amount-aWithdrawlAmount > 100):
self.amount -= aWithdrawlAmount
return self.amount
In this definition, CheckingAccount has the same definition that Account has and therefore only includes the "pass" statement (class definitions need a block of statements). SavingsAccount is defined in terms of Account, but redefines the withdraw() method.
Here, anytime an Account is required, we can use CheckingAccount or SavingsAccount, because they inherit the Account type. For example, let's write a function that transfers money between two accounts:
def transfer(aAmount, aAcct1, aAcct2):
aAcct2.deposit(aAcct1.withdraw(aAmount))
This code would work equally well with Account, CheckingAccount, or SavingsAccount.
Often the hierarchical nature of the inheritance relationship will be exploited to provide a programming interface of "expected" method implementations. This way of providing an interface will assume a method exists or will provide a method but leave its implementation empty.
Let's redefine the Account class as below:
class Account():
def __init__(self, aStartingAmount):
self.amount = aStartingAmount
def deposit(self, aDepositAmount):
self.amount += aDepositAmount
return self.amount
def withdraw(self, aWithdrawlAmount):
if (self.amount-aWithdrawlAmount > self.limit):
self.amount -= aWithdrawlAmount
return self.amount
def balance(self):
print self.amount
This code now has the withdraw() method based on a variable called self.limit, which is not defined by the Account class. This makes the Account class unusable for regular programming and turns it into an abstract class, one that is used as a base class to define other classes and to force those classes to have a specific definition. Now, to be usable, both CheckingAccount and SavingsAccount must define self.limit. Here is the new definition of these classes:
class CheckingAccount(Account):
def __init__(self, aStartingAmount):
self.amount = aStartingAmount
self.limit = 0
class SavingsAccount(Account):
def __init__(self, aStartingAmount):
Account.__init__(self,aStartingAmount)
self.limit = 100
Both definitions define a self.limit variable that is assumed by the Account definition.
Notice how the above definitions have different ways of using the parent class. The CheckingAccount definition rewrites the __init__() method completely; the SavingsAccount definition calls the parent class' __init__() method before making refinements. The latter approach is best because it does not assume a definition in the parent class. If the __init__() method changes in the parent class, the CheckingAccount definition will be incorrect while the SavingsAccount definition will still be correct.
Operator Overloading
One specialized way of working with class methods is through operator overloading. It is possible to overload — or redefine — operators in Python classes. This applies to very basic common operators used in the built-in data types as well.
For example, consider the following definitions:
class MyNumber:
def __init__(self, aInitialValue):
self.value = aInitialValue
def __add__(self, aValue):
return MyNumber(self.value + aValue)
Now, the operator "+" can be used with instances of the MyNumber class. We can now execute the following:
x = MyNumber(100)
y = x + 100
And y.value will have the value "200". While is might not be surprising, the beauty of the above code is the we used the "+" operator. This is a nice way to provide intuitive operators when classes demand it. If a class denotes a collection of objects, for instance, redefining the "[]" operator would be an intuitive thing to do.
Almost all operators in Python can be overloaded in this manner. See the Python documentation for a list.
| Overloading Accounts It would make a lot of sense to use "+" as deposit() and "-" as the withdrawl() definition in the Account definitions above. Can you show how these definitions would incorporate these operators? |
Multiple Inheritance
We should make a note here about Python's ability to use multiple inheritance. We discussed inheritance and showed how a subclass can inherit the properties of a parent class. In Python, this inheritance relationship can extend to multiple parent classes.
Multiple inheritance is useful when a class needs access to the data objects and methods of more than one class. Let's say that a Python program has a set of definitions that define, among other objects, an Employee class and a Student class. There might be a class that defines student employees, which would have to inherit both Employee and Student class properties. Again, let's say that a user interface has a class that depicts items on a menu and buttons on a page. There would also be classes that inherited aspects from both of these classes to put buttons on items on menus.
Muliple inheritance is specified in Python by putting multiple classes in the class statement. For example, we could define
class MenuItemWithButton(MenuItem, Button):
...
The MenuItemWithButton class has two parents and must implement the proper abstract properties from both.
It can happen that some of the classes that are inherited have properties with the same name. In the above, both MenuItem and Button classes could have a method named isPressed or setVisible. The rule for which one is referenced is the "left to right" rule: the classes specified in the class statement are searched in specification order left to right. So the setVisible method from the MenuItem would be used for that method unless it is redefined for the new class.
| Multiple Inheritance Considered Controversial Like some other language features (e.g., the goto statement), multiple inheritance is a controversial feature. Some languages (C++, Python) include it while others (Java,C#) specifically exclude it. Proponents find it useful and actually necessary to properly depict certain object-oriented relationships; detractors find it confusing and not needed for most programs. Some languages (Java) implement multiple inheritance, but cloak it behind language features designed to encourage better programming (in Java, the interface concept is an attempt to incorporate single inheritance with a cleaner multiple inheritance implementation). |
Exception Handling
Exception Basics
Exceptions are events that occur in a program that define a condition that has arisen that needs to be dealt with. These events are typically errors that occur during the execution of a program, but they can also be used to implement any type of situation that can modify the execution of a program. The key feature of an exception is the need for that exception to be handled. Unhandled exceptions will halt the execution of a program.
Python has a rich set of language constructs that implement exceptions. The most basic of these features is the try/except sequence of statements. This is the general format:
try:
<statementset1>
except <name1>:
<statementset2>
except <name2>,<data>
<statementset3>
except (name3, name4):
<statementset4>
except:
<statementset5>
else:
<statementset6>
finally:
<statementset7>
Using exceptions is very flexible. The statement starts by attempting to execute the statements after the try statement, i.e., those in "<statementset1>". If these statements execute without raising an exception, then the statements in the optional else part — those in "<statementset6>" above — are executed and execution moves on. If an exception is raised, however, the except parts are searched for the name of the exception. When the proper exception is found, statements corresponding to that except part are executed. If the name of the exception is not found, the statements under the generic except part — those in "statementset5" above — are executed. We can even use a form of the except statement to get more information about the exception; this is where the "<data>" form is used. The finally part is always executed.
Consider an example. A very common programming error is division by zero. This has an exception associated with it:
a = 10
b = 0
try:
c = a/b
except:
print "Oops...something bad happened"
This code will produce the message "Oops...something bad happened" and it should be obvious why this exception was raised. In Python, this exception has a name: "ZeroDivisionError". We can look for this exception specifically with the following code:
a = 10
b = 0
try:
c = a/b
except ZeroDivisionError:
print "Oops...something bad happened"
This code produces the same result. Extra information can be obtained by adding a variable to the except part. In our simple case, this extra information is an error message that explains the error:
a = 10
b = 0
try:
c = a/b
except ZeroDivisionError, message:
print "Oops...something bad happened"
print message
This code will produce the output
Oops...something bad happened
integer division or modulo by zero
Let's conclude this simple example with an else statement.
a = 10
b = 10
try:
c = a/b
except ZeroDivisionError, message:
print "Oops...something bad happened"
print message
else:
print "Hey! Something good happened"
This code prints the message in the else statement, because the division completes successfully.
With the way that the try statement is structured, multiple exceptions can be handled, common ways to complete the statement can be implemented. Multiple exceptions can be handled in the same way by naming them all in the except part.
There are many built-in exceptions in Python. They all have names and depict a specific kind of error condition. While they are too numerous to list here, note that Python always prints the name of the exception when giving an error message. So, if you were use a division by zero in a Python program, you might get an error message like this:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Note the name of the exception in the last line.
Raising Exceptions
There are lots of built-in exceptions, but you can also raise your own exceptions. This is a useful feature when you want to force certain conditions (like errors) to be handled by programmers using a programming interface. The way you raise exceptions is with the raise statement. There are several ways to use this statement:
raise <exceptionname1>
raise <exceptionname2>,<value>
These should make some sense given the way that we can use the try statement to catch exceptions. The first form is the most obvious; this form raises the named exception. This name can be a built-in exception, a string that you make yourself, or a class name or instance.
The second form gives the exception name and a string value that serves as additional data. This string is usually an error message that augments the exception.
Let's take a couple of examples. Consider an addition to the Account class definition above (we will reiterate the two child classes for clarity):
BalanceError = 'BalanceError'
class Account():
def __init__(self, aStartingAmount):
self.amount = aStartingAmount
def deposit(self, aDepositAmount):
self.amount += aDepositAmount
return self.amount
def withdraw(self, aWithdrawlAmount):
if (self.amount-aWithdrawlAmount > self.limit):
self.amount -= aWithdrawlAmount
else:
msg = "Account is overdrawn by "+str(-(self.amount-aWithdrawlAmount))
raise BalanceError,msg
return self.amount
def balance(self):
print self.amount
class CheckingAccount(Account):
def __init__(self, aStartingAmount):
self.amount = aStartingAmount
self.limit = 0
class SavingsAccount(Account):
def __init__(self, aStartingAmount):
Account.__init__(self,aStartingAmount)
self.limit = 100
Notice that we added several things. First, we have a global string that identifies "BalanceError". We will use this as our exception. Second, we added an "else" part to the withdraw() method that creates an error message in the variable msg and raises the BalanceError exception by referencing the BalanceError. Now we can use the following code to appropriately catch this new exception:
newacct = CheckingAccount(100)
try:
newacct.withdraw(200)
except BalanceError,errormsg:
print errormsg
Exceptions and Propagating
When exceptions are raised, they must be handled or eventually the program will terminate. Exceptions are propagated, that is, they work their way through the calling sequence in a Python program. Any of the functions in the calling sequence can handle the exception.
For example, if a function "funcA" called "funcB", which called "funcC", you would have a calling sequence like that in Figure 2-2:
Let's say that an exception is raised in "funcC". Python will use an exception handler in "funcC" first, if one exists. If one does not, it will look for one in "funcB" and so on up the calling chain.
Importing Modules
It would be awkward if every Python program were a single file. Python programs depend on many built-in definitions and on code written by others. So it makes sense to split code definitions up into multiple files. This is where importing modules comes in.
Python program file names end in a .py suffix. This program files are known as modules. When other program files references items from modules, this is known as importing those modules. The contents of modules are known as attributes and can be imported as a large unit or can be imported individually.
As an example, let's assume that the Account class definitions above, with the children classes CheckingAccount and SavingsAccount and the BalanceError exception, are stored in a file called "Account.py". If we create a new file that creates an instance of the SavingsAccount class, we will need to import definitions from the "Account.py" file, as below:
import Account
myaccount = Account.SavingsAccount(1000)
myaccount.balance()
The import statement made the attributes defined in the "Account.py" file available to the current program. We reference the module's attributes — in this case, the SavingsAccount class — using the same dot notation we use to reference class attributes.
We can avoid this dotted notation and be a little more specific in our importing by using the from keyword with the import statement, as below:
from Account import SavingsAccount
myacct = SavingsAccount(1000)
myacct.withdraw(2000)
Executing the last line will raise an exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "Account.py", line 17, in withdraw
raise BalanceError,msg
BalanceError: Account is overdrawn by 1000
Importing module attributes can get a little tricky. Let's say that we want to trap the BalanceError exception in try statement, like this:
from Account import SavingsAccount
try:
myacct = SavingsAccount(1000)
myacct.withdraw(2000)
except BalanceError, errormsg:
print errormsg
We get an error here:
Traceback (most recent call last):
File "accttest.py", line 6, in <module>
except BalanceError, errormsg:
NameError: name 'BalanceError' is not defined
This is because we imported the SavingsAccount attribute, but no others. And we reference the BalanceError name without importing it. We can get around importing every attribute individually by using the following syntax for the import statement:
from Account import *
try:
myacct = SavingsAccount(1000)
myacct.withdraw(2000)
except BalanceError, errormsg:
print errormsg
By using "import *", we import every attribute and do not have to use the dot notation to reference them. This is the most commonly using form of the import statement.
You can also import an entire directory of files and definitions. This called a package import. It is a somewhat advanced feature of Python, so we will not describe it here.
Miscellaneous Python Elements
As we conclude our brief overview of Python, there are a few miscellaneous items we should mention.
The first is docstrings. Docstrings are a special form of strings that can appear in program files and function definitions. They are treated like comments: they are ignored at execution time. However, they are used and displayed by tools that work with Python, including the Python runtime environment. Docstrings are comments that are surrounded by three quote marks, like below:
def sum(a,b,c):
'''
This is a contrived example to show
off docstrings
'''
return(a+b+c)
When the Python runtime system parses a docstring, it stores it as the __doc__ attribute for the item being defined. Therefore, you can print documentation by using the following code:
>>> print sum.__doc__
This is a contrived example to show
off docstrings
This works for the sum() function we just described. You can also do this for built-in definitions. For example, the function max() is built into the Python runtime system and we can get documentation on by doing the following:
>>> print max.__doc__
max(iterable[, key=func]) -> value
max(a, b, c, ...[, key=func]) -> value
With a single iterable argument, return its largest item.
With two or more arguments, return the largest argument.
The help() function is built into the Python runtime system and is defined to print docstrings if they exist.
This brings us to the second item we should discuss. The dir() built-in function allows us to explore the runtime environment to determine the definitions that currently exist. This function has two forms: without parameters, it will print the names of all definitions; with one parameter, it will print the attributes of the name given by that parameter.
For example, if you wanted to see all the definitions built into the sum() function we described above, you could call dir(sum) after you defined it. You would get something like this:
>>> dir(sum)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
Usually, definitions that begin and end with the "__" character sequence are system-defined. All of these definitions can be used in Python code.
| Explore! We cannot go over each of these attributes in this chapter. So, go ahead and explore these yourself. Try using the help() function on these, like help(sum.__init__). You will get several screens of documentation. |
Finally, a note about program formats and lines. As you know by now, Python is designed to have one statement per line and to use indentation to group statements together. This means that if you want to give a statement that takes multiple lines, Python might mistake the extra lines as a new block and find the code to be in error. The way to make statements span multiple lines is to put the "\" character at the end of lines that will be continued.
For example, this code is in error:
if (x < y and
y > z and
a > y):
...
but this code is fine:
if (x < y and \
y > z and \
a > y):
...
Conclusion
This chapter has provided a brief overview of Python's basic elements, covering many of the most import aspects of the language:
- Variables, along with data typing and operations on data
- Expressions and assignment operations
- Statements, including conditional statements and iterative looping constructs, with an introduction to Python iterators
- Functions, including parameter passing, scope rules, and lambda functions
- Object oriented features, including classes, inheritance, and operator overloading
- Exceptions
While many of these concepts can be found in other programming languages, Python provide has a unique and developer-friendly perspective on them.
Comments
Sign in to comment…




