Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Python (2005)

.pdf
Скачиваний:
158
Добавлен:
17.08.2013
Размер:
15.78 Mб
Скачать

Building a Module

Working with Classes

Most modules define a set of related functions or classes. A class, as introduced in Chapter 6, holds data as well as the methods that operate on that data. Python is a little looser than most programming languages, such as Java, C++, or C#, in that Python lets you break rules enforced in other languages. For example, Python, by default, lets you access data inside a class. This does violate some of the concepts of object-oriented programming but with good reason: Python aims first and foremost to be practical.

Defining Object-Oriented Programming

Computer geeks argue endlessly over what is truly object-oriented programming (OOP). Most experts, however, agree on the following three concepts:

Encapsulation

Inheritance

Polymorphism

Encapsulation is the idea that a class can hide the internal details and data necessary to perform a certain task. A class holds the necessary data, and you are not supposed to see that data under normal circumstances. Furthermore, a class provides a number of methods to operate on that data. These methods can hide the internal details, such as network protocols, disk access, and so on. Encapsulation is a technique to simplify your programs. At each step in creating your program, you can write code that concentrates on a single task. Encapsulation hides the complexity.

Inheritance means that a class can inherit, or gain access to, data and methods defined in a parent class. This just follows common sense in classifying a problem domain. For example, a rectangle and a circle are both shapes. In this case, the base class would be Shapes. The Rectangle class would then inherit from Shapes, as would the Circle class. Inheritance enables you to treat objects of both the Rectangle and Circle classes as children and members of the Shape class, meaning you can write more generic code in the base class, and become more specific in the children. (The terms children and child class, and membership in a class, are similar and can be used interchangeably here.) For the most part, the base class should be general and the subclasses specialized. Inheritance is often called specialization.

Polymorphism means that subclasses can override methods for more specialized behavior. For example, a rectangle and a circle are both shapes. You may define a set of common operations such as move and draw, that should apply to all shapes. However, the draw method for a Circle will obviously be different than the draw method for a Rectangle. Polymorphism enables you to name both methods draw and then call these methods as if the Circle and the Rectangle were both Shapes, which they are, at least in this example.

Creating Classes

As described in Chapter 6, creating classes is easy. (In fact, most things in Python are pleasantly easy.) The following example shows a simple class that represents a meal.

151

TEAM LinG

Chapter 10

Try It Out

Creating a Meal Class

The following code defines the Meal class. The full source file appears in the section on “Creating a Whole Module.”

class Meal:

‘’’Holds the food and drink used in a meal. In true object-oriented tradition, this class

includes setter methods for the food and drink.

Call printIt to pretty-print the values. ‘’’

def __init__(self, food=’omelet’, drink=’coffee’): ‘’’Initialize to default values.’’’

self.name = ‘generic meal’ self.food = food self.drink = drink

def printIt(self, prefix=’’): ‘’’Print the data nicely.’’’

print prefix,’A fine’,self.name,’with’,self.food,’and’,self.drink

# Setter for the food.

def setFood(self, food=’omelet’): self.food = food

# Setter for the drink.

def setDrink(self, drink=’coffee’): self.drink = drink

# Setter for the name.

def setName(self, name=’’): self.name = name

How It Works

Each instance of the Meal class holds three data values: the name of the meal, the food, and the drink. By default, the Meal class sets the name to generic meal, the drink to coffee, and the food to an omelet.

As with gin and tonics, omelets are not just for breakfast anymore.

The __init__ method initializes the data for the Meal. The printIt method prints out the internal data in a friendly manner. Finally, to support developers used to stricter programming languages, the Meal class defines a set of methods called setters. These setter methods, such as setFood and setDrink, set data into the class.

These methods are not necessary in Python, as you can set the data directly.

See Chapter 6 for more information about classes.

152

TEAM LinG

Building a Module

Extending Existing Classes

After you have defined a class, you can extend the class by defining subclasses. For example, you can create a Breakfast class that represents the first meal of the day:

class Breakfast(Meal):

‘’’Holds the food and drink for breakfast.’’’

def __init__(self):

‘’’Initialize with an omelet and coffee.’’’ Meal.__init__(self, ‘omelet’, ‘coffee’) self.setName(‘breakfast’)

The Breakfast class extends the Meal class as shown by the class definition:

class Breakfast(Meal):

Another subclass would naturally be Lunch:

class Lunch(Meal):

‘’’Holds the food and drink for lunch.’’’

def __init__(self):

‘’’Initialize with a sandwich and a gin and tonic.’’’ Meal.__init__(self, ‘sandwich’, ‘gin and tonic’) self.setName(‘midday meal’)

# Override setFood().

def setFood(self, food=’sandwich’):

if food != ‘sandwich’ and food != ‘omelet’: raise AngryChefException Meal.setFood(self, food)

With the Lunch class, you can see some use for the setter methods. In the Lunch class, the setFood method allows only two values for the food: a sandwich and an omelet. Nothing else is allowed or you will make the chef angry.

The Dinner class also overrides a method — in this case, the printIt method:

class Dinner(Meal):

‘’’Holds the food and drink for dinner.’’’

def __init__(self):

‘’’Initialize with steak and merlot.’’’ Meal.__init__(self, ‘steak’, ‘merlot’) self.setName(‘dinner’)

def printIt(self, prefix=’’): ‘’’Print even more nicely.’’’

print prefix,’A gourmet’,self.name,’with’,self.food,’and’,self.drink

Normally, you would place all these classes into a module. See the section “Creating a Whole Module” for an example of a complete module.

153

TEAM LinG

Chapter 10

Finishing Your Modules

After defining the classes and functions that you want for your module, the next step is to finish the module to make it better fit into the conventions expected by Python users and the Python interpreter.

Finishing your module can include a lot of things, but at the very least you need to do the following:

Define the errors and exceptions that apply to your module.

Define which items in the module you want to export. This defines the public API for the module.

Document your module.

Test your module.

Provide a fallback function in case your module is executed as a program.

The following sections describe how to finish up your modules.

Defining Module-Specific Errors

Python defines a few standard exception classes, such as IOError and NotImplementedError. If those classes apply, then by all means use those. Otherwise, you may need to define exceptions for specific issues that may arise when using your module. For example, a networking module may need to define a set of exceptions relating to network errors.

For the food-related theme used in the example module, you can define an AngryChefException. To make this more generic, and perhaps allow reuse in other modules, the AngryChefException is defined as a subclass of the more general SensitiveArtistException, representing issues raised by touchy artsy types.

In most cases, your exception classes will not need to define any methods or initialize any data. The base Exception class provides enough. For most exceptions, the mere presence of the exception indicates the problem.

This is not always true. For example, an XML-parsing exception should probably contain the line number where the error occurred, as well as a description of the problem.

You can define the exceptions for the meal module as follows:

class SensitiveArtistException(Exception): pass

class AngryChefException(SensitiveArtistException): pass

This is just an example, of course. In your modules, define exception classes as needed. In addition to exceptions, you should carefully decide what to export from your module.

154

TEAM LinG

Building a Module

Choosing What to Export

When you use the from form of importing a module, you can specify which items in the module to import. For example, the following statement imports the AngryChefException from the module meal:

from meal import AngryChefException

To import all public items from a module, you can use the following format:

from module_name import *

For example:

from meal import *

The asterisk, or star (*), tells the Python interpreter to import all public items from the module. What exactly, is public? You, as the module designer, can choose to define whichever items you want to be exported as public.

The Python interpreter uses two methods to determine what should be considered public:

If you have defined the variable __all__ in your module, the interpreter uses __all__ to determine what should be public.

If you have not defined the variable __all__, the interpreter imports everything except items with names that begin with an underscore, _, so printIt would be considered public, but _printIt would not.

See Chapter 7 for more information about modules and the import statement.

As a best practice, always define __all__ in your modules. This provides you with explicit control over what other Python scripts can import. To do this, simply create a sequence of text strings with the names of each item you want to export from your module. For example, in the meal module, you can define __all__ in the following manner:

__all__ = [‘Meal’, ‘AngryChefException’, ‘makeBreakfast’,

‘makeLunch’, ‘makeDinner’, ‘Breakfast’, ‘Lunch’, ‘Dinner’]

Each name in this sequence names a class or function to export from the module.

Choosing what to export is important. When you create a module, you are creating an API to perform some presumably useful function. The API you export from a module then defines what users of your module can do. You want to export enough for users of the module to get their work done, but you don’t have to export everything. You may want to exclude items for a number of reasons, including the following:

Items you are likely to change should remain private until you have settled on the API for those items. This gives you the freedom to make changes inside the module without impacting users of the module.

155

TEAM LinG

Chapter 10

Modules can oftentimes hide, in purpose, complicated code. For example, an e-mail module can hide the gory details of SMTP, POP3, and IMAP network e-mail protocols. Your e-mail module could present an API that enables users to send messages, see which messages are available, download messages, and so on.

Hiding the gory details of how your code is implemented is called encapsulation. Impress your friends with lines like “making the change you are asking for would violate the rules of encapsulation . . .”

Always define, explicitly, what you want to export from a module. You should also always document your modules.

Documenting Your Modules

It is vitally important that you document your modules. If not, no one, not even you, will know what your modules do. Think ahead six months. Will you remember everything that went into your modules? Probably not. The solution is simple: document your modules.

Python defines a few easy conventions for documenting your modules. Follow these conventions and your modules will enable users to view the documentation in the standard way. At its most basic, for each item you want to document, write a text string that describes the item. Enclose this text string in three quotes, and place it immediately inside the item.

For example, to document a method or function, use the following code as a guide:

def makeLunch():

‘’’ Creates a Breakfast object.’’’

return Lunch()

The line in bold shows the documentation. The documentation that appears right after the function is defined with the def statement.

Document a class similarly:

class Meal:

‘’’Holds the food and drink used in a meal. In true object-oriented tradition, this class

includes setter methods for the food and drink.

Call printIt to pretty-print the values.

‘’’

Place the documentation on the line after the class statement.

Exceptions are classes, too. Document them as well:

class SensitiveArtistException(Exception): ‘’’Exception raised by an overly-sensitive artist.

Base class for artistic types.’’’

pass

156

TEAM LinG

Building a Module

Note that even though this class adds no new functionality, you should describe the purpose of each exception or class.

In addition, document the module itself. Start your module with the special three-quoted text string, as shown here:

“””

Module for making meals in Python.

Import this module and then call makeBreakfast(), makeDinner() or makeLunch().

“””

Place this documentation on the first line of the text file that contains the module. For modules, start with one line that summarizes the purpose of the module. Separate this line from the remaining lines of the documentation, using a blank line as shown previously. The Python help function will extract the one-line summary and treat it specially. (See the Try It Out example, following, for more details about how to call the help function.)

Usually, one or two lines per class, method, or function should suffice. In general, your documentation should tell the user the following:

How to call the function or method, including what parameters are necessary and what type of data will be returned. Describe default values for parameters.

What a given class was designed for, what is its purpose. Include how to use objects of the class.

Any conditions that must exist prior to calling a function or method

Any side effects or other parts of the system that will change as a result of the class. For example, a method to erase all of the files on a disk should be documented as to what it does.

Exceptions that may be raised and under what reasons these exceptions will be raised

Note that some people go way overboard in writing documentation. Too much documentation doesn’t help, but don’t use this as an excuse to do nothing. Too much documentation is far better than none at all.

A good rule of thumb comes from enlightened self-interest. Ask yourself what you would like to see in someone else’s module and document to that standard.

You can view the documentation you write using the help function, as shown in the following example:

Try It Out

Viewing Module Documentation

Launch the Python interpreter in interactive mode and then run the import and help commands as shown in the following:

>>>import meal

>>>help(meal)

