In C++ terminology, all Python methods are virtual
.
Figure 6.2 The Point class’s inheritance hierarchy
Table 6.1 Comparison Special Methods
Figure 6.3 The Circle class’s inheritance hierarchy
Inside the __init__()
method we use super()
to call the base class’s __init__()
method.
If we provide only getters
as we have done here, the properties are read-only
.
The property()
decorator function is built-in and takes up to four arguments: a getter function, a setter function, a deleter function, and a docstring. The effect of using @property
is the same as calling the property() function with just one argument, the getter function.
To turn an attribute into a readable/writable property we must create a private attribute where the data is actually held and supply getter and setter methods. Here is the radius’s getter
, setter
, and docstring
in full:
Every property that is created has a getter
, setter
, and deleter
attribute, so once the radius property is created using @property
, the radius.getter, radius.setter, and radius.deleter attributes become available. The radius.getter is set to the getter method by the @property decorator. The other two are set up by Python so that they do nothing (so the attribute cannot be written to or deleted), unless they are used as decorators, in which case they in effect replace themselves with the method they are used to decorate.
Table 6.2 Fundamental Special Methods
Table 6.3 Numeric and Bitwise Special Methods
The __del__(self)
special method is called when an object is destroyed—at least in theory. In practice, __del__()
may never be called, even at program termination. Furthermore, when we write del x, all that happens is that the object reference x is deleted and the count of how many object references refer to the object that was referred to by x is decreased by 1. Only when this count reaches 0 is __del__()
likely to be called, but Python offers no guarantee that it will ever be called. In view of this, __del__()
is very rarely reimplemented.
Figure 6.6 The square_eye.xpm image
Table 6.4 Collection Special Methods
When a class is created without the use of __slots__
, behind the scenes Python creates a private dictionary called __dict__
for each instance, and this dictionary holds the instance’s data attributes. This is why we can add or remove attributes from objects.
If we only need objects where we access the original attributes and don’t need to add or remove attributes, we can create classes that don’t have a __dict__
. This is achieved simply by defining a class attribute called __slots__
whose value is a tuple of attribute names.
The class works because we are using the object’s __dict__
which is what the base class __getattr__()
, __setattr__()
, and __delattr__()
methods use, although here we have used only the base class’s __getattr__()
method.
Table 8.2 Attribute Access Special Methods
There is another way of getting constants: We can use named tuples
. Here are a couple of examples:
We provided access to them using read-only properties
. For example, we had:
Here is a different solution that handles all the Image class’s read-only properties in a single method:
If we attempt to access an object’s attribute and the attribute is not found, Python will call the __getattr__()
method (providing it is implemented, and that we have not reimplemented __getattribute__()
), with the name of the attribute as a parameter. Implementations of __getattr__()
must raise an AttributeError exception if they do not handle the given attribute.
But instead we have chosen a more compact approach. Since we know that under the hood all of an object’s nonspecial attributes are held in self.__dict__
, we have chosen to access them directly. For private attributes (those whose name begins with two leading underscores), the name is mangled to have the form _className__attributeName
, so we must account for this when retrieving the attribute’s value from the object’s private dictionary.
Every object has a __class__
special attribute, so self.__class__
is always available inside methods and can safely be accessed by __getattr__()
without risking unwanted recursion.
Note that there is a subtle difference in that using __getattr__()
and self.__class__
provides access to the attribute in the instance’s class (which may be a subclass), but accessing the attribute directly uses the class the attribute is defined in.
One special method that we have not covered is __getattribute__()
. Whereas the __getattr__()
method is called last when looking for (nonspecial) attributes, the __getattribute__()
method is called first for every attribute access. Although it can be useful or even essential in some cases to call __getattribute__()
, reimplementing the __getattribute__()
method can be tricky. Reimplementations must be very careful not to call themselves recursively—using super().__getattribute__()
or object.__getattribute__()
is often done in such cases. Also, since __getattribute__()
is called for every attribute access, reimplementing it can easily end up degrading performance compared with direct attribute access or properties. None of the classes presented in this book reimplements __getattribute__()
.
In computer science a functor is an object that can be called as though it were a function, so in Python terms a functor is just another kind of function object. Any class that has a __call__()
special method is a functor
.
A closure
is a function or method that captures some external state.
The classic use case for functors is to provide key functions for sort routines. Here is a generic SortKey functor class (from file SortKey.py):
Suppose we have a list of Person objects in the people list. We can sort the list by surnames like this: people.sort(key=SortKey("surname"))
. If there are a lot of people there are bound to be some surname clashes, so we can sort by surname, and then by forename within surname, like this: people.sort(key=SortKey("surname", "forename"))
.
Another way of achieving the same thing, but without needing to create a functor at all, is to use the operator module’s operator.attrgetter()
function. For example, to sort by surname we could write: people.sort(key=operator.attrgetter("surname"))
. And similarly, to sort by surname and forename: people.sort(key=operator.attrgetter("surname", "forename"))
. The operator.attrgetter()
function returns a function (a closure) that, when called on an object, returns those attributes of the object that were specified when the closure was created.
Context managers allow us to simplify code by ensuring that certain operations are performed before and after a particular block of code is executed. The behavior is achieved because context managers define two special methods, __enter__()
and __exit__()
, that Python treats specially in the scope of a with statement. When a context manager is created in a with statement its __enter__()
method is automatically called, and when the context manager goes out of scope after its with statement its __exit__()
method is automatically called.
with expression as variable:
suite
Using nested with statements can quickly lead to a lot of indentation. Fortunately, the standard library’s contextlib module provides some additional support for context managers, including the contextlib.nested() function which allows two or more context managers to be handled in the same with statement rather than having to nest with statements.
Descriptors are classes which provide access control for the attributes of other classes. Any class that implements one or more of the descriptor special methods, __get__()
, __set__()
, and __delete__()
, is called (and can be used as) a descriptor.
Here’s the start of a modified version of the Point class that makes use of the descriptor (from the ExternalStorage.py file):
To complete our coverage of descriptors we will create the Property descriptor that mimics the behavior of the built-in property() function, at least for setters and getters.
Class decorators take a class object (the result of the class statement), and should return a class—normally a modified version of the class they decorate.
We must use nonlocal
so that the nested function uses the attribute_name from the outer scope rather than attempting to use one from its own scope.
Given a class that defines only < (or < and ==), the decorator produces the missing comparison operators by using the following logical equivalences:
@Util.complete_comparisons
class FuzzyBool:
An abstract base class (ABC) is a class that cannot be used to create objects. Instead, the purpose of such classes is to define interfaces.
Table 8.3 The Numbers Module’s Abstract Base Classes
Table 8.4 The Collections Module’s Main Abstract Base Classes
Here’s the ABC:
We have set the class’s metaclass to be abc.ABCMeta since this is a requirement for ABCs.
- We have made
__init__()
an abstract method to ensure that it is reimplemented. - The price
property is abstract
(so we cannot use the @property decorator), and is readable/writable. - We set the private __model data once in the
__init__()
method, and provide read access via the read–only model property.
The Cooker class must reimplement the __init__()
method and the price property.
vowel_counter = CharCounter()
vowel_counter("dog fish and cat fish", "aeiou") # returns: 5
As an alternative to using the @property and @name.setter decorators, we could create classes where we use a simple naming convention to identify properties. For example, if a class has methods of the form get_name()
and set_name()
, we would expect the class to have a private __name property accessed using instance.name for getting and setting.
A metaclass’s __new__()
class method is called with the metaclass, and the class name, base classes, and dictionary of the class that is to be created. We must use a reimplementation of __new__()
rather than __init__()
because we want to change the dictionary before the class is created.