18.3. More Features

18.3.1. Exceptions

Exceptions are by now well accepted as a good means to separate the main logic of a program from error handling.

>>> try:
	1/0
    except ZeroDivisionError, e:
	print e
integer division or modulo by zero

The except statement takes the name of the exception class we want to handle and the name of the variable in which the exception should be stored. Python comes with its own hierarchy of exception classes, and we can easily add our own.

18.3.2. Nested Definitions

Most Python statements can occur anywhere in the code. You can nest function definitions, class definitions, import statements within each other or other control statements. In the first example, a function returns another (local) function.

>>> def f(a):
	def g(x): return a + x
	return g
>>> h = f(10)
>>> h(5)
15

The argument a passed to the first function f is used in the local function g. This example only works in the new versions of Python supporting nested scopes.

Similarly, the next function creates a class on the fly and returns it to the caller.

>>> def f(x):
	class B:
		def __init__(self):
			self.x = x
	return B
>>> c = f(5)
>>> d = c()
>>> d
<__main__.B instance at 0x00923D60>
>>> d.x
5

18.3.3. Generators

Generators are another recent addition to Python, probably in response to Ruby (which copied it from Icon). A generator is a function-like object which can act as an iterator. The generator interrupts its processing, returns a value, and later continues at the same place while keeping its state (local variables, etc.) between the calls. To activate the generator feature one has to import it from the future (Python's way to gradually introduce changes which could break some code, in this case because of the new keyword "yield").

>>> from __future__ import generators
>>> def f(x):
	yield x
	yield 2*x
	yield 3*x
>>> for i in f(5): print i
5
10
15

Generators are rather useful when implementing iterators on complex data structures (e.g., a tree walk).

18.3.4. String Formatting

If there is one feature I'm missing in Java, it is a formatting function as powerful as C's printf. This is even more true, since I know Python's merge operator %. The percent operator merges a format string (a printf-like pattern) with the values on the right hand side (either a single value or a tuple of values).

>>> "my name is %s" % "Joe"
'my name is Joe'
>>> "name: %s, age: %d" % ("Joe", 25)
'name: Joe, age: 25'

Many times, inserting the values in the order in which they appear is not what you want. Imagine a template for a letter in which you would like to insert the name of the recipient multiple times. In this case you can use named placeholders in the format string and pass the values as a dictionary:

>>> "name: %(name)s %(age)d" % {"name": "Joe", "age": 25}
'name: Joe 25'

Remembering that the attributes of an object are accessible as a dictionary, this results in a nice way to insert objects into templates:

>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age
>>> joe = Person("Joe", 25)
>>> "name: %(name)s, age: %(age)d" % joe.__dict__
'name: Joe, age: 25'

18.3.5. Operator Overloading

All the operators we have used so far were predefined in the language core. If we want operators to work with our own objects, we have to override the associated special methods. However, you can not change the behavior of a built-in class and redefine, say, the meaning of the operator + for integers.

18.3.6. Class Methods and Properties

Version 2.2 adds two kinds of methods to Python's toolset, static methods and class methods. Static methods are just functions which are made part of the class so that we can call them with the class or an instance. Unlike the other methods, the functions have no special argument (such as the instance passed to instance methods). Class methods are like static method, but get the class passed as the first argument.

>>> class A(object):
        def f(*args):
            print args
        fStatic = staticmethod(f)
        fClass = classmethod(f)
>>> class B(A): pass
>>> b = B()
>>> A.fStatic(55)
(55,)
>>> b.fStatic(55)
(55,)
>>> A.fClass(55)
(<class '__main__.A'>, 55)
>>> b.fClass(55)
(<class '__main__.B'>, 55)

Both types of methods are created by first defining the function inside of the class just like an instance method and then converting this function to a static or class method the built-in functions staticmethod and classmethod, respectively.

Properties look like attributes to the outside world, but are implemented with accessor methods. Prior to Python 2.2, one had to implement the special methods __getattr__ and __setattr__ to achieve this behavior. As of Python 2.2, the new property function accomplishes the same effect much more easily.

>>> class A(object):
	def setx(self, x):
		print "setting x"
		self._x = x
	def getx(self):
		print "geeting x"
		return self._x
	x = property(getx, setx)
	
>>> a = A()
>>> a.x = 55
setting x
>>> a.x
geeting x
55

18.3.7. Visibility

By default, all members of a class are public. There is no equivalent to the visibility qualifiers of C++ or Java. However, it is possible to make the access to a class member more difficult. Whenever the name of a member starts with two underscore characters but does not end with two underscores (like Python's special members), Python considers the member private and adds an underscore and the class name as a prefix (this process is known as name mangling).

>>> class A:
        def __init__(self, name): self.__name = name
        def getName(self): return self.__name

>>> a = A("Homer")
>>> dir(a)
['_A__name', '__doc__', '__init__', '__module__', 'getName']
>>> a.getName()
'Homer'
>>> a.__name
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in ?
    a.__name
AttributeError: A instance has no attribute '__name'
>>> a._A__name
'Homer'

Within the class, we can still use the original name __name, but the member is actually stored under the name _A__name. Of course this does not prevent you from accessing this attribute directly, but it makes it a lot harder. Note that this approach works for attributes and method alike, since methods are just callable attributes.