Skip to content

Commit f228157

Browse files
committed
Release version of chapter 8 notes.
1 parent f84895a commit f228157

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

‎doc/source/8_inheritance.rst

+61-23
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ A key feature of abstractions is composability: the ability to make a
77
complex object or operation out of several components. We can compose
88
objects by simply making one object a :term:`attribute` of another
99
object. This combines objects in a *has a* relationship. For example
10-
the :class:`Polynomial` class introduced in :numref:`chapter %s
10+
the :class:`~example_code.polynomial.Polynomial` class introduced in :numref:`chapter %s
1111
<objects>` *has a* :class:`tuple` of coefficients. Object composition of
1212
this sort is a core part of :term:`encapsulation`.
1313

@@ -78,7 +78,7 @@ It turns out that :func:`issubclass` is reflexive (classes are subclasses of the
7878
This means that, in a manner analogous to subset inclusion, the
7979
:term:`subclass` relationship forms a partial order on the set of all
8080
classes. This relationship defines another core mechanism for creating a new
81-
class from existing classes, :term:`inheritance`. If one class is a subclass of
81+
class from existing classes: :term:`inheritance`. If one class is a subclass of
8282
another then we say that it inherits from that class. Where composition defines
8383
a *has a* relationship, inheritance defines an *is a* relationship.
8484

@@ -110,10 +110,10 @@ cyclic group element type which can take the relevant values and which
110110
implements the group operation. This would be unfortunate for at least two
111111
reasons:
112112

113-
1. Because each group needs several elements, need a different element *type*
113+
1. Because each group needs several elements, we would need a different element *type*
114114
for each *instance* of a cyclic group. The number of classes needed would grow very fast!
115-
2. Adding a new family of groups would require adding both a group class and a
116-
set of element classes. On grounds of simplicity and robustness, always key considerations,
115+
2. Adding a new family of groups would require us to add both a group class and a
116+
set of element classes. On the basis of :term:`parsimony`,
117117
we would much prefer to only add one class in order to add a new family of
118118
groups.
119119

@@ -155,7 +155,7 @@ minimal characterisation of a group will suffice.
155155
return f"{self.value}_{self.group}"
156156
157157
def __repr__(self):
158-
return f"{self.__class__.__name__}" \
158+
return f"{type(self).__name__}" \
159159
f"({repr(self.group), repr(self.value)})"
160160
161161
@@ -181,7 +181,7 @@ minimal characterisation of a group will suffice.
181181
return f"C{self.order}"
182182
183183
def __repr__(self):
184-
return f"{self.__class__.__name__}({repr(self.order)})"
184+
return f"{type(self).__name__}({repr(self.order)})"
185185
186186
187187
:numref:`cyclic_group` shows an implementation of our minimal conception of
@@ -198,7 +198,7 @@ the concrete effects of the classes:
198198
2_C5
199199
200200
We observe that we are able to create the cyclic group of order 5. Due to the
201-
definition of the :meth:`__call__` :term:`special method` at line 35, we are
201+
definition of the :meth:`~object.__call__` :term:`special method` at line 35, we are
202202
then able to create elements of the group by calling the group object. The group
203203
operation then has the expected effect:
204204

@@ -300,7 +300,7 @@ group as follows:
300300
return f"G{self.degree}"
301301
302302
def __repr__(self):
303-
return f"{self.__class__.__name__}({repr(self.degree)})"
303+
return f"{type(self).__name__}({repr(self.degree)})"
304304
305305
We won't illustrate the operation of this class, though the reader is welcome to
306306
:keyword:`import` the :mod:`example_code.groups_basic` module and experiment.
@@ -337,9 +337,9 @@ does.
337337
'''
338338
Parameters
339339
----------
340-
n: int
341-
The primary group parameter, such as order or degree. The
342-
precise meaning of n changes from subclass to subclass.
340+
n: int
341+
The primary group parameter, such as order or degree. The
342+
precise meaning of n changes from subclass to subclass.
343343
'''
344344
self.n = n
345345
@@ -351,7 +351,7 @@ does.
351351
return f"{self.notation}{self.n}"
352352
353353
def __repr__(self):
354-
return f"{self.__class__.__name__}({repr(self.n)})"
354+
return f"{type(self).__name__}({repr(self.n)})"
355355
356356
357357
class CyclicGroup(Group):
@@ -446,10 +446,6 @@ specific to each object of the class. Our new version of the code instead sets a
446446
single attribute that is common to all objects of this class. This is called a
447447
:term:`class attribute`.
448448

449-
.. note::
450-
451-
Come back and explain class attributes in more detail.
452-
453449
.. _runtime_attributes:
454450

455451
Attributes resolve at runtime
@@ -512,7 +508,31 @@ called, and the object `self` is the actual concrete class instance, with all of
512508
the attributes that are defined for it. In this case, even though
513509
:meth:`__str__` is defined on :class:`Group`, `self` has type
514510
:class:`CyclicGroup`, and therefore `self.notation` is well-defined and has the
515-
value `"C"`.
511+
value `"C"`.
512+
513+
Parametrising over class
514+
~~~~~~~~~~~~~~~~~~~~~~~~
515+
516+
When we create a class, there is always the possibility that someone will come
517+
along later and create a subclass of it. It is therefore an important design
518+
principle to avoid doing anything which might cause a problem in a subclass.
519+
One important example of this is anywhere where it is assumed that the class of
520+
:data:`self` is in fact the current class and not some subclass of it. For this
521+
reason, it is almost always a bad idea to explicitly use the name of the
522+
current class inside its definition. Instead, we should use the fact that
523+
`type(self)` returns the type (i.e. class) of the current object. It is for
524+
this reason that we typically use the formula `type(self).__name__` in the
525+
:meth:`~object.__repr__` method of an object. A similar procedure applies if we
526+
need to create another object of the same class as the current object. For
527+
example, one might create the next larger :class:`~example_code.groups.Group`
528+
than the current one with:
529+
530+
.. code-block:: python3
531+
532+
type(self)(self.n+1)
533+
534+
Observe that since `type(self)` is a :term:`class`, we can :term:`instantiate`
535+
it by calling it.
516536

517537
Calling parent class methods
518538
----------------------------
@@ -533,7 +553,7 @@ Calling parent class methods
533553
return self.length * self.width
534554
535555
def __repr__(self):
536-
return f"{self.__class__.__name__}{self.length, self.width!r}"
556+
return f"{type(self).__name__}{self.length, self.width!r}"
537557
538558
:numref:`rectangle_class` shows a basic implementation of a class describing a
539559
rectangle. We might also want a class defining a square. Rather than redefining
@@ -572,7 +592,7 @@ the :func:`super` function. :numref:`square_class` demonstrates its application.
572592
super().__init__(length, length)
573593
574594
def __repr__(self):
575-
return f"{self.__class__.__name__}({self.length!r})"
595+
return f"{type(self).__name__}({self.length!r})"
576596
577597
The :func:`super` function returns a version of the current object in which none
578598
of the :term:`methods <method>` have been overridden by the current
@@ -593,9 +613,27 @@ the one which best matches the circumstances. However, sometimes there is no
593613
good match, or it might be that the programmer wants user code to be able to
594614
catch exactly this exception without the risk that some other operation will
595615
raise the same exception and be caught by mistake. In this case, it is necessary
596-
to create a new type of exception. A new exception will be an object which
597-
behaves in most respects like other exceptions, except that it's a new
598-
:term:`class` and may behave differently in particular circumstances.
616+
to create a new type of exception.
617+
618+
A new exception will be a new :term:`class` which inherits from another
619+
exception class. In most cases, the only argument that the exception
620+
:term:`constructor` takes is an error message, and the base :class:`Exception`
621+
class already takes this. This means that the subclass definition may only need
622+
to define the new class. Now, a class definition is a Python block and, as a
623+
matter of :term:`syntax`, a block cannot be empty. Fortunately, the Python
624+
language caters for this situation with the :keyword:`pass` statement, which
625+
simply does nothing. For example, suppose we need to be able to distinguish the
626+
:class:`ValueError` which occurs in entity validation from other occurrences of
627+
:class:`ValueError`. For example it might be advantageous to enable a user to
628+
catch exactly these errors. In this case, we're still talking about some form
629+
of value error, so we'll want our new error class to inherit from
630+
:class:`ValueError`. We could achieve this as follows:
631+
632+
.. code-block:: python3
633+
634+
class GroupValidationError(ValueError):
635+
pass
636+
599637
600638
..
601639
.. _abstract_base_classes:

‎doc/webgit

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 96f9b04f6bb9d697c32ee40abef73927a797eeb3
1+
Subproject commit c5544a45f10699ffbf661e4d53d500968a76bbac

‎example_code/groups.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __str__(self):
2727
return f"{self.value}_{self.group}"
2828

2929
def __repr__(self):
30-
return f"{self.__class__.__name__}" \
30+
return f"{type(self).__name__}" \
3131
f"({repr(self.group), repr(self.value)})"
3232

3333

@@ -53,7 +53,7 @@ def __str__(self):
5353
return f"{self.notation}{self.n}"
5454

5555
def __repr__(self):
56-
return f"{self.__class__.__name__}({repr(self.n)})"
56+
return f"{type(self).__name__}({repr(self.n)})"
5757

5858

5959
class CyclicGroup(Group):

‎example_code/groups_basic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __str__(self):
1818
return f"{self.value}_{self.group}"
1919

2020
def __repr__(self):
21-
return f"{self.__class__.__name__}" \
21+
return f"{type(self).__name__}" \
2222
f"({repr(self.group), repr(self.value)})"
2323

2424

@@ -45,7 +45,7 @@ def __str__(self):
4545
return f"C{self.order}"
4646

4747
def __repr__(self):
48-
return f"{self.__class__.__name__}({repr(self.order)})"
48+
return f"{type(self).__name__}({repr(self.order)})"
4949

5050

5151
class GeneralLinearGroup:
@@ -73,4 +73,4 @@ def __str__(self):
7373
return f"G{self.degree}"
7474

7575
def __repr__(self):
76-
return f"{self.__class__.__name__}({repr(self.degree)})"
76+
return f"{type(self).__name__}({repr(self.degree)})"

0 commit comments

Comments
 (0)