Skip to content

Commit 62fbed3

Browse files
committed
Merge remote-tracking branch 'origin/development' into development
2 parents 88a077f + dfdac1f commit 62fbed3

File tree

3 files changed

+211
-0
lines changed

3 files changed

+211
-0
lines changed

source-code/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ was used to develop it.
2626
`pytest`.
2727
1. `oo_vs_functional.ipynb`: comparing object-oriented approach to
2828
functional approach, including coroutines.
29+
1. `metaclasses`: illustration of the use of metaclasses in Python.

source-code/metaclasses/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Metaclasses
2+
3+
Python supports metaclasses, i.e., classes that control the creation of other
4+
classes.
5+
6+
Although this feature should not be overused, it can be useful in some
7+
circumstances.
8+
9+
See the following [article](https://medium.com/fintechexplained/advanced-python-metaprogramming-980da1be0c7d) for a discussion.
10+
11+
12+
## What is it?
13+
14+
1. `metaclasses.ipynb`: Jupyter notebook illustrating using a metaclass to
15+
verify the correct implementation of a mix-in.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "72694516-dbb4-47dc-bb49-cf0832d2eb2b",
6+
"metadata": {},
7+
"source": [
8+
"Metaclasses can be quite useful in some circumstances. A metaclass serves as a constructor for classes and can be used for validation at defintion time.\n",
9+
"\n",
10+
"Consider the following metaclass: it will check whether the class has a `_sound` attribute, if not, it will fail upon defintion. It will also add a `make_sound` method, acting similar to a mix-in."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": 42,
16+
"id": "a2c0fb42-c8b7-4e7c-9044-f0a543a11a6c",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"class SoundMeta(type):\n",
21+
" \n",
22+
" def __new__(cls, what, bases=None, dict=None):\n",
23+
" if '_sound' not in dict:\n",
24+
" raise Exception('no _sound attribute defined')\n",
25+
" new_dict = dict.copy()\n",
26+
" new_dict['make_sound'] = lambda self: f'say {self._sound}'\n",
27+
" return type.__new__(cls, what, bases, new_dict)"
28+
]
29+
},
30+
{
31+
"cell_type": "markdown",
32+
"id": "356b35e2-b534-44d1-bea6-67675cc0bdbb",
33+
"metadata": {},
34+
"source": [
35+
"Note that the `SoundMeta` class has `type` as a base class, to `SoundMeta` is a class. It defines its own `__new__` function to control object creation, which calls the parent's (i.e., `type`'s) `__new__` method with the modified dictionary."
36+
]
37+
},
38+
{
39+
"cell_type": "markdown",
40+
"id": "64654746-fc9f-4448-b26d-1887b391fd71",
41+
"metadata": {},
42+
"source": [
43+
"The `Dog` class has a `sound` attribute, and defines some attributes and methods of its own."
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": 43,
49+
"id": "73852058-22e2-4036-86ef-4e205a12bef7",
50+
"metadata": {},
51+
"outputs": [],
52+
"source": [
53+
"class Dog(metaclass=SoundMeta):\n",
54+
" _sound: str = 'woof'\n",
55+
" \n",
56+
" def __init__(self, name):\n",
57+
" self._name = name\n",
58+
" \n",
59+
" @property\n",
60+
" def name(self):\n",
61+
" return self.name"
62+
]
63+
},
64+
{
65+
"cell_type": "markdown",
66+
"id": "696ff05b-273d-4dd3-ab2c-ac5a219cdee3",
67+
"metadata": {},
68+
"source": [
69+
"We can instantiate a particular `Dog` and call its `make_sound()` method."
70+
]
71+
},
72+
{
73+
"cell_type": "code",
74+
"execution_count": 44,
75+
"id": "678a6d7e-f165-4183-a183-001c0a333a54",
76+
"metadata": {},
77+
"outputs": [
78+
{
79+
"data": {
80+
"text/plain": [
81+
"'say woof'"
82+
]
83+
},
84+
"execution_count": 44,
85+
"metadata": {},
86+
"output_type": "execute_result"
87+
}
88+
],
89+
"source": [
90+
"dog = Dog('felix')\n",
91+
"dog.make_sound()"
92+
]
93+
},
94+
{
95+
"cell_type": "markdown",
96+
"id": "bfbfbd5f-b2d4-4aa2-acf4-b331ce86a768",
97+
"metadata": {},
98+
"source": [
99+
"The `Bell` class also has `SoundMeta` as metaclass."
100+
]
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": 38,
105+
"id": "862b88a9-c467-43d3-975f-70aac7cdc330",
106+
"metadata": {},
107+
"outputs": [],
108+
"source": [
109+
"class Bell(metaclass=SoundMeta):\n",
110+
" _sound: str = 'dong'"
111+
]
112+
},
113+
{
114+
"cell_type": "markdown",
115+
"id": "ab5b88a3-1374-4980-aa97-4a33dc867c75",
116+
"metadata": {},
117+
"source": [
118+
"It too can be used to instantiate objects that make a sound."
119+
]
120+
},
121+
{
122+
"cell_type": "code",
123+
"execution_count": 45,
124+
"id": "be700599-60bc-490a-8fbb-4bdae3cee06f",
125+
"metadata": {},
126+
"outputs": [
127+
{
128+
"data": {
129+
"text/plain": [
130+
"'say dong'"
131+
]
132+
},
133+
"execution_count": 45,
134+
"metadata": {},
135+
"output_type": "execute_result"
136+
}
137+
],
138+
"source": [
139+
"big_ben = Bell()\n",
140+
"big_ben.make_sound()"
141+
]
142+
},
143+
{
144+
"cell_type": "markdown",
145+
"id": "31cebd32-f4df-4a5b-9516-8b2a7a5ffaa3",
146+
"metadata": {},
147+
"source": [
148+
"If you try to create a class that has no `_sound` attribute, the class definition will result in an error."
149+
]
150+
},
151+
{
152+
"cell_type": "code",
153+
"execution_count": 47,
154+
"id": "0f537499-6a88-4ca6-a9d8-1fad1ef5272c",
155+
"metadata": {},
156+
"outputs": [
157+
{
158+
"name": "stdout",
159+
"output_type": "stream",
160+
"text": [
161+
"no _sound attribute defined\n"
162+
]
163+
}
164+
],
165+
"source": [
166+
"try:\n",
167+
" class Phone(metaclass=SoundMeta):\n",
168+
" pass\n",
169+
"except Exception as e:\n",
170+
" print(e)"
171+
]
172+
}
173+
],
174+
"metadata": {
175+
"kernelspec": {
176+
"display_name": "Python 3",
177+
"language": "python",
178+
"name": "python3"
179+
},
180+
"language_info": {
181+
"codemirror_mode": {
182+
"name": "ipython",
183+
"version": 3
184+
},
185+
"file_extension": ".py",
186+
"mimetype": "text/x-python",
187+
"name": "python",
188+
"nbconvert_exporter": "python",
189+
"pygments_lexer": "ipython3",
190+
"version": "3.9.5"
191+
}
192+
},
193+
"nbformat": 4,
194+
"nbformat_minor": 5
195+
}

0 commit comments

Comments
 (0)