Skip to content

Commit d822937

Browse files
committed
Добавил текст про модули
1 parent 8f274e2 commit d822937

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

1_python_basics/5_modules.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
###Что это такое
2+
Модуль – кусок кода, который можно использовать в другом коде. В самом простом случае это файл.
3+
В любом проекте функциональность разбивается на куски, каждый кусок селится в свой модуль.
4+
5+
Всё, что устанавливается с помощью pip, представляет собой модули. Модули иерархические:
6+
ты можешь импортировать модуль `markdown` и пользоваться им, не зная, что внутри он импортирует
7+
ещё десяток других модулей: Питон сам всё разрулит.
8+
9+
10+
###Как этим пользоваться
11+
Имя модуля совпадает с именем файла и должно быть нормальным именем переменной в Питоне: например, не содержать
12+
знаков минуса.
13+
14+
Предположим, что есть папка `3_bars`, в ней файл `data_loaders.py` с таким содержанием:
15+
16+
:::python
17+
import csv
18+
import json
19+
20+
21+
def load_from_json(filepath):
22+
with open(filepath, 'r') as file_handler:
23+
return json.load(file_handler)
24+
25+
26+
def load_from_csv(filepath):
27+
with open(filepath, 'r') as file_handler:
28+
return list(csv.reader(file_handler))
29+
30+
А рядом есть файл `bars.py`, в котором мы хотим загрузить данные из csv. Вот что в нём можно написать:
31+
32+
:::python
33+
from data_loaders import load_from_csv # импортируем функцию из модуля
34+
35+
36+
print(load_from_csv('bars.csv')
37+
38+
А можно так:
39+
40+
:::python
41+
import data_loaders # импортируем модуль целиком
42+
43+
44+
print(data_loaders.load_from_csv('bars.csv') # используем функцию с указанием модуля
45+
46+
Есть ещё вариант `from data_loaders import *`, но он вне закона. Забудьте о нём.
47+
48+
49+
###Запуск модуля как скрипта
50+
51+
Когда Питон видит `import data_loaders`, он находит файл `data_loaders.py` и выполняет его. Реально выполняет:
52+
если в нём есть код, он будет выполнен. Даже если это не просто объявления функций, а их вызов. Представим,
53+
что когда мы писали код в `data_loaders.py`, мы его дебажили. Например, так:
54+
55+
:::python
56+
import json
57+
58+
59+
def load_from_json(filepath):
60+
with open(filepath, 'r') as file_handler:
61+
return json.load(file_handler)
62+
63+
64+
print(load_from_json('test.json'))
65+
66+
67+
Теперь если мы импортируем этот модуль (`import data_loaders`), девятая строка выполнится, файл загрузится и выведется
68+
на экран. А ведь в `bars.py` это не нужно! Можно этот код удалить, но тогда будет неудобно дорабатывать функцию
69+
`load_from_json`: при изменении надо будет добавлять отладочный принт, а потом удалять.
70+
71+
Вот правильный способ это обойти:
72+
73+
:::python
74+
import json
75+
76+
77+
def load_from_json(filepath):
78+
with open(filepath, 'r') as file_handler:
79+
return json.load(file_handler)
80+
81+
82+
if __name__ == '__main__':
83+
print(load_from_json('test.json'))
84+
85+
Иф на девятой строке значит "выполняй меня только если файл запущен напрямую, а не импортирован".
86+
Теперь при запуске `python data_loaders.py` будет выполняться дебажная загрузка кода, а
87+
при импорте этого модуля – не будет. То, что надо.
88+
89+
`__main__` – одна из переменных магических переменных. Их можно узнать по двойным подчёркиваниям по краям.
90+
Такие переменные доступны всегда и Питон запишет нужные значения в них за нас. В `__main__` хранится название модуля,
91+
из которого был импортирован данный модуль. Если модуль выполняется напрямую, Питон запишет в эту переменную
92+
значение `__main__` ([доки](https://docs.python.org/3/library/__main__.html)). Хитро, а?
93+
94+
95+
###Подводные камни
96+
97+
Главный подводный камень – рекурсивный импорт. Это если мы импортируем `data_loaders` из `bars`, а для `data_loaders`
98+
нужен `bars`. Вот так:
99+
100+
:::python
101+
# bars.py
102+
import data_loaders
103+
104+
# data_loaders.py
105+
import bars
106+
107+
Бах! Всё сломается при запуске.
108+
109+
Иногда бывает ещё веселее: когда импорты замыкаются в трёх и более файлах. Типа того:
110+
111+
:::python
112+
# bars.py
113+
import data_loaders
114+
115+
# data_loaders.py
116+
import helpers
117+
118+
# helpers.py
119+
import bars
120+
121+
Всё сломается так же, как в примере выше, но ещё и заставит поломать голову при починке.
122+
123+
Чинить такие случаи просто: разбивать код на максимально независимые модули. В примере выше, например,
124+
файлу `helpers.py` зачем-то нужен `bars.py`. Так быть не должно: в `helpers.py` должны жить
125+
максимально независимые общие функции, которые используются в других файлах. Не наоборот.
126+
127+
128+
###Как работает под капотом
129+
130+
В памяти все загруженные модули хранятся в `sys.modules`. Иногда встречаются случаи, когда файла нет, а модуль есть.
131+
Это не сложно устроить:
132+
133+
:::python
134+
# mod.py
135+
import sys
136+
from types import ModuleType
137+
138+
139+
dynamic_module = ModuleType(__name__)
140+
dynamic_module.x = 5
141+
142+
sys.modules['some_weird_module'] = dynamic_module
143+
144+
145+
# script.py
146+
import mod # тут выполнился код из mod.py
147+
import some_weird_module # модуль есть, а файла – нет
148+
149+
150+
print(some_weird_module.x) # 5
151+
152+
153+
Делать так незаконно: это неочевидно, затрудняет отладку и вредит читаемости. Не надо так.

0 commit comments

Comments
 (0)