# Dive Into Python

Python for experienced programmers

Find:

## 3.5. `UserDict`: a wrapper class

As you’ve seen, `FileInfo` is a class that acts like a dictionary. To explore this further, let’s look at the `UserDict` class in the `UserDict` module, which is the ancestor of our `FileInfo` class. This is nothing special; the class is written in Python and stored in a `.py` file, just like our code. In particular, it’s stored in the `lib` directory in your Python installation.

In the Python IDE on Windows, you can quickly open any module in your library path with File->Locate... (Ctrl-L).

Historical note. In versions of Python prior to 2.2, you could not directly subclass built-in datatypes like strings, lists, and dictionaries. To compensate for this, Python comes with wrapper classes that mimic the behavior of these built-in datatypes: `UserString`, `UserList`, and `UserDict`. Using a combination of normal and special methods, the `UserDict` class does an excellent imitation of a dictionary, but it’s just a class like any other, so you can subclass it to provide custom dictionary-like classes like `FileInfo`. In Python 2.2 and later, you could rewrite this chapter’s example so that `FileInfo` inherited directly from `dict` instead of `UserDict`. However, you should still read about how `UserDict` works, in case you need to implement this kind of wrapper object yourself, or in case you need to support versions of Python prior to 2.2.

### Example 3.11. Defining the `UserDict` class

    class UserDict:
        def __init__(self, dict=None):
            self.data = {}
            if dict is not None: self.update(dict)

| Note that `UserDict` is a base class, not inherited from any other class.

This is the `__init__` method that we overrode in the `FileInfo` class. Note that the argument list in this ancestor class is different than the descendant. That’s okay; each subclass can have its own set of arguments, as long as it calls the ancestor with the correct arguments. Here the ancestor class has a way to define initial values (by passing a dictionary in the `dict` argument) which our `FileInfo` does not take advantage of.

Python supports data attributes (called “instance variables” in Java and Powerbuilder, “member variables” in C++), which is data held by a specific instance of a class. In this case, each instance of `UserDict` will have a data attribute `data`. To reference this attribute from code outside the class, you would qualify it with the instance name, `instance.data`, in the same way that you qualify a function with its module name. To reference a data attribute from within the class, we use `self` as the qualifier. By convention, all data attributes are initialized to reasonable values in the `__init__` method. However, this is not required, since data attributes, like local variables, spring into existence when they are first assigned a value.

| The `update` method is a dictionary duplicator: it copies all the keys and values from one dictionary to another. This does not clear the target dictionary first; if the target dictionary already has some keys, the ones from the source dictionary will be overwritten, but others will be left untouched. Think of `update` has a merge function, not a copy function.

Also, this is a syntax you may not have seen before (I haven’t used it in the examples in this book). This is an `if` statement, but instead of having an indented block starting on the next line, there is just a single statement on the same line, after the colon. This is perfectly legal syntax, and is just a shortcut when you have only one statement in a block. (It’s like specifying a single statement without braces in C++.) You can use this syntax, or you can have indented code on subsequent lines, but you can’t do both for the same block.

Java and Powerbuilder support function overloading by argument list, i.e. one class can have multiple methods with the same name but a different number of arguments, or arguments of different types. Other languages (most notably PL/SQL) even support function overloading by argument name; i.e. one class can have multiple methods with the same name and the same number of arguments of the same type but different argument names. Python supports neither of these; it has no form of function overloading whatsoever. Methods are defined solely by their name, and there can be only one method per class with a given name. So if a descendant class has an `__init__` method, it always overrides the ancestor `__init__` method, even if the descendant defines it with a different argument list. And the same rule applies to any other method.

Guido, the original author of Python, explains method overriding this way: "Derived classes may override methods of their base classes. Because methods have no special privileges when calling other methods of the same object, a method of a base class that calls another method defined in the same base class, may in fact end up calling a method of a derived class that overrides it. (For C++ programmers: all methods in Python are effectively virtual.)" If that doesn’t make sense to you (it confuses the hell out of me), feel free to ignore it. I just thought I’d pass it along.

Always assign an initial value to all of an instance’s data attributes in the `__init__` method. It will save you hours of debugging later, tracking down `AttributeError` exceptions because you’re referencing uninitialized (and therefore non-existent) attributes.

### Example 3.12. `UserDict` normal methods

        def clear(self): self.data.clear()
        def copy(self):
            if self.__class__ is UserDict:
                return UserDict(self.data)
            import copy
            return copy.copy(self)
        def keys(self): return self.data.keys()
        def items(self): return self.data.items()
        def values(self): return self.data.values()

`clear` is a normal class method; it is publicly available to be called by anyone at any time. Note that `clear`, like all class methods, has `self` as its first argument. (Remember, you don’t include `self` when you call the method; it’s something that Python adds for you.) Also note the basic technique of this wrapper class: store a real dictionary (`data`) as a data attribute, define all the methods that a real dictionary has, and have each class method redirect to the corresponding method on the real dictionary. (In case you’d forgotten, a dictionary’s `clear` method deletes all of its keys and their associated values.)

| The `copy` method of a real dictionary returns a new dictionary that is an exact duplicate of the original (all the same key-value pairs). But `UserDict` can’t simply redirect to `self.data.copy`, because that method returns a real dictionary, and what we want is to return a new instance that is the same class as `self`.
| We use the `__class__` attribute to see if `self` is a `UserDict`; if so, we’re golden, because we know how to copy a `UserDict`: just create a new `UserDict` and give it the real dictionary that we’ve squirreled away in `self.data`.

If `self.__class__` is not `UserDict`, then `self` must be some subclass of `UserDict` (like maybe `FileInfo`), in which case life gets trickier. `UserDict` doesn’t know how to make an exact copy of one of its descendants; there could, for instance, be other data attributes defined in the subclass, so we would have to iterate through them and make sure to copy all of them. Luckily, Python comes with a module to do exactly this, and it’s called `copy`. I won’t go into the details here (though it’s a wicked cool module, if you’re ever inclined to dive into it on your own). Suffice to say that `copy` can copy arbitrary Python objects, and that’s how we’re using it here.

| The rest of the methods are straightforward, redirecting the calls to the built-in methods on `self.data`.

### Further reading

- Python Library Reference documents the `UserDict` module and the `copy` module.