Zen of Python
20.1K subscribers
1.29K photos
179 videos
36 files
3.3K links
Полный Дзен Пайтона в одном канале

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Сайт: https://tprg.ru/site

Регистрация в перечне РКН: https://tprg.ru/xZOL
Download Telegram
Что значит стрелка ->

Если вы столкнулись со знаком -> в коде на Python и задумались, а не код из R ли это нужно ли это, как это работает и откуда оно взялось, этот лонгрид ответит на такие вопросы.

Стрелка в определении функции служит для аннотации типа возвращаемого значения. Она ставится после списка параметров и перед двоеточием:


def count_titles(titles: list[str]) -> int:
return len(titles)


Здесь -> int говорит: «ожидается, что функция вернёт int». Аннотации опциональны и сами по себе ничего в рантайме не проверяют — это метаданные.


Краткая история оператора

Идея аннотаций функций впервые появилась в PEP 3107 (Function Annotations), который ввёл возможность добавлять произвольные метаданные к параметрам и к возвращаемому значению. На базе этой синтаксики PEP 484 затем ввёл систему типовых подсказок (type hints) для Python, сделав их стандартной практикой для тех, кто хочет статической проверки типов. Все эти дополнения остались опциональными — сам интерпретатор не будет выбрасывать ошибки, если аннотации не совпадают с фактическим типом во время выполнения.

Простой пример:


import random

def get_game_recommendation(titles: list[str]) -> str:
return random.choice(titles)

games = ["Minecraft", "Cyberpunk 2077", "The Witcher 3", "Elden Ring"]
recommendation = get_game_recommendation(games)
print(f"Рекомендуемая игра: {recommendation}") # Например, Minecraft
print(f"Тип возвращаемого значения: {type(recommendation).__name__}") # str



Что происходит, если функция возвращает другой тип?

Python сам по себе не будет проверять, совпадает ли реальный возвращаемый тип с аннотацией — код просто выполнится. Но когда вы используете статический анализатор типов (например, mypy) или линтер, несоответствие будет обнаружено и выведет предупреждение / ошибку. Это ключевая идея: аннотации дают выгоду при статической проверке, CI и чтении кода, но не изменяют семантику программы во время выполнения.


Какие типы можно указывать после ->?

Почти любые:
🔘 простые встроенные (int, str, bool),
🔘 коллекции (list[str], dict[str, int]),
🔘 собственные классы и проч.


Где ещё применяются стрелки и аннотации?

Хотя наиболее часто -> встречается в объявлениях функций, аннотации — это часть более широкой системы типизации в Python. Типы используются для:

🔘 аннотаций параметров функций;
🔘 переменных и атрибутов классов (PEP 526);
🔘 аннотаций методов, Callable, Protocol и т.д.;
🔘 документации и автодополнения в IDE.

Инструменты типа mypy, pyright, IDE (PyCharm, VS Code) и линтеры используют аннотации, чтобы находить ошибки и улучшать подсказки.


Ограничения и подводные камни

🔘 Аннотации не выполняются: они не гарантия корректности в рантайме;
🔘 Сложные типы для сложной структуры данных или динамических конструкций типы могут стать громоздкими; иногда приходится балансировать читабельность и строгость;

#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍3
Что такое JSON и зачем он нужен

JSON (JavaScript Object Notation) — текстовый формат для обмена данными, удобный для людей и машин. Он возник в экосистеме JavaScript, но стал язык-независимым стандартом для API, конфигураций и документных хранилищ. Формат простой: объекты (пар «ключ: значение») и массивы — это основные строительные блоки.

Почему Python-разработчику это важно:

🔘 JSON часто используется в web-API, логах, конфигурациях и при обмене данными между сервисами.
🔘 Python имеет встроенный модуль json, который делает сериализует (Python → JSON) и десериализует (JSON → Python).


Синтаксис JSON

🔘 Строки — в двойных кавычках ("...");
🔘 Логические значения — в нижнем регистре: true / false (в Python — True / False);
🔘 Отсутствующее значение — null (в Python → None);
🔘 Нельзя оставлять комментарии и нельзя ставить завершающие запятые после последнего элемента.

Валидный JSON:


{
"name": "Frieda",
"isDog": true,
"hobbies": ["eating", "sleeping"],
"age": 8,
"address": {"home": ["Berlin", "Germany"], "work": null}
}



Модуль json

Главные функции одноименного модуля стандартной библиотеки:

🔘 json.dumps(obj, **opts) — сериализует Python-объект в строку JSON;
🔘 json.dump(obj, fp, **opts) — сериализует и записывает в файл-объект;
🔘 json.loads(s) — парсит JSON-строку в Python-объект;
🔘 json.load(fp) — считывает JSON из файла и парсит.

Эти функции следуют стандартному отображению типов: словари → объекты, списки → массивы, строки → строки, числа → числа, True / Falsetrue / false, Nonenull.


import json

