@@ -7,7 +7,7 @@ A key feature of abstractions is composability: the ability to make a
7
7
complex object or operation out of several components. We can compose
8
8
objects by simply making one object a :term: `attribute ` of another
9
9
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
11
11
<objects>` *has a * :class: `tuple ` of coefficients. Object composition of
12
12
this sort is a core part of :term: `encapsulation `.
13
13
@@ -78,7 +78,7 @@ It turns out that :func:`issubclass` is reflexive (classes are subclasses of the
78
78
This means that, in a manner analogous to subset inclusion, the
79
79
:term: `subclass ` relationship forms a partial order on the set of all
80
80
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
82
82
another then we say that it inherits from that class. Where composition defines
83
83
a *has a * relationship, inheritance defines an *is a * relationship.
84
84
@@ -110,10 +110,10 @@ cyclic group element type which can take the relevant values and which
110
110
implements the group operation. This would be unfortunate for at least two
111
111
reasons:
112
112
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 *
114
114
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 ` ,
117
117
we would much prefer to only add one class in order to add a new family of
118
118
groups.
119
119
@@ -155,7 +155,7 @@ minimal characterisation of a group will suffice.
155
155
return f"{self.value}_{self.group}"
156
156
157
157
def __repr__(self):
158
- return f"{self.__class__ .__name__}" \
158
+ return f"{type( self) .__name__}" \
159
159
f"({repr(self.group), repr(self.value)})"
160
160
161
161
@@ -181,7 +181,7 @@ minimal characterisation of a group will suffice.
181
181
return f"C{self.order}"
182
182
183
183
def __repr__(self):
184
- return f"{self.__class__ .__name__}({repr(self.order)})"
184
+ return f"{type( self) .__name__}({repr(self.order)})"
185
185
186
186
187
187
:numref: `cyclic_group ` shows an implementation of our minimal conception of
@@ -198,7 +198,7 @@ the concrete effects of the classes:
198
198
2_C5
199
199
200
200
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
202
202
then able to create elements of the group by calling the group object. The group
203
203
operation then has the expected effect:
204
204
@@ -300,7 +300,7 @@ group as follows:
300
300
return f"G{self.degree}"
301
301
302
302
def __repr__(self):
303
- return f"{self.__class__ .__name__}({repr(self.degree)})"
303
+ return f"{type( self) .__name__}({repr(self.degree)})"
304
304
305
305
We won't illustrate the operation of this class, though the reader is welcome to
306
306
:keyword: `import ` the :mod: `example_code.groups_basic ` module and experiment.
@@ -337,9 +337,9 @@ does.
337
337
'''
338
338
Parameters
339
339
----------
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.
343
343
'''
344
344
self.n = n
345
345
@@ -351,7 +351,7 @@ does.
351
351
return f"{self.notation}{self.n}"
352
352
353
353
def __repr__(self):
354
- return f"{self.__class__ .__name__}({repr(self.n)})"
354
+ return f"{type( self) .__name__}({repr(self.n)})"
355
355
356
356
357
357
class CyclicGroup(Group):
@@ -446,10 +446,6 @@ specific to each object of the class. Our new version of the code instead sets a
446
446
single attribute that is common to all objects of this class. This is called a
447
447
:term: `class attribute `.
448
448
449
- .. note ::
450
-
451
- Come back and explain class attributes in more detail.
452
-
453
449
.. _runtime_attributes :
454
450
455
451
Attributes resolve at runtime
@@ -512,7 +508,31 @@ called, and the object `self` is the actual concrete class instance, with all of
512
508
the attributes that are defined for it. In this case, even though
513
509
:meth: `__str__ ` is defined on :class: `Group `, `self ` has type
514
510
: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.
516
536
517
537
Calling parent class methods
518
538
----------------------------
@@ -533,7 +553,7 @@ Calling parent class methods
533
553
return self.length * self.width
534
554
535
555
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}"
537
557
538
558
:numref: `rectangle_class ` shows a basic implementation of a class describing a
539
559
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.
572
592
super().__init__(length, length)
573
593
574
594
def __repr__(self):
575
- return f"{self.__class__ .__name__}({self.length!r})"
595
+ return f"{type( self) .__name__}({self.length!r})"
576
596
577
597
The :func: `super ` function returns a version of the current object in which none
578
598
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
593
613
good match, or it might be that the programmer wants user code to be able to
594
614
catch exactly this exception without the risk that some other operation will
595
615
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
+
599
637
600
638
..
601
639
.. _abstract_base_classes:
0 commit comments