Skip to content

Commit 2f3ab09

Browse files
authored
Merge pull request realpython#479 from realpython/python-magic-methods
Sample code for the article on magic methods
2 parents 57dec9a + aced9ed commit 2f3ab09

File tree

15 files changed

+375
-0
lines changed

15 files changed

+375
-0
lines changed

python-magic-methods/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Python's Magic Methods: Leverage Their Power in Your Classes
2+
3+
This folder provides the code examples for the Real Python tutorial [Python's Magic Methods: Leverage Their Power in Your Classes](https://realpython.com/python-magic-methods/).
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class BitwiseNumber:
2+
def __init__(self, value):
3+
self.value = value
4+
5+
def __and__(self, other):
6+
return type(self)(self.value & other.value)
7+
8+
def __or__(self, other):
9+
return type(self)(self.value | other.value)
10+
11+
def __xor__(self, other):
12+
return type(self)(self.value ^ other.value)
13+
14+
def __invert__(self):
15+
return type(self)(~self.value)
16+
17+
def __lshift__(self, places):
18+
return type(self)(self.value << places)
19+
20+
def __rshift__(self, places):
21+
return type(self)(self.value >> places)
22+
23+
def __repr__(self):
24+
return bin(self.value)

python-magic-methods/circle.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import math
2+
3+
4+
class Circle:
5+
def __init__(self, radius):
6+
self.radius = radius
7+
8+
def area(self):
9+
return math.pi * self.radius**2
10+
11+
def perimeter(self):
12+
return 2 * math.pi * self.radius
13+
14+
def __getattribute__(self, name):
15+
print(f"__getattribute__ called for '{name}'")
16+
return super().__getattribute__(name)
17+
18+
def __getattr__(self, name):
19+
print(f"__getattr__ called for '{name}'")
20+
if name == "diameter":
21+
return self.radius * 2
22+
return super().__getattr__(name)
23+
24+
def __setattr__(self, name, value):
25+
print(f"__setattr__ called for '{name}'")
26+
if name == "radius":
27+
if not isinstance(value, int | float):
28+
raise TypeError("radius must be a number")
29+
if value <= 0:
30+
raise ValueError("radius must be positive")
31+
super().__setattr__(name, value)

python-magic-methods/distance.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class Distance:
2+
_multiples = {
3+
"mm": 0.001,
4+
"cm": 0.01,
5+
"m": 1,
6+
"km": 1_000,
7+
}
8+
9+
def __init__(self, value, unit="m"):
10+
self.value = value
11+
self.unit = unit.lower()
12+
13+
def to_meter(self):
14+
return self.value * type(self)._multiples[self.unit]
15+
16+
def __add__(self, other):
17+
return self._compute(other, "+")
18+
19+
def __sub__(self, other):
20+
return self._compute(other, "-")
21+
22+
def _compute(self, other, operator):
23+
operation = eval(f"{self.to_meter()} {operator} {other.to_meter()}")
24+
cls = type(self)
25+
return cls(operation / cls._multiples[self.unit], self.unit)
26+
27+
def __radd__(self, other):
28+
return self + other
29+
30+
def __str__(self):
31+
return str(self.value) + self.unit
32+
33+
def __repr__(self):
34+
return f"{type(self).__name__}(value={self.value}, unit={self.unit})"

python-magic-methods/factorial.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Factorial:
2+
def __init__(self):
3+
self._cache = {0: 1, 1: 1}
4+
5+
def __call__(self, number):
6+
if number not in self._cache:
7+
self._cache[number] = number * self(number - 1)
8+
return self._cache[number]

python-magic-methods/fibonacci.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class FibonacciIterator:
2+
def __init__(self, stop=10):
3+
self._stop = stop
4+
self._index = 0
5+
self._current = 0
6+
self._next = 1
7+
8+
def __iter__(self):
9+
return self
10+
11+
def __next__(self):
12+
if self._index < self._stop:
13+
self._index += 1
14+
fib_number = self._current
15+
self._current, self._next = (
16+
self._next,
17+
self._current + self._next,
18+
)
19+
return fib_number
20+
else:
21+
raise StopIteration

python-magic-methods/number.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Number:
2+
def __init__(self, value):
3+
self.value = value
4+
5+
def __add__(self, other):
6+
print("__add__ called")
7+
if isinstance(other, Number):
8+
return Number(self.value + other.value)
9+
elif isinstance(other, int | float):
10+
return Number(self.value + other)
11+
else:
12+
raise TypeError("unsupported operand type for +")
13+
14+
def __radd__(self, other):
15+
print("__radd__ called")
16+
return self.__add__(other)
17+
18+
def __iadd__(self, other):
19+
print("__iadd__ called")
20+
return self.__add__(other)
21+
22+
def __str__(self):
23+
return str(self.value)

python-magic-methods/person.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Person:
2+
def __init__(self, name, age):
3+
self.name = name
4+
self.age = age
5+
6+
def __str__(self):
7+
return f"I'm {self.name}, and I'm {self.age} years old."
8+
9+
def __repr__(self):
10+
return f"{type(self).__name__}(name='{self.name}', age={self.age})"

python-magic-methods/point.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class Point:
2+
def __init__(self, x, y):
3+
self.x = x
4+
self.y = y
5+
6+
def __eq__(self, other):
7+
if not isinstance(other, type(self)):
8+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
9+
return self.x == other.x and self.y == other.y
10+
11+
def __lt__(self, other):
12+
if not isinstance(other, type(self)):
13+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
14+
return self.x < other.x and self.y < other.y
15+
16+
def __gt__(self, other):
17+
if not isinstance(other, type(self)):
18+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
19+
return self.x > other.x and self.y > other.y
20+
21+
def __le__(self, other):
22+
if not isinstance(other, type(self)):
23+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
24+
return self.x <= other.x and self.y <= other.y
25+
26+
def __ge__(self, other):
27+
if not isinstance(other, type(self)):
28+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
29+
return self.x >= other.x and self.y >= other.y
30+
31+
def __ne__(self, other):
32+
if not isinstance(other, type(self)):
33+
raise TypeError(f"'other' must be of type '{type(self).__name__}'")
34+
return self.x != other.x and self.y != other.y

python-magic-methods/reader.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class TextFileReader:
2+
def __init__(self, file_path):
3+
self.file_path = file_path
4+
5+
def __enter__(self):
6+
self.file_obj = open(self.file_path, mode="r")
7+
return self.file_obj
8+
9+
def __exit__(self, exc_type, exc_val, exc_tb):
10+
self.file_obj.close()

0 commit comments

Comments
 (0)