We will now focus on syntax best practices for classes. It is not intended to cover design patterns here, as they will be discussed in Chapter 14, Useful Design Patterns. This chapter gives an overview of the advanced Python syntax to manipulate and enhance the class code.
Object model evolved greatly during history of Python 2. For a long time we lived in a world where two implementations of the object-oriented programming paradigm coexisted in the same language. These two models were simply referred to as old-style and new-style classes. Python 3 ended this dichotomy and only model known as new-style classes is available to the developers. Anyway, it is still important to know how both of them worked in Python 2 because it will help you in porting old code and writing backwards compatible applications. Knowing how the object model changed will also help you in understanding why it is designed that way right now. This is the reason why the following chapter will have a relatively large number of notes about old Python 2 features despite this book targets the latest Python 3 releases.
The following topics will be discussed in this chapter:
Subclassing built-in types in Python is pretty straightforward. A built-in type called object
is a common ancestor for all built-in types as well as all user-defined classes that have no explicit parent class specified. Thanks to this, every time a class that behaves almost like one of the built-in types needs to be implemented, the best practice is to subtype it.
Now, we will show you the code for a class called distinctdict
, which uses this technique. It is a subclass of the usual Python dict
type. This new class behaves in most ways like an ordinary Python
dict
. But instead of allowing multiple keys with the same value, when someone tries to add a new entry with an identical value, it raises a ValueError
subclass with a help message:
class DistinctError(ValueError): """Raised when duplicate value is added to a distinctdict.""" class distinctdict(dict): """Dictionary that does not accept duplicate values.""" def __setitem__(self, key, value): if value in self.values(): if ( (key in self and self[key] != value) or key not in self ): raise DistinctError( "This value already exists for different key" ) super().__setitem__(key, value)
The following is an example of using distictdict
in interactive session:
>>> my = distinctdict() >>> my['key'] = 'value' >>> my['other_key'] = 'value' Traceback (most recent call last): File "<input>", line 1, in <module> File "<input>", line 10, in __setitem__ DistinctError: This value already exists for different key >>> my['other_key'] = 'value2' >>> my {'key': 'value', 'other_key': 'value2'}
If you take a look at your existing code, you may find a lot of classes that partially implement the built-in types, and could be faster and cleaner as subtypes. The list
type, for instance, manages the sequences and could be used every time a class works internally with a sequence:
class Folder(list): def __init__(self, name): self.name = name def dir(self, nesting=0): offset = " " * nesting print('%s%s/' % (offset, self.name)) for element in self: if hasattr(element, 'dir'): element.dir(nesting + 1) else: print("%s %s" % (offset, element))
Here is an example usage in interactive session:
>>> tree = Folder('project') >>> tree.append('README.md') >>> tree.dir() project/ README.md >>> src = Folder('src') >>> src.append('script.py') >>> tree.append(src) >>> tree.dir() project/ README.md src/ script.py
Built-in types cover most use cases
When you are about to create a new class that acts like a sequence or a mapping, think about its features and look over the existing built-in types. The collections
module extends basic built-in types with many useful containers. You will end up using one of them most of the time.