Skip to content

PyBCSession04

Katy Huff edited this page Jan 29, 2012 · 9 revisions

Other Wiki Pages:

PyBc, Session01, Session02, Session03, Session04, Session05, Session06, Session07, Session08, Session09, f2py, swig, Cpython, Cython, PyTables, PyTaps, PythonBots, Django, GIS, AdvancedPython, WxPython, StandardLib

Modules, Scripts, Packages and Classes

Python Boot Camp 2010 - Session 4 - January 12

Editing files

In this session, we are going to be creating and editing several ascii text files. If you are already familiar with editing text files (MS Word is not a text editor), use whatever makes you happy. Otherwise, we are going to use the python IDE IDLE. IDLE is a simple, but complete IDE. Its editor does syntax highlighting and simple code completion, block commenting and highlighting. It has a built in python shell (which we are ignoring) and a debugger (which we are also ignoring). Learning to use a text editor/IDE well is worth the time investment, so we highly encourage you to explore IDLE in between sessions.

IDLE on Windows, python(x,y)

from the start menu open:

Start->All Programs->Python(x,y)

Applications->IDLE->Green Check

Image(IDLE_win.jpg, 200px)

IDLE on Windows, EPD

Start->All Programs->EPD->idle

IDLE on MacOS

from a terminal, execute

>>> idle

You can also open idle from spotlight.

IDLE on Linux

from a terminal, execute

>>> idle

You should get something that looks like this:

Image(IDLE_newWindow.jpg, 300px)

Now open up a new window and begin writing. The python shell is unnecessary for our purposes and you may close it. Be sure to save your files with a .py suffix and place them someplace sensible. The python interpreter can only see source files in the current working directory. If you're using ipython, you can Print the current Working Directory with the command "pwd". You can Change Directories using the cd command. If you get lost, cd with no argument returns you to your home directory.

Modules, Packages and Scripts (oh my)

If it hasn't already been pointed out, the official python documentation and [http://docs.python.org/tutorial/ tutorial] is really fantastic. They probably describe things better than I do. You should check it out.

This session is really about organization. We're going to learn how python organizes source code into modules, how library developers organize modules into packages and how to organize your own code into classes. The best way to learn is to do. Doubly so with programming.

Modules and Scripts

