Explore 7

Beautiful Code
Ka-Ping Yee, March 5, 2003

1. Sending Mail

The smtplib module provides a quick and easy way to send e-mail messages. This module contains a class called SMTP that initiates a connection to a mail server. In the CS labs, you can use mail.eecs.berkeley.edu. Elsewhere, you will have to choose a server that's willing to deliver mail for you (such as the one provided by your ISP).

Instances of the SMTP class provide a sendmail() method, which you can call with three arguments: the sender's address, the receiver's address, and the message. The message should be a multi-line string with the headers at the beginning (such as Date: or Subject:), followed by a blank line, and then the body of the message.

When you're done with a connection, call its close() method.

>>> message = '''Subject: test message
... Date: Tue, 4 Mar 2003 17:06:16 -0800 (PST)
...
... Hello!'''
>>>
>>> import smtplib
>>> s = smtplib.SMTP('mail.eecs.berkeley.edu')
>>> s.sendmail('gremlins@underthebed.net', 'me@mydomain.com', message)
{}
>>> s.close()
>>>

Q1. Send a message to yourself – any message you like.

2. Fetching Pages

The urllib module lets you easily download Web pages (or anything else from the Web). Calling urllib.urlopen() on a URL returns a file-like object (sometimes we call this a stream). You can read all the data by calling read() on this stream, read the data one line at a time by calling readline(), or read a list of all the lines of the file with readlines().

>>> import urllib
>>> stream = urllib.urlopen('http://example.com')
>>> print stream.read()
<HTML>
<HEAD>
  <TITLE>Example Web Page
</HEAD>
<body>
<p>You have reached this web page by typing "example.com",
"example.net",
  or "example.org" into your web browser.</p>
<p>These domain names are reserved for use in documentation and are not available
  for registration. See <a href="http://www.rfc-editor.org/rfc/rfc2606.txt">RFC
  2606</a>, Section 3.</p>
</BODY>
</HTML>


>>>

If you need to find out other information about the downloaded file, the received headers are stored in a dictionary in stream.headers.

>>> stream.headers.keys()
['last-modified', 'content-length', 'etag', 'date', 'accept-ranges',
 'content-type', 'connection', 'server']
>>> stream.headers['content-type']
'text/html'
>>> stream.headers['last-modified']
'Wed, 08 Jan 2003 23:11:55 GMT'
>>>

Q2. Get the top headline from Google News. If you download http://news.google.com/, it turns out that each headline is on a line that contains the string class=y . Just look for that and print the first such line. The find() method on strings will come in handy. You can use the regular expression <[^>]*> to match HTML tags and remove them.

Q3. Put these two pieces together to get a script that will e-mail you the top headline from Google News.

3. Operating System Functions

The os module provides all the system and file functions you might expect. Commonly used functions from this module include:

Unix-heads will also recognize functions such as fork(), waitpid(pid, options), link(source, dest), symlink(source, dest), readlink(path), popen(command, mode), and kill(pid). Look at the list of functions in help(os) for more.

If you don't know what all of these do, that's okay. I'm just mentioning them here so you know they're available, and you can find them if you need to use them.

The os module also contains a submodule, os.path, specifically for manipulating paths. Here are some useful functions in os.path:

There are more; again, look at help(os.path) for anything having to do with file paths.

4. Class Objects

Everything in Python is an object. Certain other languages like to say this, but they don't really mean it. In Python, we really mean it. Numbers, strings, lists, dictionaries, and instances are objects; but functions, modules, classes, and methods are objects too.

Classes have their own namespaces, just as modules and instances do. Last time, we just talked about defining methods in classes. But you can write any statements inside a class, if you want. What really happens is that the whole block inside the class keyword just gets executed inside the namespace of the class. Any definitions or assignments are left in that namespace.

>>> class Foo:
...     a = 3
...     def noogie(self):
...         return self.a
... 
>>> Foo
<class __main__.Foo at 0x81532b4>
>>> Foo.a
3
>>> Foo.b = 4
>>> Foo.b
4
>>> 

Try creating an instance of Foo. Call its noogie() method. What happens? Do you understand why?

Now try setting Foo.a to some other number. What happens when you call the noogie() method of the instance you just created? Now create a second instance of Foo. Try setting the a attribute directly on that instance. Now what happens to the a attribute of the first instance, or of the class?

Explore the behaviour of the class and instances by setting and checking their attributes until you feel you understand how they work. (Keep in mind that you can also remove assignments using del, as in del Foo.a, if that helps to clear things up as you experiment.)

Q4. Draw a picture showing the relationships between the Foo class and the two instances you just created, showing how the value of the a attribute is determined. Call me over and show me your picture.

5. Functions and Methods

Define a function outside the class, such as this one:

>>> def ribbit(self, z):
...     print self, z
...
>>>

Try assigning this function to an attribute of a Foo instance. What happens when you try to call it on the instance? What happens when you try to call it on the class?

Try assigning this function to an attribute of the Foo class. What happens when you try to call it on the class? What happens when you try to call it on a previously-created instance? What happens when you try to call it on a newly-created instance?

See if you can figure out what is happening. Ask me if you are confused.

Q5. Draw a picture showing how the class and instance behave when functions are assigned to their attributes, and showing what happens when you retrieve attributes that refer to functions. Call me over and show me your picture.

6. Inheritance

You can define a class that is derived from another class by giving the other class name in parentheses, like this:

>>> class Wong(Foo):
...     def meow(self):
...         return self.noogie() + 1
...
>>>

The result is a class with two callable methods: noogie() and meow(). Try creating an instance and calling its meow() method.

You can test that this instance is an instance of the Wong class by asking isinstance(x, Wong) (assuming your instance is called x). Notice that isinstance(x, Foo) is also true, because Wong is a subclass of Foo. This is also the preferred way to check types; for example, isinstance(3, int) is true.

What happens if you set the a attribute on the Wong class and call the instance's meow() method again? How about if you set the a attribute on the Foo class?

Q6. Explain what happens when you call meow(), and explain which value of a affects the result.

Now try this definition:

>>> class Wing(Wong):
...     def noogie(self):
...         return self.meow() + 1
...
>>>

What do you think will happen when you call noogie() on an instance of Wing? Try it.

If you want a method to call another method of a specific class, you call the method on the class, passing the instance as the first argument.

Q7. What one change could you make, among the definitions of Foo, Wong, and Wing, that would cause a call to noogie() on an instance of Wing to add 1 to Foo.a twice, returning 5?

7. The Exception Hierarchy

The exceptions are actually classes, arranged in an inheritance hierarchy to represent how some exceptions are subcategories of others.

You can see this hierarchy by doing import exceptions and then asking for help(exceptions). Try it.

Here are some of the most common exceptions used by Python programmers (as opposed to exceptions raised by Python itself):

When you catch exceptions using except, you catch not only exceptions that are exactly of the class you specify, but also its subclasses.

8. Custom Exceptions

You can create your own classes of exceptions, to represent exceptional conditions more specifically than with the existing categories.

When you do this, you should define your exception as a subclass of an appropriate existing exception. If none of the specific errors is appropriate, you can make your exception a subclass of StandardError, the superclass of all errors.

When you raise an exception, you can create an instance of the exception and raise that. You can define your exception to take whatever arguments it likes, in order to record what caused the problem; your exception class should then define a __str__ method to produce the message that gets displayed.

Q8. Define a custom exception that you might throw to indicate that someone tried to create a Date using a string in the wrong format.


Here's the seventh assignment.

If you have any questions about these exercises or the assignment, feel free to send me e-mail at bc@zesty.ca.