Help on module meal:

157

TEAM LinG

Chapter 10

NAME

 

 

meal - Module for making meals in Python.

FILE

 

 

/Users/ericfj/writing/python/meal.py

DESCRIPTION

 

Import this module and then call

makeBreakfast(), makeDinner() or makeLunch().

CLASSES

 

 

exceptions.Exception

 

AngryChefException

 

SensitiveArtistException

Meal

 

 

 

Breakfast

 

 

Dinner

 

 

Lunch

 

class AngryChefException(exceptions.Exception)

|

Exception that indicates the chef is unhappy.

|

 

 

|

Methods inherited from exceptions.Exception:

|

 

 

| __getitem__(...)

|

 

 

| __init__(...)

|

 

 

|

__str__(...

)

class Breakfast(Meal)

|

Holds the food and drink for breakfast.

|

 

 

|

Methods defined here:

|

 

 

| __init__(self)

|

Initialize with an omelet and coffee.

|

 

 

|----------------------------------------------------------------------

|

Methods inherited from Meal:

|

 

|

printIt(self, prefix=’’)

|

Print the data nicely.

|

 

|

setDrink(self, drink=’coffee’)

|

# Setter for the name.

|

 

|

setFood(self, food=’omelet’)

|

# Setter for the drink.

|

 

|setName(self, name=’’)

| # Setter for the name.

158

TEAM LinG

Building a Module

class Dinner(Meal)

 

|

Holds the food

and drink for dinner.

|

 

 

|

Methods defined here:

|

 

 

|__init__(self)

| Initialize with steak and merlot.

|

|printIt(self, prefix=’’)

| Print even more nicely.

|

|----------------------------------------------------------------------

|

Methods inherited from Meal:

|

 

|

setDrink(self, drink=’coffee’)

|

# Setter for the name.

|

 

|

setFood(self, food=’omelet’)

|

# Setter for the drink.

|

 

|setName(self, name=’’)

|# Setter for the name.

class Lunch(Meal)

|

Holds the food and drink for lunch.

|

 

|

Methods defined here:

|

 

|__init__(self)

|

Initialize with a sandwich and a gin and tonic.

|

 

|

setFood(self, food=’sandwich’)

|

# Override setFood().

|

 

|----------------------------------------------------------------------

|

Methods inherited from Meal:

|

 

|

printIt(self, prefix=’’)

|

Print the data nicely.

|

 

|

setDrink(self, drink=’coffee’)

|

# Setter for the name.

|

 

|setName(self, name=’’)

|# Setter for the name.

class Meal

|Holds the food and drink used in a meal.

|In true object-oriented tradition, this class

|

includes setter methods for the food and drink.

|

 

|

Call printIt to pretty-print the values.

159

TEAM LinG

Chapter 10

|

 

|

Methods defined here:

|

 

|

__init__(self, food=’omelet’, drink=’coffee’)

|

Initialize to default values.

|

 

|

printIt(self, prefix=’’)

|

Print the data nicely.

|

 

|

setDrink(self, drink=’coffee’)

|

# Setter for the name.

|

 

|

setFood(self, food=’omelet’)

|

# Setter for the drink.

|

 

|setName(self, name=’’)

|# Setter for the name.

class SensitiveArtistException(exceptions.Exception)

|

Exception raised by an overly-sensitive artist.

|

 

|

Base class for artistic types.

|

 

|

Methods inherited from exceptions.Exception:

|

 

| __getitem__(...)

|

 

| __init__(...)

|

 

|__str__(...)

FUNCTIONS makeBreakfast()

Creates a Breakfast object.

makeDinner()

Creates a Breakfast object.

makeLunch()

Creates a Breakfast object.

test()

Test function.

DATA

__all__ = [‘Meal’, ‘AngryChefException’, ‘makeBreakfast’, ‘makeLunch’,...

(END)

Press q to quit the listing.

How It Works

The help function is your friend. It can show you the documentation for your modules, as well as the documentation on any Python module.

160

TEAM LinG