[http://docs.python.org/tutorial/modules.html Module Section of the Python Tutorial]

In previous sessions, you're written a few functions for this and that. In each case your function definitions and executable statements all comfortably and logically fit into a single source file, though you have seen examples where you bring in functionality from elsewhere to use in your program. (import math, for example). In this section we will walk you through the syntax and vocabulary of modules and packages.

Hands on Example

On the desktop, create a directory called Session4. Navigate an ipython session to the Session4 directory. In this directory, write a function that takes a string in the format 'xxx-xxx-xxxx' and returns a string with the '-'s removed. Name your source file phonenum.py and place it in the Session4 directory. In the same file, write another function that takes a string (an assumed phone number) in the format 'xxxxxxxxxx' and return the area code as a string. One last thing, define a string named test_str to be '608-473-1286'

Now create a second file called test_phonenum.py that uses your definitions in phonenum.py and tests that they work as advertised. Print "works!" if all your test succeed and "broken!" if they don't.

#!CodeExample
#!python
def format_phone_number(phone_string):
    formatted_string = ''
    for char in phone_string:
        if char in '0123456789':
            formatted_string += char
    return formatted_string

def area_code(fstring):
    return fstring[0:3]

test_string = '608-473-1286'
#!CodeExample
#!python
import phonenum as ph
ph.format_phone_number(ph.test_string)='6084731286'
ph.area_code('6084731286')='608'

Congratulations! You've just intentionally written your first module. In ipython import the phonenum module and start exploring.

::
In [1]: import phonenum In [2]: dir(phonenum)

A module is just a single file of python source code. That's it. "Module" carries the implication that it is intended for reuse, but quality and reuseability of the code is really up to the author. A few more things about modules.

  • Modules can contain function and class definitions (more on classes later)
  • Modules can (and should) have documentation a la docstrings (more on docstrings later)
  • Modules can contain executable code
  • Modules have their own scope. Things defined within a module only know about things defined within the module itself.
  • Unless you explicitly import another module
  • Module named module_name comes from the source file module_name.py
  • A "script" is just a module that is intended to be executed directly rather than imported

Our phonenum module is nice, but it is kinda hard to use. help(phonenum) and help(area_code) are kinda sparse. What it really could use is some documentation. Python has a built in documentation convention called docstrings. "A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the !__doc!__ special attribute of that object." You are encouraged to use """triple double quotes""" to delimit your docstrings. """ delimited strings can span multiple lines, so expanding your documentation is really easy. For more information on using docstrings, you should check out [http://www.python.org/dev/peps/pep-0257/ PEP 257].

Hands on Example Go write some documentation and verify that it works with help.

  • Docstrings are awesome
  • A Module is only executed once, the first time it is imported
  • You can reload a module if you need to re-import

Something that can be confusing to a python initiate is that modules are only executed once. So if you have a 300 module program and each module imports string, you don't take a huge performance hit just from importing. It also means that while you're developing and testing a module, repeatedly importing it is going to do nothing. Lets illustrate this.

Hands on Example

In your phonenum module, put a command to print something, perhaps "Guaranteed to break the ice at parties!" Open up a brand new ipython session, import phonenum, then import your test script. Now change your print statement to something else, "Nudge, nudge, wink, wink, say no more, say no more!" Reload the phonenum module.

Coding with style

from x import *'ing is considered sloppy and can quickly pollute your namespace and lead to surprising name resolutions (did you want to use math.cos or numpy.cos). So it is recommended that you not use it. However in simple scripts, it is probably OK.

Testing your codes is possibly more important than the code itself. Writing good and extensive test suites is good practice and very pythonic.

Hands on Example

In your phonenum modules write a function that tests your two functions and returns True if things behave as expected.

We've already discussed the somewhat arbitrary line between modules and scripts. A common python idiom is that if a module is executed as a script, it should execute a suite of unit tests. We can achieve this behavior by adding the following code to our module. The top level python namespace has the name (__name__) "__main__". Consequently this code only executes when the module is executed as a script.

#!CodeExample
#!python
if __name__ == "__main__":
  if test_phonenum():
    print "Everyting works!"
  else:
    print "Something is broken!"
#!CodeExample
#!python
"""phonenum defines function for manipulating phone number strings"""

print "Nudge, nudge, wink, wing, say no more, say no more!"

def format_phone_number(phone_string):
  """format_phone_number formats a string in the format 'xxx-xxx-xxxx'
to 'xxxxxxxxxx'"""

    formatted_string = ''
    for char in phone_string:
        if char in '0123456789':
            formatted_string += char
    return formatted_string

def area_code(fstring):
    """area_code returns the area code from a properly formatted
phone number string"""
    return fstring[0:3]

test_string = '608-473-1286'

def test_phonenum():
    formatted = format_phone_number(test_string)
    success formatted = '6084731286'
    code = area_code(formatted)
    success
 success and code = '608'
    return success

Hands on Example

Move your testing function into the !__name!__ block as described above

Packages and PYTHONPATH

Packages are installed python libraries, generally composed of several modules/files. You import them just as if they were single modules. It is up to the package developers to organize their package into something sensible.

So you might have noticed that scripts/modules/packages aren't really all that different. Everything is a modules, but we call modules that we intend to directly execute scripts. If a module is acting as an interface to a larger body of code, we call it a package. You interact with modules as if they were objects. This all boils down to nested namespaces with an object oriented interface.

Thus far we have only dealt with modules that are in the current working directory. Obviously there are importable modules that are not in the current directory. Obvious questions are: where are they? and How to I install my modules such that they are globally accessible? If a modules is in a directory pointed to by your PYTHONPATH, it will be globally accessible. the module sys contains the variable path that contains the path for the current python interpreter. Anything more complicated than that is beyond the scope of this session.

>>> import sys
>>> print sys.path

The first step in becoming a Pythonista

The power of python is that it takes a few really powerful ideas and reuses them extensively. Using these ideas in a natural way yields beautiful, logical, maintainable and easy to understand code. We should all strive to write pythonic code. Python even comes with a description of what it is to write pythonic code.

::
#!python >>> import this

Classes and Objects

Basic Classes

Thus far we've developed a few functions for dealing with phone numbers. One converts a phone number string formatted in a particular way to a format more convenient for manipulation. The second assumes an already formatted number and does something with that. It would be really nice if there was a thing that encapsulated all this behavior, kinda like the split() functions were attached to strings. Really what we want is a new type. We want a type representing a phone number and having a set of functions that do sensible things with phone numbers. This thought process is the basis of objects and object oriented programming.

Object oriented programming really deserves of a bootcamp of it own, but this one is on python, not OOP.

Object oriented (OO) programming revolves around the create and manipulation of objects that have attributes and can do things. They can be as simple as a coordinate with x and y values or as complicated as a dynamic webpage framework. They are a useful way to organize programs. C++ and Java are OO languages. Even fortran is adding OO constructs in newer standards. Convinced already? Great! Lets start writing code. Here is the code for making a very simple class that sets an attribute. Start a new file, call it myclass.py and type this in.

 #!CodeExample
 #!python
 class MyClass(object):
   def setA(self, A):
     self.A = A


* Explicit is better than implicit - all python class functions (methods)
  explicitly pass the object they work on.
* Simple is better than complicated - class variables (attributes) can be
  directly accessed and dynamically created
* Namespaces are one honking great idea -- let's do more of those! - Notice the
  similarity of accessing functions in a module and object attributes/functions

You create a !MyClass object and play around with it. Instances of a class are instantiated by calling the class like a function. a = !MyClass()

  • create a !MyClass
  • ask its type
  • set A using setA
  • directly set A
  • what types are fair game for A?
  • add an attribute B to an instance of a !MyClass
Vocabulary time
  • Class - user defined type (!MyClass)
  • object - instance of a Class (tmp = !MyClass(), tmp is an object)
  • method - a Class function, also called a member function (tmp.getA())
  • attribute - a Class variable (tmp.A)
  • Duck typing - if it behaves like a duck, who cares if type(a)== 'Duck'?

Hands on Example

Write a !PhoneNumber class with functions set_number(number_string) and area_code().

#!CodeExample
#!python
"""phonenum defines function for manipulating phone number strings"""

print "Nudge, nudge, wink, wing, say no more, say no more!"

class PhoneNumber(object):
    """I am a phone number"""

    def set_phone_number(self, phone_string):
        """format_phone_number formats a string in the format 'xxx-xxx-xxxx'
    to 'xxx-xxx-xxxx'"""

        formatted_string = ''
        for char in phone_string:
            if char in '0123456789':
                formatted_string += char
        self.phone_number = formatted_string

    def area_code(self):
        """area_code returns the area code from a properly formatted
    phone number string"""
        return self.phone_number[0:3]

Constructors

Usually you want to create an object with a set of initial values for things. Perhaps an object needs certain information to be created. For this you write a "constructor." In python, constructors are just methods with a special name: !__init!__. !MyClass(stuff) -> !__init!__(self, stuff).

Aside: Magic functions

Methods with leading and trailing double underscores are "magic functions" in python. If you go to the [Session09 Advanced Python] breakout session you will learn more about them.

  • Iteration (for x in sequence) uses !__next!__
  • Slicing ie brackets) (a[1:2]) uses !__get!__
  • Calling ie parentheses (a(3)) uses !__call!__
  • Help uses !__doc!__

Convert your set_number function to be a constructor.

Aside: Old vs New Style Classes

It is worth noting that there are two types of classes in python: Old style classes (OSC) and new style classes (NSC). NSC fix some conceptual problems with OSC (typing, diamond inheritance, subclassing built in types, etc). Consequently OSC are gone in python 3. This is not a cause for concern or confusion as the difference are subtle and will not affect you until you have written enough python to be comfortable with the distinction. Or you can always subclass object and render the issue moot. Below illustrates the fix in the typing system.

#!CodeExample
#!python
class OldStyleClass: # don't use this one
    def __init__(self):
        print "Old Style"

class NewStyleClass(object): # <- use this one
    def __init__(self):
        print "New Style"

ns = NewStyleClass()
os = OldStyleClass()

print type(os)
print type(ns)

Class methods with variable numbers of arguments

In the previous session you learned about the power of python functions. The full power of functions (keyword arguments, variable length arguments, etc) are available in classes. For converts from other languages, be aware that python functions do not have a type signature so function overloading is not available.

Subclassing

If you want a to create a Class that behaves mostly like another class, you should not have to copy code. What you do is subclass and change the things that need changing. When we created classes we were already subclassing the built in python class "object."

Python guidelines for code formatting and pythonic conventions on class behavior, naming, etc. [http://www.python.org/dev/peps/pep-0008/ python conventions]

Questions?