Dive Into Python Python for experienced programmers |

## 3.6. Special class methods

In addition to normal class methods, there are a number of special methods which Python classes can define. Instead of being called directly by your code (like normal methods), special methods are called for you by Python in particular circumstances or when specific syntax is used.

As you saw in the previous section, normal methods went a long way towards wrapping a dictionary in a class. But normal methods alone are not enough, because there are lots of things you can do with dictionaries besides call methods on them. For starters, you can get and set items with a syntax that doesn't include explicitly invoking methods. This is where special class methods come in: they provide a way to map non-method-calling syntax into method calls.

Example 3.13. The `__getitem__` special method

        def __getitem__(self, key): return self.data[key]

    >>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
    >>> f
    {'name':'/music/_singles/kairo.mp3'}
    >>> f.__getitem__("name")
    '/music/_singles/kairo.mp3'
    >>> f["name"]
    '/music/_singles/kairo.mp3'

| The `__getitem__` special method looks simple enough. Like the normal methods `clear`, `keys`, and `values`, it just redirects to the dictionary to return its value. But how does it get called? Well, you can call `__getitem__` directly, but in practice you wouldn't actually do that; I'm just doing it here to show you how it works. The right way to use `__getitem__` is to get Python to call it for you.
| This looks just like the syntax you would use to get a dictionary value, and in fact it returns the value you would expect. But here's the missing link: under the covers, Python has converted this syntax to the method call `f.__getitem__("name")`. That's why `__getitem__` is a special class method; not only can you call it yourself, you can get Python to call it for you by using the right syntax.

Example 3.14. The `__setitem__` special method

        def __setitem__(self, key, item): self.data[key] = item

    >>> f
    {'name':'/music/_singles/kairo.mp3'}
    >>> f.__setitem__("genre", 31)
    >>> f
    {'name':'/music/_singles/kairo.mp3', 'genre':31}
    >>> f["genre"] = 32
    >>> f
    {'name':'/music/_singles/kairo.mp3', 'genre':32}

| Like the `__getitem__` method, `__setitem__` simply redirects to the real dictionary `self.data` to do its work. And like `__getitem__`, you wouldn't ordinarily call it directly like this; Python calls `__setitem__` for you when you use the right syntax.
| This looks like regular dictionary syntax, except of course that `f` is really a class that's trying very hard to masquerade as a dictionary, and `__setitem__` is an essential part of that masquerade. This line of code actually calls `f.__setitem__("genre", 32)` under the covers.

`__setitem__` is a special class method because it gets called for you, but it's still a class method. Just as easily as the `__setitem__` method was defined in `UserDict`, we can redefine it in our descendant class to override the ancestor method. This allows us to define classes that act like dictionaries in some ways but define their own behavior above and beyond the built-in dictionary.

This concept is the basis of the entire framework we're studying in this chapter. Each file type can have a handler class which knows how to get metadata from a particular type of file. Once some attributes (like the file's name and location) are known, the handler class knows how to derive other attributes automatically. This is done by overriding the `__setitem__` method, checking for particular keys, and adding additional processing when they are found.

For example, `MP3FileInfo` is a descendant of `FileInfo`. When an `MP3FileInfo`'s `name` is set, it doesn't just set the `name` key (like the ancestor `FileInfo` does); it also looks in the file itself for MP3 tags and populates a whole set of keys.

Example 3.15. Overriding `__setitem__` in `MP3FileInfo`

        def __setitem__(self, key, item):
            if key == "name" and item:
                self.__parse(item)
            FileInfo.__setitem__(self, key, item)

| Note that our `__setitem__` method is defined exactly the same way as the ancestor method. This is important, since Python will be calling the method for us, and it expects it to be defined with a certain number of arguments. (Technically speaking, the names of the arguments don't matter, just the number.)
| Here's the crux of the entire `MP3FileInfo` class: if we're assigning a value to the `name` key, we want to do something extra.
| The extra processing we do for `name`s is encapsulated in the `__parse` method. This is another class method defined in `MP3FileInfo`, and when we call it, we qualify it with `self`. Just calling `__parse` would look for a normal function defined outside the class, which is not what we want; calling `self.__parse` will look for a class method defined within the class. This isn't anything new; you reference data attributes the same way.
| After doing our extra processing, we want to call the ancestor method. Remember, this is never done for you in Python; you have to do it manually. Note that we're calling the immediate ancestor, `FileInfo`, even though it doesn't have a `__setitem__` method. That's okay, because Python will walk up the ancestor tree until it finds a class with the method we're calling, so this line of code will eventually find and call the `__setitem__` defined in `UserDict`.

When accessing data attributes within a class, you need to qualify the attribute name: `self.attribute`. When calling other methods within a class, you need to qualify the method name: `self.method`.

Example 3.16. Setting an `MP3FileInfo`'s `name`

    >>> import fileinfo
    >>> mp3file = fileinfo.MP3FileInfo()
    >>> mp3file
    {'name':None}
    >>> mp3file["name"] = "/music/_singles/kairo.mp3"
    >>> mp3file
    {'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31,
    'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3',
    'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'}
    >>> mp3file["name"] = "/music/_singles/sidewinder.mp3"
    >>> mp3file
    {'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder',
    'name': '/music/_singles/sidewinder.mp3', 'year': '2000',
    'comment': 'http://mp3.com/cynicproject'}

| First, we create an instance of `MP3FileInfo`, without passing it a filename. (We can get away with this because the `filename` argument of the `__init__` method is optional.) Since `MP3FileInfo` has no `__init__` method of its own, Python walks up the ancestor tree and finds the `__init__` method of `FileInfo`. This `__init__` method manually calls the `__init__` method of `UserDict` and then sets the `name` key to `filename`, which is `None`, since we didn't pass a filename. Thus, `mp3file` initially looks like a dictionary with one key, `name`, whose value is `None`.
| Now the real fun begins. Setting the `name` key of `mp3file` triggers the `__setitem__` method on `MP3FileInfo` (not `UserDict`), which notices that we're setting the `name` key with a real value and calls `self.__parse`. Although we haven't traced through the `__parse` method yet, you can see from the output that it sets several other keys: `album`, `artist`, `genre`, `title`, `year`, and `comment`.
| Modifying the `name` key will go through the same process again: Python calls `__setitem__`, which calls `self.__parse`, which sets all the other keys.