data = {"name": "Frieda", "age": 8, "is_dog": True}
s = json.dumps(data) # --> '{"name": "Frieda", "age": 8, "is_dog": true}'
obj = json.loads(s) # --> {'name': 'Frieda', 'age': 8, 'is_dog': True}



Полезные параметры dumps / dump

При сериализации полезны параметры:

🔘 indent — делает вывод читабельным (pretty print) (например, indent=2);
🔘 sort_keys=True — сортирует ключи объекта по алфавиту;
🔘 separators — позволяет контролировать символы между элементами (полезно для минификации);
🔘 ensure_ascii=False — по умолчанию json экранирует не-ASCII символы; если хотите сохранить UTF-8 в читаемом виде, ставьте False;
🔘 skipkeys=True — пропускает неподдерживаемые типы ключей вместо TypeError (использовать осторожно — вы можете потерять данные).


Маппинг типов

При загрузке JSON в Python выполняется обратное отображение типов:

* objectdict
* arraylist
* stringstr
* numberint / float
* true`/`falseTrue`/`False
* nullNone

JSON-ключи — всегда строки. Если у вас в исходном Python-словаре были числовые ключи (например {1: "a"}`), при сериализации они станут строками (`"1"`). После `json.loads() вы получите ключи как строки — Python не "угадает" исходный тип. Это частая ловушка при конвертации словарей ключами-числами.


Полезные утилиты и приёмы

🔘 python -m json.tool — встроенная утилита для форматирования/проверки JSON в терминале (можно использовать для prettify/minify);
🔘 Онлайн-валидаторы (например, JSONLint) и редакторы с подсветкой помогут быстро найти синтаксические ошибки (комментарии, лишние запятые и т. п.).


Подборка примеров

Читаем JSON из файла:

import json

with open("data.json", "r", encoding="utf-8") as f:
data = json.load(f)


Записываем с красивым форматированием:

with open("data_pretty.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, sort_keys=True, ensure_ascii=False)


Сериализуем datetime:

from datetime import datetime
import json

def default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError

json.dumps({"now": datetime.utcnow()}, default=default)


Проверяем валидность JSON в CLI

python -m json.tool input.json > /dev/null
# код возврата 0 — валидный JSON


#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
10
Фабрики в Python

В программировании довольно часто встречается задача: в зависимости от входных данных нужно выбрать подходящий класс для обработки. Например, у нас есть XML-файлы и JSON-файлы, и мы хотим написать систему, которая будет их импортировать.

На первый взгляд решение простое — внутри метода проверять расширение файла и создавать соответствующий объект. Но у такого подхода есть несколько серьёзных недостатков. Именно здесь на помощь приходит паттерн «Фабрика».

Рассмотрим упрощённый пример кода:


class XMLImporter:
def __init__(self, filename):
self.filename = filename

def execute(self):
print("XML imported from", self.filename)


class JSONImporter:
def __init__(self, filename):
self.filename = filename

def execute(self):
print("JSON imported from", self.filename)


class Document:
def import_file(self, filename):
if filename.endswith(".xml"):
importer = XMLImporter(filename)
elif filename.endswith(".json"):
importer = JSONImporter(filename)
data = importer.execute()


document = Document()
document.import_file("text.xml")
document.import_file("text.json")


На первый взгляд код рабочий, но здесь нарушаются два принципа SOLID:

1️⃣ Принцип единственной ответственности (Single Responsibility Principle): класс Document отвечает сразу за создание объектов XMLImporter, JSONImporter и их использование.

2️⃣ Принцип открытости / закрытости (Open / Closed Principle).
Если появится новый формат (например, CSV), придётся снова лезть в метод import_file и расширять блок if / elif.


Решение: фабрика

Чтобы убрать ответственность за создание объектов из класса Document, вводится так называемася фабрика. Это объект, который берёт на себя ответственность за создание других объектов. Она не избавляет нас полностью от изменения кода при добавлении новых форматов, но значительно улучшает читаемость, гибкость и расширяемость системы.


class ImporterFactory:
def get_importer(self, filename):
if filename.endswith(".xml"):
return XMLImporter(filename)
elif filename.endswith(".json"):
return JSONImporter(filename)


Теперь Document больше не знает, как именно создаются нужные классы. Ему просто передают готовую фабрику:


class Document:
def __init__(self, factory):
self.factory = factory

def import_file(self, filename):
importer = self.factory.get_importer(filename)
data = importer.execute()


factory = ImporterFactory()
document = Document(factory)

document.import_file("text.xml")
document.import_file("text.json")


Преимущества фабрики

1️⃣ Изоляция ответственности: логика создания объектов теперь сосредоточена только в одном месте — в ImporterFactory.
2️⃣ Гибкость: если нужно добавить поддержку нового формата (CSV, YAML и т.д.), достаточно изменить фабрику, не трогая Document.
3️⃣ Удобное тестирование: в тестах можно подменить фабрику на «фиктивную» (mock) и проверять работу Document независимо от импортеров.


Что дальше?

Фабрика — это базовый паттерн. На его основе строятся более сложные подходы, например:

🔘 Factory Method — когда создание объектов делегируется подклассам.
🔘 Abstract Factory — когда фабрика создаёт целые семейства объектов, согласованных между собой.

#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
👎4🔥21
Что такое замыкание и зачем оно нужно

Замыкания (Closures) — понятие, которое кажется сложным при первом знакомстве. Но на самом деле вы уже его скорее всего используете неосознанно, настолько это стало базой.

Представим программу, где пользователь вводит число, нажимает OK, и программа сохраняет это число в список, выводя все введённые значения:


numbers = []

def enter_number(x):
numbers.append(x)
print(numbers)

enter_number(3) # [3]
enter_number(7) # [3, 7]
enter_number(4) # v


Код работает, но есть проблема: переменная numbers находится вне функции, то есть она глобальная. Это значит, что:

🔘 функция зависит от переменной, объявленной в другом месте;
🔘 код становится менее гибким — нельзя просто перенести функцию в другой модуль, не взяв с собой numbers.

Замыкание помогает «связать» данные и логику в одном месте без использования классов:


def enter_number_outer():
numbers = [] # локальная переменная

def enter_number_inner(x):
numbers.append(x)
print(numbers)

return enter_number_inner


Когда мы вызываем внешнюю функцию enter_number_outer(), она создаёт свой контекст с переменной numbers и возвращает внутреннюю функцию, которая имеет к ней доступ.


enter_num = enter_number_outer()

enter_num(3) # [3]
enter_num(7) # [3, 7]
enter_num(4) # [3, 7, 4]


Ключевая идея замыкания:

Внутренняя функция «замыкает» (сохраняет) значения переменных из области видимости внешней функции.


Даже когда enter_number_outer() завершает выполнение, её переменные не уничтожаются, потому что они нужны внутренней функции, которая всё ещё существует. Это и есть closure — функция, которая запоминает контекст, в котором была создана.


Используйте замыкания, если хотите:
🔘 инкапсулировать состояние в функции без создания класса;
🔘 нужно создать функцию-конфигуратор (например, с частично зафиксированными параметрами);

Замыкание — это функция, которая:
🔘 определена внутри другой функции;
🔘 использует переменные из внешней функции;
🔘 «запоминает» эти переменные даже после завершения внешней функции.

#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122🤣1
Как писать docstrings

Докстринги (буквально «строки документации») — это встроенная в код документация (обычно после инициализации функции / класса и прочих объектов между двумя '''), которую могут читать люди и инструменты (help(), pydoc, автогенераторы). В этом лонгриде мы разберемся, где и как их писать.


Зачем нужны docstrings — и чем они отличаются от комментариев

🔘Комментарии (#) объясняют реализацию и помогают разработчикам; интерпретатор их игнорирует.
🔘Докстринги — это строковые литералы (обычно в """`), помещённые сразу после определения модуля / функции / класса / метода; они сохраняются в атрибуте .__doc__` и доступны в рантайме (через .__doc__, help() и инструментах вроде pydoc.

Докстринги описывают интерфейс (что делает код, какие аргументы и что возвращает), а комментарий — реализацию и все остальное.

Многострочные докстринги используются когда нужно подробнее описать параметры, поведение, побочные эффекты, примеры использования. По PEP 257 закрывающие кавычки обычно ставят на отдельной строке в многострочном docstring:


def get_book(publication_year, title):
"""
Retrieve a Harry Potter book by its publication year and name.

Parameters:
publication_year (int): The year the book was published.
title (str): The title of the book.

Returns:
str: A sentence describing the book and its publication year.
"""



Чтобы получить доступ к docstring в коде и терминале, вызываем:

🔘 obj.__doc__ — возвращает сырой docstring (часто краткий);
🔘 help(obj) — даёт структурированный вывод, полезный для модулей и классов;
🔘 python -m pydoc module — позволяет просматривать документацию из терминала и генерировать статичные страницы.


Что писать в docstring для модулей, функций и классов

Модуль:
🔘 Краткое описание назначения модуля.
🔘 При необходимости — описание экспортируемых переменных/классов/функций, примеры использования.

Функция / метод:
🔘 Краткое резюме (1–2 предложения).
🔘 Секция Parameters`/`Args: имена параметров, типы, краткое описание.
🔘 Секция Returns / Yields: что возвращается, тип.
🔘 Исключения: какие ошибки может выбросить функция (опционально, но полезно).
🔘 Пример использования или заметки о поведении (если нужно).

Класс:
🔘 Краткое описание назначения класса.
🔘 Описание атрибутов (публичных), краткая информация о методах (если интерфейс не очевиден).
🔘 Для сложных иерархий — примеры создания/использования. ([realpython.com][1])

#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍1