664 PythonBasics PDF
664 PythonBasics PDF
664 PythonBasics PDF
2014
c Mark Wickert
November 2, 2014
Contents
1 Introduction 2
2 Before Numpy 2
2.1 The Environment and Choices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 Launching the IPython Notebook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.2 Launching the IPython QT Console with the Canopy Editor . . . . . . . . . . . . . . 5
2.1.3 Launching the IPython QT Console From the Terminal . . . . . . . . . . . . . . . . . 6
2.1.4 Launching the Native Python Console From the Terminal . . . . . . . . . . . . . . . . 7
2.1.5 Ending Your Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 Data Types and Simple Calculations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.1 Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 float, complex, long, int, str, and boolean . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.2 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3.3 Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5 Formatting Strings and Gathering User Input . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5.1 Formatting Strings and Printing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5.2 Gathering User Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6 Flow Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6.1 If, elif, and else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6.2 For Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6.3 While Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.6.4 The Statements break and continue . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.7 Exceptions: try, except, and finally Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.8 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.9 Object Oriented Python: Writing a Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.9.1 Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.9.2 Writing a Simple Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3 After Numpy 27
3.1 NumPy Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.1 The N-Dimensional Array and Available Types . . . . . . . . . . . . . . . . . . . . . . 27
3.1.2 Array Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.1.3 Working With Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1
5 Signals and Systems Tools and Examples 34
5.1 The Scipy Module scipy.signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.2 The Signals and Systems for Dummies Module ssd.py . . . . . . . . . . . . . . . . . . . . . . 35
5.3 Modules for Digital Communications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.4 A Simple DSP Class Case Study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.4.1 The class Code Base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.4.2 Lowpass and Bandpass Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6 References 42
1 Introduction
This tutorial is structured around the idea that you want to get up and running with Python using PyLab
as quickly as possible. The first question I asked my myself before I started using PyLab was why consider
Python? What makes it a vialble alternative to other languages available for scientific and engineering
computations and simulations? OK, everyone has favorites, and presently MATLAB is very popular in the
signals and system community. Is there a need to change? This is a debate that lies outside the scope of
this tutorial, but the ability to use open-source tools that work really, really well is very compelling.
To answer the first question, why consider Python, I can say:
3. The SciPy library of modules, particularly signal, provides reasonable suppost for signals and systems
work. Additional libraries of modules are also available
2 Before Numpy
I have been saying a lot about using Python with Numpy as a means to do scientific and engineering analysis,
simulation, and visualization. The fact of the matter is, Python is a good language for doing many other
things outside the computational realm.
Numpy plus Scipy are key elements to the attractiveness of using Python, but before getting too carried
away with the great scientific computing abiliies of the language, you should learn some basics of the language.
This way you will feel more comfortable at coding and debugging.
Before exploring the core language, I will spend time going over the environment and various choices.
2
2.1 The Environment and Choices
How you choose to work with Python is up to you. I do have some strong suggestions. But first I want to
review four options in order of most recommended to least recommended. My recommendations assume you
are just starting out with Python, so I have a bias towards the IPython notebook.
The first thing you want to do is get a version of Python with scientific support installed. A good place
to get started is the IPython homepage installation site.
Out[13]:
3
From the above you can see that the notebook is all set. Note that the first cell is only relevant if you
intend to render your notebook to pdf using the LaTeX backend. This requires that you install Pandoc and
then an appropriate install of the TeX/LaTeX type setting system. The Pandoc Web Site provides details.
The second cell issues commands to fine tune the configuration of the notebook. The first line makes
sure the workspace is populated with %pylab, which gives you full access to NumPy and Matplotlib without
4
having to type the module name or namespace. Note: commands that begin with % are known as IPython
magics, which in general allow you to perform OS operations outside the default (see option four) Python
console. The option inline directs all plots to show up right in the notebook. If you prefer pop-up plots,
enable the second line. The resolution of the embedded png plots can be controlled using the third line.
The fifth line, if uncommented and run as a magic (put % at start) with change the render mode from
png to pdf. This will then result in a link to plots that opens them in a pdf viewer. For LaTeX to pdf
rendering, this will create crisp vector graphics. I recommend using this only when you get ready to export
a notebook to pdf. You will have to use Run All from the Cell menu to convert all graphics to pdf and
then switch back later to again have regular inline plots.
The two import lines just bring my ssd (Signals and Systems for Dummies ssd.py module into the
workspace). Note: for this to be sucessful ssd.py must be in the same folder as the notebook you are
working from. Once you import a module you can navigate to another location in your file system. By the
way, IPython magics make general OS path manipulation a breeze. Some of then don’t even require that
you forst type %. You do need to know basic Linux/Unix OS commends. I show you a few examples below:
/Users/wickert/Documents/Documents
In [55]: %ls
If you are reading the present document in pdf format, you should consider downloading the notebook
version so you can follow along with interactive calculations and experiments, as you learn Python Basics.
Moving on to the QT console. . .
Out[14]:
5
From the above figure you can see the top window is a code editor with Python syntax highlighting and
other features. This is an Enthought (makers of Canopy) product. They plan to someday have a debugger
included with the editor.
The lower window is the IPython console. By enabling the the Keep Directory Synced to Editor option
you can freely move around to import code modules from various locations and always have the path in
command console where you want it. By default when Canopy opens the editor it starts the qtconsole woth
pylab. It also by default has all plots going to pop-up windows. The inline plots mode for the qtconsole is
available, but not that great compared to the IPython notebook.
6
Note: The option --pylab is used to start up pylab as was done in earlier environments. Everything
else you see in the above figure is very similar to the Canopy editor with qtconsole.
Out[9]:
7
2.1.5 Ending Your Session
Not mentioned up to this point, is how you end a Python session.
• In the notebook you use the File menu and select Close and Halt
• On both the qtconsole and the traditional Python console you type exit()
Note: The () are required since exit() is a function that takes no arguments.
Now its finally time to start discussing some language details. . .
As I roll through basics be aware that comments begin with # and multiline comments look like
"""
A multiline comment
The second line
The third line
"""
More of discussion of import and modules will occur later. Until I start talking about NumPy I will
keep all he topics limited to what you can do with native Python. Note: If you need to contiune a line you
can use \ (backslash). You cannot break a string this way. You can also break lines at commas.
8
2.2.2 float, complex, long, int, str, and boolean
The Native Python data types are actually rather few. When Numpy is brought in this changes, but for
now that is not the case.
Float In signals and systems work the float type is actually is actually a double as found in the C
language. This means it consumes 64 bits (8 bytes) of memory. A float is created by simply including a
decimal point in the number.
In [71]: a = 1.2
b = 4.603
a*b
Out[71]: 5.523599999999999
To be sure you can use the built-in function type(). To compare several calculation I will string together
several calls to type() with parenthesis and commas in between. This way I can display the rults all on one
line. Note: I have actually created a compount type known as a tuple. More on tuples later.
Out[88]: tuple
The native operations available with float types are given in the following table. The table order is
from lowest to highest precedence.
A frustration with Python 2.7 (what I am currently using in this IPython notebook), is that when do
perform simple division such as
x = 6/7
9
thinking you will form a float, you instead get and integer. In the example above you get 0. In Python
3.3 this is resolved. I am making a big deal about this because over and over again I get tripped up.
So what can you do? In Python 2.7 I find it best to just remember to put a decimal point on one of
the two numbers when working with ratios of integers in math calculations. A hard conversion to float is
possible using the native function float(), e.g.,
x = 6/7.
# or
x = 6/float(7)
Another option is to import the new Python 3.3 division rule into Python 2.7 as follows:
Out[98]: (0.8571428571428571, 0)
Note: It is strongly recommended that this import be placed before any other imports. Also, with this
division change, when you really want integer division you need to use //. More on the int type coming up.
Complex Another standard type to Python is complex. √ For signals and systems work, complex plays a
significant role. The constant 1j gives you access to −1, and allows you to readily form complex quantities.
In the following example I will again create a tuple just for the convenience of displaying multiple results
without using a formatted print statement.
The convenience of built-in complex arithmeic is very nice. I need to mention however, that getting access
to functions beyond the operators listed in the table above, requires the import of specific code modules. The
math and cmath bring in additional functions for float or real numbers and complex numbers respectively.
Don’t get too excited about jumping in to use these modules. With NumPy, which will be talked about
later, the use of math and cmath is taken care of for you. AN with NumPy you will have full vector/matrix
support. I just mention it here so you know it does exist, and if for some strange case you don’t want to use
NumPy, this is what you will have work with.
Int and Long For integer math, indices, and loop counters, Python has the types int and long. The int
type is a signed integer similar to int in the C language. The size of int depends upon the machine you are
running on. If you import the sys module you can find out more information about int for your machine:
Out[93]: 9223372036854775807
On a 64-bit OS the maximum value should be like 264−1 −1, accounting for the fact that one bit is needed
for the sign and since zero is represented you have to stop one value short of 263 .
The native math capability of Python goes one step further via the long type. The long type offers
unlimited size! Furthermore if you are working with an int type and perform an operation that exceeds
the maximum size, it will converted to a long integer for you. Loop counters however, are bound to the
maximum size on int. There are work arounds for this too.
In [94]: x = 34
(type(x),x) # display two results, again using a tuple
10
Out[94]: (int, 34)
In [95]: y = x**32
(type(y),y) # display two results, again using a tuple
Out[95]: (long, 10170102859315411774579628461341138023025901305856L)
In [99]: 1-y
Out[99]: -10170102859315411774579628461341138023025901305855L
Notice in the above examples that long integers are displayed with L appended to the end.
Other Bases In computer engineering you often need to work with other bases.
Bitwise Operations Along with the display of integers in other formats, Python also supports the
bitwise operations shown in the following table.
In [19]: #SVG(’Python Basics_files/Bitwise_table.svg’)
Note: If you search the Internet you will find little helper functions to
allow you to represent hex values with proper sign extension.
String String creation and manipulation in Python is a breeze. In signals and systems work string manip-
ulation often shows up when working with formatted printing, on the screen or in a text file, and in labels
for plots.
The ability to mix fixed text with formatted numbers, on the fly, is very handy when you have to tabulate
analysis and simulation results. Formatted print strings will discussed when I discuss the print() function.
Presently the basic of type str are discussed.
Formally a string in Python is a sequence of immutable characters. Immutable means the values of the
string cannot be changed. You can easily create a new string from an existing string, and this is where you
can introduce changes you may desire.
A string can be indicated using: (1) single quotes (2) double quotes, or (3) triple quotes to create a string
block.
xa = ’Bat’
xb = "Bat"
xc =\
"""
Many bats flying
through the air.
"""
11
In [124]: xa = ’Bat’
xb = "Bat"
xc =\
"""
Many bats flying
through the air.
"""
# Use a tuple to display some results
(xa,type(xa),xb,xc)
Out[124]: (’Bat’, str, ’Bat’, ’\nMany bats flying \nthrough the air.\n’)
Note: The multi-line string has embedded line feed, \n, characters.
Single and double quotes are interchangeable and are useful when you want to preserve quotes that belong
in a string, e.g.,
The number of characters in a string can be found using the len() function, while individual characters
can be accessed using brackets, e.g., xd[3].
Indexing can be used to obtain substrings. The indices are integers which run from 0 to len()-1. To
generate substrings use brackets, i.e.,
In [121]: len(xd)
Out[121]: 20
The table below sumarizes basic string manipulation, including the fun topic of slicing. Slicing returns
with native Python lists, tuples, and NumPy ndarrays.
12
Native string operations
Name Operation Quick Example
xa + xb
Concatenate/ >>> ‘Hello’ + ‘ ‘ + ‘World’
to concatenate
adding ‘Hello World’
strings
x*n or
Replicate/ n*x >>> ‘Bat’ * 3
multiply to replicate a ‘BatBatBat’
string n times
x[n] >>> x = ‘Bat’
x[-1] >>> x[1]
the end value ‘a’
Indexing x[-2] >>> x[-1]
the second t
from the end
x[n:m] >>> x = ‘Bright colors’
the substring >>> x[1:6]
from n to m-1 ‘right’
x[:m] >>> x[7:]
the substring ‘colors’
from 0 to m-1 >>> x[:-1]
x[n:] ‘Bright color’
>>> x[::2]
the substring
‘Bih oos’
Slicing: from n to the
In the above the third argument is the
Many forms end
optional stride (the default, if not
possible x[n:-1]
given is 1) factor which controls the
the substring
step size as you run from 0 to the
from n to
end-1 end in this case, since only :: is
x[n:-2] given.
the substring
from n to
end-2
x[n:m:k]
Note: Indexing and slicing will work the same way when wiring with
Python lists and tuples, and the Numpy ndarray.
There are many functions for searching and modifying strings. Too many to cover here. If you feel the
need, do some searching on your own. As a specific example, consider breaking a string down into substrings
and then put back together in a differnt form. Below I use find() to do some simple string parsing to assit
in the tear-apart:
In [120]: xd[0:5] + xd[xd.find(’beau’):xd.find(’day’)-1]
Out[120]: "It’s beautiful"
Boolean The boolean type has it place in making logical decisions in program flow. In Python the boolean
type holds either True (1) or False (0). You will see booleans in action when I discuss program flow. Logical
operation as used in program flow control return booleans. A few simple examples follow:
In [161]: b1 = True
b1 > 1
Out[161]: False
In [162]: 34 > 0 and 4 < 3
Out[162]: False
In [163]: 34 > 0 or 4 < 3
Out[163]: True
13
2.3 Data Structures
Python’s native data structures of interest here are lists, tuples, and briefly dictionaries. All three
of these data structures are sequences that can be thought of as containers for Python objects. The most
import object you will be using is the ndarray, which I have made mention of many times. Although note
mentioned in the section on string, they are also sequences of characters.
2.3.1 Lists
Simply put, a list is a mutable (changable) sequence of objects. A list can be created using brackets:
In [146]: l1 = [1,’abc’,23.4]
l1
Indexing and slicing of lists works the same as with strings. In fact a list can hold strings as you see in
the above example.
When I introduce for loops a little bit later, you will encounter a list object containing integers. With
regard to for loops, the native function range(), is frequently used to create a list of integers. Consider the
examples below:
n1 = range(start,stop,step) # = [start,start+step,start+2*step,...]
n2 = range(20) # = [0,1,2,...,19]
If start is omitted the sequence starts at 0. If step is omitted the step size is 1. Note step may be
negative.
The fact that lists are mutable means I can write
n1 = range(10)
n1[4] = 20 # replace the 5th element with 20, not a problem
In [169]: n1 = range(10)
n1
Out[169]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [170]: n1[4] = 20
n1
In [136]: (type(n1),len(n1))
List can contain lists, and so on. Below I create n2 as a list of two lists made from subsequences of the
original n1.
In [137]: n2 = [n1[:5],n1[5:]]
n2
In [144]: n2[1][:3]
Out[144]: [5, 6, 7]
14
There are methods (functions) associated with list objects. In engineering/scientific applications of
Python you need to be aware of lists, but explicit use of lists beyond range() (or the memory conserv-
ing xrange()) is minimal, as NumPy’s ndarray is more powerful. As a simple example consider sort()
which places the list elements in ascending order:
List Comprehensions Indexing through lists and performing calculations is a frequent task, at least
without NumPy. Python allows you to combine looping and list manipulation into one operation.
Below is a simple example that returns a list of numbers corresponding to 3 + 4n + n2 for 0 ≤ n ≤ 10.
Out[173]: [3, 8, 15, 24, 35, 48, 63, 80, 99, 120, 143]
When you use list comprehensions you can write very terse Python code. I encourage you to explore list
comprehensions as you feel more comfortable with the language. With NumPy the list comprehension still
provides a convenient way to fill a list or array with values of interest, but again NumPy has its own ways
too, that most likely are even faster.
2.3.2 Tuples
A tuple is like a list, but it is immutable (not changable). Your first reaction to this might be ‘what good is it
if I can’t change it’. It turns out that the immuatabilty aspect is perfect for the needs of engineering/scientific
computing.
Creating a tuple can be done using parenthesis much like you do with lists. One significant difference
is that a single element tuple requires a comma so as not to be confused with the ordinary use of parenthesis.
In [147]: t1 = (1,2,23.5,’abcd’)
t1
In [159]: t2 = (27)
t2 # This is not a one element tuple
Out[159]: 27
In [160]: t3 = (34.5,) # the comma does it, its a one element tuple
t3
Out[160]: (34.5,)
In [156]: type(t3)
Out[156]: tuple
Trying to change a value of a tuple element fails, as you can see from the following:
In [151]: t1[1] = 56
15
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-151-4a896955a572> in <module>()
----> 1 t1[1] = 56
A typical use of the tuple is as a return from a function call. Each element of the tuple can be an object
such as a list or with NumPy an ndarray. You can then unpack the tuple into its constituent objects, say
a frequency array and a frequency response array. Further analysis follows.
Suppose you have a function that returns a tuple of two lists. List 1, denoted l1, containing numbers
and list 2, denoted l2, containing characters, you can unpack the tuple into two lists as follows:
In [153]: l1
Out[153]: [0, 1, 2, 3, 4]
In [154]: l2
2.3.3 Dictionaries
A dictionary is a mutable (changable) sequence of values that is addressable using a name/key. The key
needs to be unique, but the value does not. Dictionaries like lists are mutable.
The motivation for introducing dictionaries at this time is because some of the numerical algorithms in
SciPy return dictionaries. If you should need to use one of these algorithms, then you will need to know
something about dictionaries.
To create a dictionary using braces to create {key : value} pairs.
Out[180]: dict
You can now access the dictionary elements using the keys:
In [177]: weekdays[’wednesday’]
Out[177]: 3
Dictionaries have a collection of associated functions. For example, you can list the keys using the keys()
method:
In [179]: weekdays.keys()
16
Out[179]: [’tuesday’, ’thursday’, ’friday’, ’wednesday’, ’monday’]
If you have a dictionary but don’t know whats inside, you can list() it as tuples. The order of the list
is the hash ordering, which is an internal order scheme for fast retrieval.
In [181]: weekdays.items()
2.4 Variables
You have already seen variables in action, but there are some fine points you need to know about them.
Variable names can contain characters, numbers, and underscores. Variables cannot begin with a number.
Since everything in Python is an object, all objects have an address. If you declare a structure variable
(say a list) it is given an address. If you later set the list variable name equal to the first variable you will
not be creating a new object. Rather you create a new reference to the same object. Python does have a
copy method for these instances when you really do want a copy.
In [184]: a1 = [0,23.4]
a2 = a1
(a1,a2)
Notice in the above reassignement of the first element of list a2, the values held by a1 have followed.
In other words a1 references the same object. To insure you actually make a copy, you can use some form
of copy method. For Numpy ndarrays seen later, there is a copy() method. For lists you can use a4 =
list(a3) to make a copy:
In [190]: a3 = [2,17]
a4 = list(a3)
(a3,a4)
In [193]: a4[1] = 20
(a3,a4,’<== It works!’)
17
2.5.1 Formatting Strings and Printing
Being able to control the format of numers displayed both on the screen and in files allows you to effectively
communicate the results of your Python analysis and simulation. Python supports two approaches: string
interpolation and formatting strings. I will be showing just string interpolation, as to me it is very easy to
pick up, as it follows from a background in C. Formatting strings do give more control.
To print a string to the console/terminal, IPython qtconsole, or IPython notebook, you use the print()
with a formatted string as the argument, or in many cases all rolled up into one statement. A string
interpolation expression takes the form format % values, where format is a string and values is a tuple
of values. The % character present in the string format indicates a value should placed in the string using a
format specification.
Consider the following simple example:
In [197]: v1 = 3.141516
v2 = 2*v1
print(’v1 = %6.4f and v2 = %2.4e’ % (v1,v2))
v1 = 3.1415 and v2 = 6.2830e+00
Note: The values to be formatted are contained in the tuple following the % character. The formatting for
the values always follows a % as well. Don’t be confused, there are multiple uses of % in string interpolation.
If you simply wanted a string for use in plot labels, etc. you can write:
In [198]: str1 = ’v1 = %6.4f and v2 = %2.4e’ % (v1,v2)
str1
Out[198]: ’v1 = 3.1415 and v2 = 6.2830e+00’
As I said from the start, string interpolation is very much like string formatting in C. The format string
specifications are given is the table below.
In [21]: #SVG(’Python Basics_files/Printformat_table.svg’)
When for loops are introduced in the next section you will see how nice tabular lists of data can be
prepared. As a quick example which iterates over the list [-23, 34, 1004] consider:
In [203]: # Use of format specifications; also not line continue via \
for k in [-23,34,1004]:
print(’Decimal: %o, Decimal padded: %4d, Hex: %x,\
Hex string: %s’ % (k,k,k,hex(k)))
18
Decimal: -27, Decimal padded: -23, Hex: -17, Hex string: -0x17
Decimal: 42, Decimal padded: 34, Hex: 22, Hex string: 0x22
Decimal: 1754, Decimal padded: 1004, Hex: 3ec, Hex string: 0x3ec
In [202]: (val,type(val))
Running Scripts with Command Line Arguments as Inputs There are times when you may want
to write a Python script that you can run from the command line or perhaps have another program call.
As an example, I have written GUI apps in another language that bring together both command line C++
executables and Python script outputs.
A Python script is a *.py file containing code you might ordinarily type at the Python or IPython
prompt. You run the script right from the terminal provided by your OS:
Note: You can also run scripts from IPython using the %run magic, i.e.,
Note: one or more command line argument may be supplied following the script file name. The script
is actually a Python code module that may contain functions as well as a script body, that will run from the
command line. Any functions in the module can be used by importing the modules namespace into your
Python (IPython) environment using:
import my_script
A sample script that reads four command line arguments is given below. This script imports methods
from the sys module for reading the command line arguments and the os module to allow the full path to
the script to be discerned. Having the full path comes in handy when you want to read or write files from
your script and you have called the script from another directory, say even via another program.
#!/usr/bin/python
"""
cmd_line_test.py
A simple command line script program taking four arguments:
string = a file_name, e.g. data_set.txt
int = an interger loop variable
float = a calculations variable
19
float = a second calculations variable
"""
Sample command line:
>>>python cmd_line_test.py cmd_test_results.txt 5 109.8 -34.567
"""
# Read command line arguments and convert as needed
if len(argv) < 4+1: # argv[0] is the script name itself
print(’error: Need 4 command line arguments!’)
print(’User provided only %d.’ % len(argv))
exit(1)
else:
out_file = argv[1]
N_loops = int(argv[2])
value1 = float(argv[3])
value2 = float(argv[4])
# Do something with the collected inputs
print(’Echo commandline inputs back to user:’)
print(’argv[0] = %s’ % argv[0])
print(’argv[1] = %s’ % out_file)
print(’argv[2] = %d’ % N_loops)
print(’argv[3] = %6.4f’ % value1)
print(’argv[4] = %6.4f’ % value2)
# For reading and writing files you may want the full path
print(’FYI, the path to your script is:’)
print(’%s’ % app_path)
# Create an empty N_loops x 2 2D array
output_data = np.zeros((N_loops,2))
for k in xrange(N_loops):
output_data[k,0] = value1 + k*10.0
output_data[k,1] = (value1 + k*10.0)/value2
np.savetxt(app_path + out_file,output_data)
Running the above script from the terminal prompt results in:
20
argv[3] = 1823.6900
argv[4] = -38276.7600
FYI, the path to your script is:
/Users/wickert/Documents/Documents/IPython_notebooks
A quick look at the file sample output.txt reveals a nice list of two columns separated by a space.
1.823690000000000055e+03 -4.764483723282744027e-02
1.833690000000000055e+03 -4.790609236518451192e-02
1.843690000000000055e+03 -4.816734749754159051e-02
...
The complementary Numpy function loadtxt() (discussed later) can easily load a text file into ndarrays,
using a variety of options.
Note: this script has also used a numpy method that makes it easy to write ndarrays to a text file. More
will be said about reading and writing ndarrays to files in the NumPy chapter.
This is also where one of the unusual aspects of Python comes to light, that of code indenting. Indenting
and unindenting code by 4 four spaces is the standard. Python code editors are set up this way, or you can
make it so if not.
Indenting must be consistent all the way through a code block inthe IPython notebook or in general in
code module file. It is easy to mess up your indenting, so be careful. This is an area that a newcomer is
likely to get frustrated with. Hang in there, it gets better with practice.
In this section I cover if, elif, else blocks, for loops, and while loops. What I will leave for self
study is try, else, and finally blocks.
21
if condition1:
block1
elif condition2:
block2
...
elif conditionN:
blockN
else:
elseblock
All code blocks must be indented (by convention 4 spaces and not a tab) from the if, elif, else
statements. A condition can be passed over by including the pass statement in place of an actual block.
Coding continues following the elseblock by outdenting. No blank lines required. AT first this seems
strange, but you get used to it. The Canopy code editor as well as the editor used for code in the IPython
notebook help get you up to speed.
In [210]: my_value = 10
if my_value <= 4:
print(’I am in the first block!’)
elif my_value > 4 and my_value <= 8:
print(’I am in the second block!’)
else:
print(’I am in the default block!’)
I am in the default block!
In [217]: modeA = ’Green’
modeB = ’hot’
if (modeA.lower() == ’green’) and (modeB.lower() != ’cold’):
print(’What I am looking for!’)
else:
print(’No match!’)
print(’Entered a new block due to outdent’)
What I am looking for!
Entered a new block due to outdent
22
Get Two For One by Using the Iterator enumerate When you process float values in a loop your
frequently need to use both the sequence index and the value itself. The loop interation contruct that I
really like makes use of the Python iterator enumerate. Consider:
The iterator enumerate returns both an index to x and the value at the corresponding index, in that
order. Check it out in the notebook:
while condition:
while_block
23
As a simple example consider:
5
6
7
8
9
10
Here I break at 2
i = 0
i = 1
Here I continue at 2
i = 0
i = 1
i = 3
2.8 Functions
To me the heart and soul of any programming language is the ability to write reusable functions. In Python
functions are written using a def construct.
24
Function docstring to describe the purpose and variable input/output
"""
function_body # The function body must be indented
return one_or_more variables # The use of return is optional
Note: The return statement does not have to appear at the end of the function. You can actually return
from multiple locations if you need to. The bottom line is the function does end when it reaches a return
statement.
Arguments to the left can be given default values. If say two arguments are given default values and you
want to overide the lasgt value only, you must explicity refer to the last value in the function call and give
it a value:
def my_f1(a,b,c=5,d=25):
function_body
return a + b + c + d
25
2.9.2 Writing a Simple Class
In a separate code module or right here in the Notebook, you write a class as follows:
Note: You see self everywhere when you write a class. All objects and data must be preceeded by self
and every class method (function) must begin with a reference to self. Forgetting self somewhere in your
class definition is a fairly common error. Be on the look out for this error.
26
In [23]: # Use the repr method to give the representation of person1
person1
In [26]: str(person1)
Out[26]: ’Person John started at 1414386667.63s, \nwhich corresponds to year 2014, month 10, and day 26.
In [27]: person1.service_time()
Out[27]: 17.271279096603394
3 After Numpy
With Python basics taken care of, now its time to move on to the real focus of using Python for science and
engineering. NumPy (Numerical Python) is an open-source Python library for numerical computing. When
you combine NumPy with MatPlotLib and SciPy, and the IPython console or notebook app, and you really
have a very powerful set of tools. The full NumPy documentation.
The writing for the NumPy section is far from complete. At present I have placed many tables.
1. Numpy Fundamentals
2. Working with 1D Arrays
a. Signals
b. Systems
dir(numpy)
27
The structure of the ndarray
attributes
dim count How many dimensions
Elements per dimension:
dimensions
(n, m, …)
Bytes per row, p,
strides
bytes per element, q
The data memory block
data with row and element
boundaries
q
e0 e1 e2 e3 e4 e5 e6 … … …
p
From outside
In [33]: # Here I use the ‘array()‘ method (see Array Creation below)
A = array([1., 34.,-345, 98.2])
A
In [34]: A.dtype
Out[34]: dtype(’float64’)
Out[36]: (4,)
When using PyLab, which makes the IPython environment similar to MATLAB, you work with ndarrays
in a very natural manner. The default data type for floats is double precision or 64 bit (128 bits for complex).
Many other data types can be used to make more efficient use of memory. The table below lists the types
and makes mention of how you declare types and perform casting from one type to another.
28
Available ndarray data types set by dtype
Type Description
bool Boolean (True or False) stored as a bit
int8 Byte (-128 to 127)
int16 Integer (-32768 to 32767)
int32 Integer (-2**31 to 2**31 - 1)
int64 Integer (-2**63 to 2**63 - 1)
uint8 Unsigned integer (0 to 255)
uint16 Unsigned integer (0 to 65535)
uint32 Unsigned integer (0 to 2**32 - 1)
uint64 Unsigned integer (0 to 2**64 - 1)
float16 Half precision float: sign bit, 5b expo, 10b mantissa
float32 Single precision float: sign bit, 8b expo. 23b mantissa
float64 Double precision float: sign bit, 11b expo, 52b mantissa
Complex number, represented by two 32-bit floats (real &
complex64
imag)
Complex number, represented by two 64-bit floats (real &
complex128
imag)
Note: The highlighted types are the defaults on a 64-bit OS. Type casting
is possible using methods such as y = uint32(x), etc.
29
Creating NumPy ndarrays
Method Description Example
>>> a = array([1,2,3,4])
This is the core method used to
will create an int64 array
create ndarrays from a list. The
>>> a =
array() dtype argument is good for array([1,2,3,4],dtype=float16)
setting the per element data will create a float16 array
type
>>> a = ones(20)
ones(n1) Will create an array of specified
a 20 element 1D array
or length (n1 or n1xn2, etc)
>>> a = ones((5,4))
ones((n1,n2)) containing all ones as 1D,2D, …
a 5x4 2D array of ones
Similar to ones() except fills >>> a = zeors(20)
zeros()
array with zeros a 20 element 1D array
Create a new array of zeros or
ones_like() >>> a = ones(10)
ones that replicates the shape
zeros_like() >>> b = zeros_like(a)
of the input argument
Special 1D Array
Creation Methods
Create an array of values
arange([start, ] running from start to stop- >>> x = arange(0,5,0.5)
stop[,step] step, where step is the step creates an array of floats [0,0.5,1.0,…,4.5]
size
Create an array of linearly
linspace(start,s >>> x linspace(1,2,6)
spaced of num values running
top,num=50) creates the array [1.0,1.2,1.4,1.6,1.8,2.0]
from start to stop
Create an array of log spaced of >>> x = logspace(0,1,10)
logspace(start, num values running from creates the array [1. , 1.291, 1.668, …,
stop, num=50)
10start to 10stop 5.995, 7.743, 10.]
Note: I frequently use arange() to create index vectors and initialize arrays using zeros() and/or
zeros_like().
Tip: If you add step to stop in arange() the final value will be stop.
In [25]: ‘
30
Popular methods for working with ndarrays
Function Description Example
Logical
all() True if all elements are nonzero >>> x =
array([0,1,2,0,4,5])
all(x) = False
any() True if any (at least one) elements are nonzero >>> x =
array([0,1,2,0,4,5])
any(x) = True
find() Return the indices where ravel(condition) is >>> x =
true array([0,1,2,2,1,7])
find(x >= 3) =
array([5])
Slicing 1D arrays
(a few cases)
x[n:m] The 1D subarray from n to m-1 >>> x =
x[:m] The 1D subarray from 0 to m-1 array([0,1,2,3,4,5])
x[n:] The 1D subarray from n to the end x[:2] = array([0,1])
x[n:-1] The 1D subarray from n to end-1 x[::3] = array([0,3])
x[1:-2] = array([1,2])
x[n:-2] The 1D subarray from n to end-2
x[n:m:k] The 1D subarray from n to m-1 with k index
striding
Slicing 2D arrays
(a few cases)
x[n:m,j:k] The 2D subarray from n to m-1, j to k-1 >>> x =
x[n:m,:] The 2D subarray from 0 to m-1, all columns array([[0,1,2],
x[:,j:k] The 2D subarray all rows, columns j to k-1 [3,4,5]])
x[n:m:o,j:k:l] The 2D subarray with striding by o and l in x[:2,:2] =
array([[0,1],[3,4])
rows and columns respectively
x[-1,-1] =
x[n:-1,:] The 2D subarray from n to end-1, all columns array([[5]])
x[:,j:-2] The 2D subarray all rows, columns j to k-2
x[3,:] The 2D subarray row 3, all columns
x[:,0] The 2D subarray all rows, column 0
31
Popular methods for working with ndarrays (cont.)
Function Description Example
Shape &
Concatenation
reshape() Reshape a 1D or 2D array to a new shape; the >>> x = arange(0,5)
new shape must be consistent. 1D 6 elements
y = reshape(x,(2,3))
2D 2x3 elements
concatenate() Join a sequence of arrays together. The arrays >>> x =
must have the same shape except in the axis array([[0,1,2,3,4,5]])
used for combining. axis=0 is rows, axis=1 is 2D 1x6 elements
columns. concatenate((x,x),
axis=0)
2D 1x6 elements
concatenate((x,x)),
axis=1)
2D 1x12 elements
hstack() Stack arrays horizontally. A subset of >>> x =
concatenate() array([[0,1,2,3,4,5]])
2D 1x6 elements
x = x.T #transpose
y=hstack((x,x))
2D 6x2 columns
vstack() Stack arrays vertically. A subset of >>> x =
concatenate() array([[0,1,2,3,4,5]])
2D 1x6 elements
y=vstack((x,x))
2D 2x6 columns
flatten() Values of the argument array become a 1D >>> x =
array. May be done in-place with x.flatten() array([[0,1,2,3,4,5]])
x.flatten()
1D 6 element
transpose() or Like matrix transpose for 2D arrays. In-place >>> x =
array.T via x.T. array([[0,1,2,3,4,5]])
x.T
2D 6x1 array
32
Popular methods for working with ndarrays (cont.)
Function Description Example
Many other standard functions, e.g., trig,
Math
are also available for array operations
mean(x) The sample mean of the values contained in >>> x =
array x. array([0,1,2,3,4,5])
mean(x) = 2.5
var(x) The sample variance of the values contained >>> x =
in array x. array([0,1,2,3,4,5])
var(x) = 2.9167
std(x) The sample standard deviation of the values >>> x =
contained in array x. array([0,1,2,3,4,5])
std(x) = 1.7078
sum(x) The sum of the values contained in array x. >>> x =
array([0,1,2,3,4,5])
sum(x) = 15
prod(x) The sample mean of the values contained in >>> x =
array x. array([0,1,2,3,4,5])
prod(x) = 0
cumsum(x) The sample mean of the values contained in >>> x =
array x. array([0,1,2,3,4,5])
cumsum(x) = array([0,
1,3,6,10,15])
cumprod(x) The sample mean of the values contained in >>> x =
array x. array([1,1,2,3,4,5])
cumprod(x) = array([1,
1,2,6,24,120])
min(x) The sample mean of the values contained in >>> x =
array x. array([0,1,2,3,4,5])
min(x) = 0
max(x) The sample mean of the values contained in >>> x =
array x. array([0,1,2,3,4,5])
max(x) = 5
conj(x) The sample mean of the values contained in >>> x = array([2+5j])
array x. conj(x) =
array([2+5j])
x.real & The real or imaginary part of the values >>> x = array([2+5j])
x.imag contained in array x. Also real(x), imag(x) x.real = array([ 2.])
imag(x) = array([ 5.])
33
Popular methods for working with ndarrays (cont.)
Function Description Example
To and From Files
and Conversion
x.tofile(fname) Writes an array to a binary file. Assume a >>> x =
float type. This a quick means to save array([0,1,2,3,4,5],dty
data in binary form, but not very robust. pe-float64)
x.tofile(‘x_arr.bin’)
Reads an array from a binary file. >>> x =
Assumes a l=float type by default. fromfile(x_arr.bin)
x =
Undoes the operation of tofile (see returns
fromfile(fname)
above) array([1.,2.,3.,4.,5.])
dir(signal)
Out[31]:
[ ’abcd_normalize’, ’absolute_import’, ’argrelextrema’, ’argrelmax’,
’argrelmin’ , ’band_dict’, ’band_stop_obj’, ’barthann’, ’bartlett’,
’bench’ , ’bessel’, ’besselap’, ’bilinear’, ’blackman’,
’blackmanharris’ , ’bode’, ’bohman’, ’boxcar’, ’bspline’, ’bsplines’,
’buttap’ , ’butter’, ’buttord’, ’cascade’, ’cheb1ap’, ’cheb1ord’,
’cheb2ap’ , ’cheb2ord’, ’chebwin’, ’cheby1’, ’cheby2’, ’chirp’,
’cmplx_sort’ , ’cont2discrete’, ’convolve’, ’convolve2d’,
’correlate’ , ’correlate2d’, ’cosine’, ’cspline1d’, ’cspline1d_eval’,
’cspline2d’ , ’cubic’, ’cwt’, ’daub’, ’decimate’, ’deconvolve’,
’detrend’ , ’dimpulse’, ’division’, ’dlsim’, ’dltisys’, ’dstep’,
’ellip’ , ’ellipap’, ’ellipord’, ’fftconvolve’, ’filter_design’,
’filter_dict’ , ’filtfilt’, ’find_peaks_cwt’, ’findfreqs’,
’fir_filter_design’ , ’firwin’, ’firwin2’, ’flattop’, ’freqresp’,
’freqs’ , ’freqz’, ’gauss_spline’, ’gaussian’, ’gausspulse’,
34
’general_gaussian’ , ’get_window’, ’hamming’, ’hann’, ’hanning’,
’hilbert’ , ’hilbert2’, ’iirdesign’, ’iirfilter’, ’impulse’,
’impulse2’ , ’invres’, ’invresz’, ’kaiser’, ’kaiser_atten’,
’kaiser_beta’ , ’kaiserord’, ’lfilter’, ’lfilter_zi’, ’lfiltic’,
’lombscargle’ , ’lp2bp’, ’lp2bs’, ’lp2hp’, ’lp2lp’, ’lsim’,
’lsim2’ , ’lti’, ’ltisys’, ’medfilt’, ’medfilt2d’, ’morlet’,
’normalize’ , ’np’, ’nuttall’, ’order_filter’, ’parzen’,
’periodogram’ , ’print_function’, ’qmf’, ’qspline1d’,
’qspline1d_eval’ , ’qspline2d’, ’quadratic’, ’remez’, ’resample’,
’residue’ , ’residuez’, ’ricker’, ’s’, ’savgol_coeffs’,
’savgol_filter’ , ’sawtooth’, ’scoreatpercentile’, ’sepfir2d’,
’signaltools’ , ’sigtools’, ’slepian’, ’spectral’, ’spline’,
’spline_filter’ , ’square’, ’ss2tf’, ’ss2zpk’, ’step’, ’step2’,
’sweep_poly’ , ’symiirorder1’, ’symiirorder2’, ’test’, ’tf2ss’,
’tf2zpk’ , ’triang’, ’unique_roots’, ’vectorstrength’,
’waveforms’ , ’wavelets’, ’welch’, ’wiener’, ’windows’, ’xrange’,
’zpk2ss’ , ’zpk2tf’]
dir(ssd)
Out[30]:
[’BPSK_tx’, ’CIC’, ’NRZ_bits’, ’NRZ_bits2’, ’OA_filter’,
’OS_filter’ , ’PN_gen’, ’am_rx’, ’am_rx_BPF’, ’am_tx’,
’biquad2’ , ’bit_errors’, ’cascade_filters’, ’conv_integral’,
’conv_sum’ , ’cpx_AWGN’, ’cruise_control’, ’deci24’,
’delta_eps’ , ’dimpulse’, ’downsample’, ’drect’, ’dstep’,
’env_det’ , ’ex6_2’, ’eye_plot’, ’fft’, ’fir_iir_notch’,
’from_wav’ , ’fs_approx’, ’fs_coeff’, ’interp24’, ’line_spectra’,
’lms_ic’ , ’lp_samp’, ’lp_tri’, ’m_seq’, ’mlab’, ’my_psd’,
’peaking’ , ’plot_na’, ’plt’, ’position_CD’, ’prin_alias’,
’pylab’ , ’rc_imp’, ’rect’, ’rect_conv’, ’scatter’, ’signal’,
’simpleQuant’ , ’simple_SA’, ’sinusoidAWGN’, ’soi_snoi_gen’,
’splane’ , ’sqrt_rc_imp’, ’step’, ’ten_band_eq_filt’,
’ten_band_eq_resp’ , ’to_wav’, ’tri’, ’upsample’, ’wavfile’,
’zplane’ ]
35
In [52]: from __future__ import division #provides float div as x/y and int div as x//y
import numpy as np
import scipy.signal as signal
import ssd
# Create an FIR filter object around the signal.firwin method
class FIR_filter(object):
"""
An FIR filter class that implements LPF, HPF, BPF, and BSF designs using
the function signal.firwin.
"""
self.N = order # The number of filter taps is N+1
self.f_type = f_type # ’lpf’, ’hpf’, ’bpf’, ’bsf’
self.fc = array(cutoff) # The cutoff freq in Hz; two cutoffs for bpf & bsf
self.fs = fsamp # In Hz
# Choose a window from from the type in the signal catalog
self.window = window_type
# Design the filter
# Note under some circumstances the end coeffients may be almost zero
# or zero. In these cases trim the filter length and report that the
# requested filter order was not not achieved. The threshold for removing
# coefficients is b_eps
b_eps = 1e-10
if f_type.lower() == ’lpf’:
if len(self.fc) == 1:
self.b = signal.firwin(self.N+1,2*self.fc/self.fs,
window=window_type,pass_zero=True)
else:
print(’For LPF only one cutoff frequency required’)
elif f_type.lower() == ’hpf’:
if len(self.fc) == 1:
self.b = signal.firwin(self.N+1,2*self.fc/self.fs,
window=window_type,pass_zero=False)
else:
print(’For HPF only one cutoff frequency required’)
elif f_type.lower() == ’bpf’:
36
if len(self.fc) == 2:
self.b = signal.firwin(self.N+1,2*self.fc/self.fs,
window=window_type,pass_zero=False)
else:
print(’For BPF two cutoff frequencies required’)
elif f_type.lower() == ’bsf’:
if len(self.fc) == 2:
self.b = signal.firwin(self.N+1,2*self.fc/self.fs,
window=window_type,pass_zero=True)
else:
print(’For BSF two cutoff frequencies required’)
else:
print(’Filter type must be LPF, HPF, BPF, or BSF’)
#Remove small or zero coefficients from the end of the filter
if self.b[0] < b_eps and self.b[-1] < b_eps:
self.b = self.b[1:-1]
print(’Effective/realized filter order = %d’ % (len(self.b)-1))
"""
WRITE ANY ADDITIONAL INITIALIZATION CODE HERE
"""
37
elif mode.lower() == ’groupdelay’:
"""
Notes
-----
def pz_plot(self,auto_scale=True,size=1.5):
"""
Write doc string
"""
"""
Write code here
"""
pass
def impulse_resp(self):
"""
Write doc string
"""
"""
Write code here
"""
pass
def step_resp(self):
"""
Write doc string
"""
38
"""
Write code here
"""
pass
def firfilt(self,x,reset=False):
"""
Write doc string
"""
"""
Write code here
"""
pass
def decimate(self,x,M,reset=False):
"""
Assuming the filter design is lowpass of the appropriate bandwidth,
follow LPF filtering with downsampling by M.
"""
"""
Write code here
"""
pass
def interpolate(self,x,L,reset=False):
"""
Assuming the filter design is lowpass of the appropriate bandwidth,
upsample by L then LPF filter. A gain scale of L is also included.
"""
"""
Write code here
"""
pass
The key features of the class at present is that it can design lowpass, highpass, bandpass, and bandstop
FIR filters using the window method. Once a filter object is created using say
fir = FIR_filter(31,’LPF’,(100,),1000)
you can then use methods to plot the frequency response magnitude in dB and the frequency response
phase in radians.
Notice that code place holders are present for adding more methods to the class:
39
9. Not shown is rational number rate changing.
10. Not shown is a means to choose alternate FIR types such as equal-ripple (remez) and frequency domain
sampling (fir2).
Making a Standalone Module The code has imports listed at the top should you desire to place it in a
module by itself. There is one detail missing however. Any of the current commands that plot, i.e., plot()
or stem() will require some rework in a standalone code module. YOu will want to changes the import
section of the module to look something like:
All three matplotlib imports are needed, but it is plt that you will directly work with for doing plotting
inside the module. Take a portion of the frequency response plotting method for example. In the following
code listing I have added or augmented five lines:
The changes need to be made throught the class definition so it can draw plots when methods are called
from FIR filter objects. This of course assumes you have imported the module into your IPython notebook
or IPython qt console session.
In [75]: # Bandpass: N = 64 or 65 Taps, fs = 1000 Hz and fc1 = 200 Hz, fc2 = 300 Hz
fir2 = FIR_filter(64,’BPF’,(200,300),1000)
You may wonder in the above BPF design what the message *effective filter order of 62 is all about.
With the windowed FIR design approach, it is possible for the first and last coefficients to be very small or
even zero. This effectively reduces the filter order by two. In the filter constuctor I remove these coefficients
to reduce the calculation count and reduce the filter delay.
40
Frequency Response Magnitude Plots Verify that the frequency response magnitude in dB method
does indeed work.
In [76]: fir1.freq_resp()
fir2.freq_resp()
ylim([-80,0])
grid()
legend(((r’FIR1 (LPF)’,r’FIR2 (BPF)’)),loc=’best’).get_frame().set_alpha(0.8)
Frequency Response Phase Plots Verify that the frequency response phase in radians method does
indeed work.
In [77]: fir1.freq_resp(’phase’)
fir2.freq_resp(’phase’)
grid()
legend(((r’FIR1 (LPF)’,r’FIR2 (BPF)’)),loc=’best’).get_frame().set_alpha(0.8)
41
Note: The neat matplotlib legend feature (.get frame().set alpha(0.8)) that allows the transparency
so the plot lines can be seen behind the legend frame. Here the opacity is 80% (100% or 1.0) means not
opaque.
This is a cross-reference link to Mark Lutz, just to verify that it can be done.
6 References
Python Converage (core language only no NumPy or SciPy)
[1]: Mark Lutz, Python Pocket Reference, 5th edition, O’Reilly, 2014. On Amazon
[2]: Toby Donaldson, Visual QuickStart Guide Python, Third Edition, Peachpit Press, 2014. On Amazon
42