Skip to content

Commit 870d542

Browse files
committed
Add special methods
1 parent 7128232 commit 870d542

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Special methods
2+
3+
## Arithmetical operations
4+
5+
Consider a class defining a fraction. It should have two attributes, *nominator* (named shortly `nom`) and *denominator* (`denom`):
6+
7+
```python
8+
class Fraction:
9+
def __init__(self, nom, denom):
10+
self.nom = nom
11+
self.denom = denom
12+
```
13+
14+
It is possible to perform mathematical operations on fractions, like addition, subtraction, multiplication and division. Let's define a function `mul` that takes two fractions, multiplies them, and returns a `Fraction` instance, which is their factor:
15+
16+
```python
17+
def mul(a, b):
18+
return Fraction(a.nom * b.nom, a.denom * b.denom)
19+
```
20+
21+
Actually this function is specific for fractions only. So, in order to keep things together, it is better to make it a method in a fraction:
22+
23+
```python
24+
class Fraction:
25+
def __init__(self, nom, denom):
26+
self.nom = nom
27+
self.denom = denom
28+
29+
def mul(self, other):
30+
return Fraction(self.nom * self.nom, self.denom * self.denom)
31+
```
32+
33+
We may use this method as follows:
34+
35+
```python
36+
x = Fraction(1, 2)
37+
y = Fraction(5, 6)
38+
39+
z = x.mul(y)
40+
```
41+
42+
The last line does not look good. It would be much more clear if we were able to write `c = a * b`. In fact we can. Let's rename the method `mul` to `__mul__` (two underscored in the beginning and the end, like in `__init__`):
43+
44+
```python
45+
class Fraction:
46+
def __init__(self, nom, denom):
47+
self.nom = nom
48+
self.denom = denom
49+
50+
def __mul__(self, other):
51+
return Fraction(self.nom * self.nom, self.denom * self.denom)
52+
```
53+
54+
Now we can write:
55+
56+
57+
```python
58+
x = Fraction(1, 2)
59+
y = Fraction(5, 6)
60+
61+
z = a * b
62+
```
63+
64+
Methods with two underscores in the beginning and the end are [*special methods*](https://diveintopython3.net/special-method-names.html). Generally you never invoke them directly. For example, you never call the `__init__` method explicitly. It is automatically invoked on instance creation. `__mul__` is another such method and it is called when two objects are multiplied.
65+
66+
There are many such methods. You can find the list of all of them in the [documentation](https://docs.python.org/3/reference/datamodel.html#special-method-names). Here are the most basic ones used for arithmetics:
67+
68+
| You want… | So you write… | And the special function is… |
69+
| ------------------ | ------------- | ---------------------------- |
70+
| addition | `x + y` | `x.__add__(y)` |
71+
| subtraction | `x - y` | `x.__sub__(y)` |
72+
| multiplication | `x * y` | `x.__mul__(y)` |
73+
| division | `x / y` | `x.__truediv__(y)` |
74+
| modulo (remainder) | `x % y` | `x.__mod__(y)` |
75+
| raise to power | `x ** y` | `x.__pow__(y)` |
76+
77+
Please implement `__add__`, `__sub__` and `__truediv__` for the `Fraction` class.
78+
79+
80+
## Pretty-printing objects
81+
82+
Other special methods allow to access “elements” of our class using square brackets (like for lists or dictionaries) ([`__getitem__`](https://docs.python.org/3/reference/datamodel.html#object.__getitem__) and [`__setitem__`](https://docs.python.org/3/reference/datamodel.html#object.__setitem__)), getting the “length” of our custom sequence ([`__len__`](https://docs.python.org/3/reference/datamodel.html#object.__len__)), etc.
83+
84+
An important special method you should always implement is `__str__`. Consider the following code:
85+
86+
```python
87+
class Fraction:
88+
def __init__(self, nom, denom):
89+
self.nom = nom
90+
self.denom = denom
91+
92+
frac = Fraction(1, 2)
93+
94+
print(frac)
95+
```
96+
97+
If you run it, the output will be something like:
98+
99+
```
100+
<__main__.Fraction object at 0x7f61ad175b80>
101+
```
102+
103+
This is not very useful. Luckily, the `__str__` method can be used to convert the class into a string that can be printed:
104+
105+
```python
106+
class Fraction:
107+
def __init__(self, nom, denom):
108+
self.nom = nom
109+
self.denom = denom
110+
111+
def __str__(self):
112+
# This method must RETURN a string. Do not try to print anything!
113+
return f"{self.nom}/{self.denom}"
114+
115+
frac = Fraction(1, 2)
116+
117+
print("Our fraction is:", frac)
118+
```
119+
120+
This time the output looks much better:
121+
122+
```
123+
Our fraction is: 1/2
124+
```
125+
126+
## Object comparison
127+
128+
Some other special functions that are very useful are comparisons. Fractions can be equal (if both nominators and denominators equal) or one can be larger than another one. Special functions doing comparisons should always return `True` or `False`. Their names are as follow:
129+
130+
| Comparison | Special function |
131+
| ---------- | ------------------- |
132+
| `x < y` | `x.__lt__(self, y)` |
133+
| `x <= y` | `x.__le__(self, y)` |
134+
| `x == y` | `x.__eq__(self, y)` |
135+
| `x != y` | `x.__ne__(self, y)` |
136+
| `x > y` | `x.__gt__(self, y)` |
137+
| `x >= y` | `x.__ge__(self, y)` |
138+
139+
140+
141+
> **Note: Fraction reduction**
142+
>
143+
> When you implement arithmetic operation on fractions, you may end up with a reducible fraction, e.g. 2/3 × 3/5 = 6/15. Naturally, this fraction is equal to 2/5.
144+
>
145+
> In order to have your class behave elegantly and for comparisons to work, you should always reduce the fraction by dividing both the nominator and denominator by their [greatest common divisor](https://en.wikipedia.org/wiki/Greatest_common_divisor), which can be found using the function `gcd` in the `math` module. In which method you should do it? If you do this in every special method responsible for mathematical operation, you will need to do it several times. Furthermore, if someone creates an instance of you class e.g. as `Fraction(4, 6)`, the fraction will not be reduced. However if you make the reduction in the constructor, this will cover all use cases:
146+
>
147+
> ```python
148+
> from math import gcd
149+
>
150+
> class Fraction:
151+
> def __init__(self, nom, denom):
152+
> r = gcd(nom, denom)
153+
> self.nom = nom // r # we use // for integer division
154+
> self.denom = denom // r
155+
>
156+
> def __str__(self):
157+
> return f"{self.nom}/{self.denom}"
158+
>
159+
> def __mul__(self, other):
160+
> return Fraction(self.nom * self.nom, self.denom * self.denom)
161+
> ```
162+
>
163+
> Also in the constructor you can do more sanitizations, like checking if the denominator is not 0 (and raising `ValueError` in such case), making sure that the denominator is always positive (and the sign of the nominator is adjusted accordingly), etc.
164+
165+
166+
167+
168+
<hr />
169+
170+
Published under [Creative Commons Attribution-NonCommercial-ShareAlike](https://creativecommons.org/licenses/by-nc-sa/4.0/license.

0 commit comments

Comments
 (0)