Python для начинающих
Open in Telegram
1 240
Subscribers
No data24 hours
+17 days
+130 days
Posts Archive
Используем
shutil: копируем и удаляем файлы без боли
Работа с файлами в Python часто начинается с модуля os, но как только дело доходит до копирования и целых папок — на сцену выходит shutil. Это такой «швейцарский нож» для файловой системы.
Подключается просто:
import shutil
from pathlib import Path
## Копирование файлов
Базовый вариант — shutil.copy():
src = Path("data/source.txt")
dst = Path("backup/source_copy.txt")
shutil.copy(src, dst)
copy переносит содержимое и права доступа, но не метаданные (например, время изменения).
Если нужны максимально точные «клоны» файла, есть copy2:
shutil.copy2(src, dst)
Разница — в сохранении метаданных (atime, mtime и т.п.). Для бэкапов это часто критично.
## Копирование папок
Для директорий используется copytree:
src_dir = Path("data")
dst_dir = Path("data_backup")
shutil.copytree(src_dir, dst_dir)
Важно: если data_backup уже существует, будет ошибка. В Python 3.8+ можно указать dirs_exist_ok=True:
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
Так можно «обновлять» уже существующую папку бэкапа.
## Удаление файлов и папок
Одиночные файлы удобнее удалять через Path.unlink(), а вот для целых деревьев директорий есть rmtree:
target_dir = Path("old_logs")
shutil.rmtree(target_dir)
Команда безвозвратная: корзины не будет, поэтому перед вызовом стоит дважды проверить путь или добавить «защиту от дурака»:
if "backup" in str(target_dir):
shutil.rmtree(target_dir)
## Мини-утилита «скопировать и почистить»
Соберём всё вместе: скопируем папку и удалим старую:
def move_folder(src: Path, dst: Path):
shutil.copytree(src, dst, dirs_exist_ok=True)
shutil.rmtree(src)
move_folder(Path("tmp_uploads"), Path("storage/uploads"))
Фактически мы реализовали аналог «перемещения» папки силами shutil.
---
shutil хорош тем, что закрывает 80% задач по управлению файлами: копирование, рекурсивные операции, бэкапы, чистка временных директорий. А главное — код остаётся коротким и читаемым, без ручного обхода каталогов и велосипедов на os.listdir().Генерация диаграмм с pygraphviz: основы визуализации графов
Иногда проще один раз увидеть, чем сто раз распечатать
print(). Особенно когда дело касается связей: кто с кем соединен, в каком направлении идет поток данных, как устроена архитектура проекта. Для этого отлично подходит pygraphviz — оболочка над знаменитым Graphviz, позволяющая генерировать диаграммы прямо из Python.
---
### Установка
pip install pygraphviz
Важно: на некоторых системах сначала нужно установить сам Graphviz (через пакетный менеджер ОС).
---
### Простейший граф
Создадим ориентированный граф и сохраним его как PNG:
from pygraphviz import AGraph
g = AGraph(directed=True)
g.add_node("User")
g.add_node("API")
g.add_node("DB")
g.add_edge("User", "API")
g.add_edge("API", "DB")
g.layout(prog="dot") # алгоритм раскладки
g.draw("simple_graph.png")
prog="dot" — классический и самый читаемый для иерархических структур (запросы сверху, база снизу).
---
### Стайлинг: делаем диаграмму понятной
Граф без стиля — это просто клубок линий. Добавим цвета и формы, чтобы по диаграмме можно было ориентироваться за секунды:
from pygraphviz import AGraph
g = AGraph(directed=True, strict=True, rankdir="LR") # слева направо
g.add_node("Client", shape="box", style="filled", fillcolor="#AED6F1")
g.add_node("Service", shape="ellipse", style="filled", fillcolor="#A9DFBF")
g.add_node("Cache", shape="diamond", style="filled", fillcolor="#F9E79F")
g.add_edge("Client", "Service", label="HTTP")
g.add_edge("Service", "Cache", label="GET")
g.add_edge("Cache", "Service", label="HIT", color="green")
g.add_edge("Service", "Client", label="Response", color="blue")
g.graph_attr.update(label="Request Flow", fontsize="20")
g.layout(prog="dot")
g.draw("styled_graph.png")
Ключевые идеи:
- rankdir="LR" — направление слева направо (удобно для потоков).
- shape, fillcolor, style — визуальное кодирование типов узлов.
- label и color у ребер помогают понимать протоколы, типы взаимодействия и т.п.
---
### Быстрая визуализация структур из кода
pygraphviz удобно использовать для генерации диаграмм по данным из программы: граф зависимостей модулей, цепочка этапов обработки данных, схема микросервисов. Достаточно обхода вашей структуры (словаря, списка связей, дерева) и вызова add_node / add_edge в цикле.
---
pygraphviz хорош тем, что избавляет от ручного рисования схем в редакторах: диаграмма становится частью кода, обновляется автоматически и всегда соответствует реальности. Для начинающего питониста это отличный инструмент, чтобы увидеть свои структуры данных и архитектуру, а не только представлять их в голове.### Работа с буфером ввода-вывода с помощью
io.StringIO
Иногда хочется поработать с текстом «как с файлом», но без создания настоящего файла на диске. Для этого в Python есть удобный инструмент — io.StringIO. Это такой «файлик в памяти»: его можно читать, писать в него, перематывать курсор — почти как с обычным файлом.
---
### Зачем вообще нужен StringIO?
Типичные сценарии:
1. Тестирование кода, который работает с файлами, без создания временных файлов.
2. Промежуточная обработка строк в виде потока: удобно, когда данные приходят частями.
3. Перенаправление вывода (например, print) в строку, чтобы потом её обработать.
---
### Базовый пример: пишем и читаем
from io import StringIO
buffer = StringIO()
buffer.write("Hello, world!\n")
buffer.write("This is in-memory file.\n")
# Перемещаем курсор в начало, как после открытия файла для чтения
buffer.seek(0)
content = buffer.read()
print(content)
Здесь buffer ведет себя почти как файл, но все хранится в оперативной памяти в виде строки.
---
### Построчное чтение
from io import StringIO
data = "line 1\nline 2\nline 3\n"
buffer = StringIO(data)
for line in buffer:
print(line.strip())
Интерфейс знакомый: можно итерироваться по строкам, использовать readline(), readlines() и т.д.
---
### Перенаправление print в строку
Иногда нужно собрать то, что обычно выводится в консоль:
from io import StringIO
import sys
buffer = StringIO()
stdout_backup = sys.stdout
try:
sys.stdout = buffer
print("First line")
print("Second line")
finally:
sys.stdout = stdout_backup
result = buffer.getvalue()
print("Captured:")
print(result)
buffer.getvalue() возвращает всю накопленную строку. Удобно для логирования и тестов.
---
### Важные нюансы
- StringIO работает только с текстом (str). Для байтов используйте io.BytesIO.
- Метод getvalue() можно вызывать сколько угодно раз — он не сдвигает курсор.
- Не забывайте про seek(0), если хотите перечитать содержимое с начала.
io.StringIO — отличный способ тренироваться с файловым API, не засоряя диск, и гибкий инструмент для чистого и удобного кода при работе с текстовыми потоками.Создание локального веб-сервера с помощью встроенного
http.server
Когда слышишь «веб‑сервер», в голове всплывают сложные штуки вроде Nginx, Apache или хотя бы Flask. Но в Python уже есть крошечный сервер «из коробки» — модуль http.server. Он идеален для экспериментов, отладки и быстрых демо.
---
### Запускаем самый простой сервер
Перейдите в папку с файлами, которые хотите раздавать, и запустите:
python -m http.server 8000
Теперь в браузере откройте http://localhost:8000 — вы уже подняли сервер, который раздает текущую директорию.
Порт можно не указывать, тогда по умолчанию будет 8000.
---
### Минимальный сервер своим скриптом
Можно сделать то же самое из Python‑файла:
from http.server import HTTPServer, SimpleHTTPRequestHandler
def run_server(port: int = 8000):
server_address = ("", port) # "" означает слушать на всех интерфейсах
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
print(f"Serving on port {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run_server()
SimpleHTTPRequestHandler умеет раздавать файлы из текущей папки и показывает простую HTML‑страницу со списком файлов.
---
### Свой обработчик запросов
Хотите кастомный ответ вместо списка файлов? Наследуемся от BaseHTTPRequestHandler:
from http.server import HTTPServer, BaseHTTPRequestHandler
class HelloHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
message = "Hello, Python beginner!"
else:
message = f"Unknown path: {self.path}"
data = message.encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def run_server(port: int = 8080):
server_address = ("", port)
httpd = HTTPServer(server_address, HelloHandler)
print(f"Custom server on port {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run_server()
Теперь при открытии http://localhost:8080/ вы получите текстовый ответ, а при переходе по другому пути — сообщение с этим путем.
---
### Когда это полезно
- Быстро показать HTML/CSS/JS без настройки больших серверов.
- Локально протестировать AJAX‑запросы.
- Написать минимальный API‑мок для отладки клиента.
http.server не для продакшена: нет защиты, логики масштабирования, нормального логирования. Но для обучения он идеально показывает, как HTTP‑запрос превращается в ответ, и помогает почувствовать «скелет» любого веб‑фреймворка.Использование
difflib: как Python видит отличия лучше нас
Когда файл «чуть-чуть поменяли», глазами искать отличия — то еще удовольствие. Python-модуль difflib умеет делать это аккуратно, наглядно и… очень гибко. Разберёмся, как сравнивать строки и файлы, будто у нас встроен мини-Git.
---
### 1. Быстро сравнить две строки
Начнем с простого: найти различия между двумя строками посимвольно.
import difflib
s1 = "Python is awesome!"
s2 = "Python is awesom?"
diff = difflib.ndiff(s1, s2)
print("\n".join(diff))
Результат покажет строки с префиксами:
- - — удалено
- + — добавлено
- ? — подсказка, какие именно символы отличаются
Это удобно, когда нужно понять, почему строки «почти одинаковы», но сравнение == возвращает False.
---
### 2. Похожесть строк в процентах
Иногда важно не чем строки отличаются, а насколько они похожи. Для этого есть SequenceMatcher.
from difflib import SequenceMatcher
a = "color"
b = "colour"
ratio = SequenceMatcher(None, a, b).ratio()
print(ratio) # например, 0.91...
Значение от 0 до 1 — коэффициент похожести. Можно использовать для:
- поиска опечаток,
- подсказки «Вы имели в виду…»,
- фильтрации «почти одинаковых» записей.
---
### 3. HTML-отчет о различиях
Хотите красивый, наглядный diff в браузере? difflib.HtmlDiff как раз для этого:
import difflib
old_lines = [
"def add(a, b):\n",
" return a + b\n",
]
new_lines = [
"def add(a, b):\n",
" result = a + b\n",
" return result\n",
]
html = difflib.HtmlDiff().make_file(old_lines, new_lines,
fromdesc="Old version",
todesc="New version")
with open("diff.html", "w", encoding="utf-8") as f:
f.write(html)
Откройте diff.html в браузере — увидите таблицу с подсветкой добавленных/удалённых строк. Мини-система контроля версий прямо в одном файле.
---
### 4. Сравнение файлов построчно
Классический сценарий — сравнить два текстовых файла:
import difflib
with open("old.txt", encoding="utf-8") as f:
old = f.readlines()
with open("new.txt", encoding="utf-8") as f:
new = f.readlines()
for line in difflib.unified_diff(old, new,
fromfile="old.txt",
tofile="new.txt"):
print(line.rstrip())
Формат unified diff знаком всем, кто работал с Git: строки с +, - и контекстом вокруг изменений. Удобно для логов, конфигов, автопроверок.
---
difflib часто недооценивают, а зря. Это готовый швейцарский нож для работы с похожими текстами: от проверки орфографии до построения собственных дифф-отчетов. И всё это — в стандартной библиотеке, без единого pip install.Модуль
uuid: генерация уникальных идентификаторов без боли и коллизий
Когда нужно что-то «уникальное» — многие программисты тянутся к времени, счётчикам или даже случайным числам. Проблема в том, что всё это легко пересекается, особенно в распределённых системах. Здесь в игру вступает модуль uuid — стандартный способ генерировать уникальные идентификаторы в Python.
UUID (Universally Unique Identifier) — это 128-битное число, обычно записываемое как строка вроде:
550e8400-e29b-41d4-a716-446655440000
Вероятность коллизии настолько мала, что их смело используют в базах данных, API, токенах и временных файлах.
---
### Базовая генерация UUID
import uuid
user_id = uuid.uuid4()
print(user_id) # например: 3e7c0e78-9a8e-4b52-9780-4e1c23f57abc
print(type(user_id)) # <class 'uuid.UUID'>
uuid4() генерирует UUID на основе криптографически стойкого генератора случайных чисел. Это самый популярный вариант.
---
### Разные версии UUID
Модуль поддерживает несколько схем:
import uuid
# v1 — на основе MAC-адреса и времени
session_id = uuid.uuid1()
# v3 — хеш MD5 (детерминированный)
name_based_v3 = uuid.uuid3(uuid.NAMESPACE_DNS, "example.com")
# v5 — хеш SHA-1 (детерминированный, предпочтительнее v3)
name_based_v5 = uuid.uuid5(uuid.NAMESPACE_URL, "https://example.com")
print(session_id)
print(name_based_v3)
print(name_based_v5)
Где это полезно:
- uuid4() — когда нужна просто уникальность.
- uuid1() — когда порядок по времени важен (но может раскрывать MAC-адрес и время).
- uuid3() / uuid5() — когда один и тот же «вход» должен давать один и тот же UUID (идентификатор по имени, URL и т.п.).
---
### UUID как строки и обратно
Чаще всего UUID хранят как строки (например, в базе):
import uuid
order_id = uuid.uuid4()
order_str = str(order_id) # в строку
print(order_str)
restored = uuid.UUID(order_str) # обратно в объект UUID
print(restored == order_id) # True
Можно работать и с байтами:
token = uuid.uuid4()
token_bytes = token.bytes
restored_token = uuid.UUID(bytes=token_bytes)
---
### Практический мини-пример: генерация ключей для API
import uuid
def generate_api_key() -> str:
return uuid.uuid4().hex # без дефисов, 32 символа
keys = [generate_api_key() for _ in range(3)]
for k in keys:
print(k)
Метод .hex возвращает компактное шестнадцатеричное представление, удобное для токенов.
---
Модуль uuid снимает головную боль с уникальностью: вместо придумывания схем и «умных» счётчиков достаточно вызвать одну функцию — и получить идентификатор, с которым комфортно работать и локально, и в распределённых системах.### Изучаем
itertools: комбинации и декартовы произведения
Если вы хоть раз вручную писали вложенные циклы, чтобы перебрать все пары, тройки или варианты, — модуль itertools создан для вас. Он умеет генерировать комбинации и продукты «на лету», не занимая лишнюю память.
---
## Комбинации: выбираем без повторов
Комбинация — это выбор элементов без учета порядка. Из [1, 2, 3] пары такие: (1, 2), (1, 3), (2, 3).
from itertools import combinations
items = ['a', 'b', 'c', 'd']
for pair in combinations(items, 2):
print(pair)
combinations(iterable, r):
- не повторяет элементы;
- (a, b) и (b, a) считаются одним вариантом;
- всё генерируется лениво (по мере запроса).
Полезно, когда перебираете возможные команды, наборы опций или подсеты признаков в задаче машинного обучения.
---
## Комбинации с повторениями
Иногда элемент можно брать несколько раз: монеты, кубики, вкусы мороженого.
from itertools import combinations_with_replacement
flavors = ['vanilla', 'chocolate', 'strawberry']
for combo in combinations_with_replacement(flavors, 2):
print(combo)
('vanilla', 'vanilla') здесь вполне допустим.
---
## Декартово произведение: все возможные варианты
product — это как вложенные циклы, только в одну строку. Идеален для генерации всех комбинаций параметров.
from itertools import product
sizes = ['S', 'M', 'L']
colors = ['black', 'white']
for item in product(sizes, colors):
print(item)
Результат:
- ('S', 'black')
- ('S', 'white')
- ('M', 'black')
- и т.д.
Можно задать повторения:
for roll in product(range(1, 7), repeat=2):
print(roll)
Это уже все возможные броски двух кубиков.
---
## Комбинации против продукта: что когда использовать?
- Нужен выбор из одного набора без повторов → combinations
- Нужен выбор из одного набора с возможными повторами → combinations_with_replacement
- Нужны все сочетания из нескольких наборов → product
- Хотите сымитировать несколько одинаковых наборов (кубики, пароли фиксированной длины) → product(..., repeat=n)
---
itertools экономит память и время разработки: вы получаете мощный генератор вариантов без километров вложенных циклов. Как только ловите себя на мысли «сейчас напишу ещё один for внутри for», сначала загляните в этот модуль.### Создание простого HTTP‑клиента с помощью
http.client
Большинство начинающих знакомятся с веб‑запросами через библиотеку requests. Удобно, но иногда полезно заглянуть на «низкий уровень» и понять, как все работает внутри. Для этого в стандартной библиотеке Python есть модуль http.client.
---
## Первый запрос: GET к публичному API
Сделаем простой GET‑запрос к публичному API и разберём шаги.
import http.client
import json
conn = http.client.HTTPSConnection("jsonplaceholder.typicode.com")
conn.request("GET", "/posts/1")
response = conn.getresponse()
print("Status:", response.status, response.reason)
data = response.read()
text = data.decode("utf-8")
post = json.loads(text)
print("Title:", post["title"])
conn.close()
Что тут важно:
- HTTPSConnection — шифрованное соединение (для http:// был бы HTTPConnection);
- request(method, url, body=None, headers={}) — отправка запроса;
- getresponse() — получение объекта ответа;
- read() — сырые байты, которые обычно нужно декодировать (decode) и парсить (JSON, HTML и т.д.).
---
## Отправка данных: простой POST
Теперь отправим JSON‑данные методом POST. Здесь уже нужны заголовки и тело запроса:
import http.client
import json
conn = http.client.HTTPSConnection("jsonplaceholder.typicode.com")
payload = {"title": "foo", "body": "bar", "userId": 1}
body = json.dumps(payload)
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
conn.request("POST", "/posts", body=body, headers=headers)
response = conn.getresponse()
print("Status:", response.status)
print("Headers:", dict(response.getheaders()))
data = response.read().decode("utf-8")
created_post = json.loads(data)
print("Created ID:", created_post["id"])
conn.close()
Ключевые моменты:
- Тело запроса мы сериализуем через json.dumps;
- Обязательно указываем Content-Type;
- getheaders() возвращает список пар заголовков.
---
## Обработка ошибок и таймаут
Реальный клиент должен быть готов к сетевым ошибкам:
import http.client
import socket
try:
conn = http.client.HTTPSConnection("example.com", timeout=3)
conn.request("GET", "/")
response = conn.getresponse()
print("Status:", response.status)
conn.close()
except (http.client.HTTPException, socket.timeout) as exc:
print("Request failed:", exc)
Тут:
- timeout защищает от «вечного ожидания»;
- исключения HTTPException и socket.timeout помогают корректно отреагировать на проблемы сети.
---
Зная http.client, легче понимать, что делает любая высокоуровневая библиотека: устанавливает соединение, формирует запрос, отправляет тело, читает статус, заголовки и содержимое. Это отличный шаг к более глубокому пониманию сетевого кода в Python.Как использовать модуль
statistics: среднее, медиана и мода без боли в мозге
Когда вы считаете средние оценки, анализируете продажи или смотрите на результаты эксперимента — без базовой статистики далеко не уедешь. В Python для этого есть готовый инструмент — модуль statistics. Он избавляет от самописных циклов и формул и помогает считать всё в одну строку.
---
## Подготовка
Модуль встроен в стандартную библиотеку, поэтому никаких установок не нужно:
import statistics as stats
Для примеров возьмём список чисел:
data = [10, 12, 13, 15, 15, 17, 18]
---
## Среднее (mean)
Среднее арифметическое — сумма всех значений, делённая на их количество. В statistics это делается так:
import statistics as stats
data = [10, 12, 13, 15, 15, 17, 18]
avg = stats.mean(data)
print(avg) # 14.285714285714286
Круто то, что модуль сам обрабатывает int и float, не нужно думать о типах.
---
## Медиана (median)
Медиана — это «середина» отсортированных данных. Она устойчивее к выбросам. Если в выборке внезапно появится «сумасшедшее» значение, среднее уедет, а медиана — почти нет.
import statistics as stats
data = [10, 12, 13, 1000] # явный выброс
mean_value = stats.mean(data)
median_value = stats.median(data)
print(mean_value) # 258.75
print(median_value) # 12.5
Медиана сразу показывает, что «нормальные» значения всё ещё где-то около 12–13, а не 258.75.
---
## Мода (mode)
Мода — самое часто встречающееся значение. Полезно, когда нужно найти «типичный» элемент: самый популярный рейтинг, любимый номер, частый результат.
import statistics as stats
grades = [5, 4, 5, 3, 4, 5, 5, 4]
most_common = stats.mode(grades)
print(most_common) # 5
Важно: если в данных несколько значений с одинаковой максимальной частотой, mode() в некоторых версиях Python может выбросить StatisticsError. Для таких случаев есть более гибкая функция multimode():
import statistics as stats
values = [1, 2, 2, 3, 3]
modes = stats.multimode(values)
print(modes) # [2, 3]
---
## Быстрый мини-анализ набора данных
Соберём всё вместе:
import statistics as stats
data = [12, 15, 17, 15, 19, 21, 15, 18]
summary = {
"mean": stats.mean(data),
"median": stats.median(data),
"mode": stats.mode(data),
"min": min(data),
"max": max(data)
}
print(summary)
# Пример вывода:
# {'mean': 16.5, 'median': 16.0, 'mode': 15, 'min': 12, 'max': 21}
Всего несколько строк — и вы уже видите «портрет» своих данных: где центр, где разброс и какое значение встречается чаще всего.
Модуль statistics — отличный первый шаг в сторону анализа данных, без pandas и сложной математики. Идеален для учебных задач, прототипов и маленьких утилит.Как использовать модуль statistics для вычислений: среднее, медиана и мода
Модуль
pathlib: современная работа с файловой системой
Если вы всё ещё склеиваете пути к файлам через os.path и плюсики — самое время познакомиться с pathlib. Это «современный» способ работы с путями в Python: объектно-ориентированный, удобный и кроссплатформенный.
---
### Зачем нужен pathlib
pathlib решает несколько болезненных мест:
- Не нужно думать о слэше: / под Linux и \ под Windows.
- Пути становятся объектами, а не строками.
- Методы для чтения/записи файлов встроены прямо в объект пути.
Подключение модуля:
from pathlib import Path
---
### Создание и комбинирование путей
from pathlib import Path
base_dir = Path.home() # Домашняя папка пользователя
project_dir = base_dir / "my_project" / "data" # Склеивание через /
print(project_dir)
print(project_dir.exists()) # Проверяем, существует ли путь
print(project_dir.is_dir()) # Это папка?
Оператор / перегружен и безопасно соединяет части пути с учётом ОС.
---
### Поиск файлов по маске
Найдем все .txt в директории:
data_dir = Path("data")
for txt_file in data_dir.glob("*.txt"):
print(txt_file.name, txt_file.stat().st_size, "bytes")
glob() поддерживает маски (*, **) и позволяет быстро обходить дерево каталогов.
---
### Чтение и запись файлов
Необязательно открывать файл через open:
file_path = Path("data") / "notes.txt"
# Запись текста
file_path.write_text("Hello, pathlib!\nSecond line.")
# Чтение текста
content = file_path.read_text()
print(content)
Есть и бинарные варианты: write_bytes() и read_bytes().
---
### Создание директорий и безопасная работа
logs_dir = Path("logs")
logs_dir.mkdir(exist_ok=True, parents=True) # Создаст все недостающие папки
log_file = logs_dir / "app.log"
if not log_file.exists():
log_file.write_text("Log start\n")
else:
with log_file.open("a", encoding="utf-8") as f:
f.write("New log line\n")
parents=True создаст всю цепочку директорий, а exist_ok=True не вызовет ошибку, если папка уже есть.
---
### Абсолютные пути и нормализация
p = Path("data") / ".." / "data" / "file.txt"
print(p) # Относительный "кривой" путь
print(p.resolve()) # Нормализованный абсолютный путь
resolve() приводит путь к каноничному виду и показывает, куда он реально ведет.
---
pathlib хорошо ложится на мышление «объект пути + действия с ним», избавляя от ручной возни со строками. Если вы начинаете с Python сегодня — имеет смысл сразу привыкать именно к такому стилю работы с файловой системой.
Available now! Telegram Research 2025 — the year's key insights 
