Библиотека Python разработчика | Книги по питону
Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍 По всем вопросам @evgenycarter РКН clck.ru/3Ko7Hq
Больше📈 Аналитический обзор Telegram-канала Библиотека Python разработчика | Книги по питону
Канал Библиотека Python разработчика | Книги по питону (@bookpython) языкового сегмента Русский является активным участником. Сейчас сообщество объединяет 17 977 подписчиков, занимая 7 272 место в категории Технологии и приложения и 36 658 место в регионе Россия.
📊 Показатели аудитории и динамика
С момента создания невідомо проект демонстрирует стремительный рост, собрав аудиторию из 17 977 подписчиков.
Согласно последним данным от 27 октября, 2026, канал показывает стабильную активность. За последние 30 дней изменение числа участников составило -96, а за последние 24 часа — -2, при этом общий охват остаётся высоким.
- Статус верификации: Не верифицирован
- Уровень вовлечённости (ER): Средний показатель вовлечённости аудитории составляет 5.41%. В первые 24 часа после публикации контент обычно набирает 2.62% реакций от общего числа подписчиков.
- Охват публикаций: В среднем каждый пост получает 972 просмотров. В течение первых суток публикация набирает 471 просмотров.
- Реакции и взаимодействия: Аудитория активно поддерживает контент: среднее количество реакций на один пост — 4.
- Тематические интересы: Контент сосредоточен на ключевых темах, таких как numbers, yield, модуль, none, декоратор.
📝 Описание и контентная политика
Автор описывает ресурс как площадку для выражения субъективного мнения:
“Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍
По всем вопросам @evgenycarter
РКН clck.ru/3Ko7Hq”
Благодаря высокой частоте обновлений (последние данные получены 28 октября, 2026) канал поддерживает актуальность и высокий уровень охвата публикаций. Аналитика показывает, что аудитория активно взаимодействует с контентом, что делает его важной точкой влияния в категории Технологии и приложения.
functools в Python предоставляет инструменты для работы с функциями, позволяя выполнять различные манипуляции с ними. Вот некоторые из наиболее важных функций и возможностей, которые предоставляет functools:
1. functools.partial: Позволяет зафиксировать некоторые аргументы функции и создать новую функцию с предопределенными значениями.
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, 2)
print(double(5)) # Вывод: 10
2. functools.reduce: Применяет функцию к паре элементов в последовательности, сокращая ее до одного значения. Обычно используется для аккумуляции значений.
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
print(product) # Вывод: 24
3. functools.lru_cache: Кэширует результаты вызовов функции, чтобы ускорить повторные вызовы с теми же аргументами. Полезно для функций с дорогими вычислениями.
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # Вывод: 55
4. functools.wraps: Декоратор, который сохраняет метаданные оригинальной функции (такие как имя и документация) при создании декоратора.
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Что-то делаем перед вызовом функции")
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""Выводит приветствие."""
print("Привет!")
print(say_hello.__name__) # Вывод: say_hello
print(say_hello.__doc__) # Вывод: Выводит приветствие.
5. functools.total_ordering: Упрощает реализацию всех методов сравнения для класса, определяя только несколько из них.
from functools import total_ordering
@total_ordering
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __lt__(self, other):
return (self.x, self.y) < (other.x, other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 < p2) # Вывод: True
print(p1 <= p2) # Вывод: True
Эти функции и декораторы делают functools мощным инструментом для функционального программирования в Python.
📲 Мы в MAX
👉@BookPythong.close(), но обычно это делает сборщик мусора. Когда вызывается close, в точке, где выполнение генератора было приостановлено, выбрасывается исключение GeneratorExit:
def gen():
try:
yield 1
yield 2
finally:
print('END')
g = gen()
print(next(g)) # выведет '1'
g.close() # выведет 'END'
Обрати внимание на три момента:
1. Нельзя использовать yield при обработке GeneratorExit
Если в блоке finally попытаться сделать yield, возникнет ошибка RuntimeError:
def gen():
try:
yield 1
finally:
yield 3 # ошибка!
g = gen()
next(g)
g.close() # RuntimeError
2. Исключение не выбрасывается, если генератор ещё не запускался
В этом случае генератор просто переходит в состояние остановлен, но finally не выполняется:
def gen():
try:
yield 1
finally:
print('END')
g = gen()
g.close() # ничего не выводит
print(list(g)) # выведет '[]'
3. close() ничего не делает, если генератор уже завершён
Если генератор полностью отработал, close() не вызывает finally повторно и просто игнорируется:
def gen():
try:
yield 1
yield 2
finally:
print('END')
g = gen()
print(list(g)) # ['1', '2']
print('Closing now')
g.close()
# Вывод:
# END
# [1, 2]
# Closing now
📲 Мы в MAX
👉@BookPythonjson имеет интерфейс командной строки, который может быть полезен для форматирования JSON с помощью одного только Python. Этот модуль называется json.tool и используется следующим образом:
$ echo '{"a": [], "b": "c"}' | python -m json.tool
{
"a": [],
"b": "c"
}
📲 Мы в MAX
👉@BookPython__new__. Даже если вы определяете свой собственный __new__ для класса, вы должны вызвать super().__new__(...).
Можно подумать, что object.__new__ — это базовая реализация, отвечающая за создание всех объектов. Но это не совсем так. Существует несколько таких реализаций, и они несовместимы.
Например, у dict есть собственная низкоуровневая реализация __new__, и объекты типов, унаследованных от dict, нельзя создать с помощью object.__new__:
In : class D(dict):
...: pass
...:
In : class A:
...: pass
...:
In : object.__new__(A)
Out: <__main__.A at 0x7f200c8902e8>
In : object.__new__(D)
...
TypeError: object.__new__(D) is not safe,
use D.__new__()
📲 Мы в MAX
👉@BookPython
from collections import deque
from math import sqrt
class Compose:
def __init__(self):
self._functions = deque()
def __call__(self, *args, **kwargs):
result = None
for f in self._functions:
result = f(*args, **kwargs)
args = [result]
kwargs = dict()
return result
def __rshift__(self, f):
self._functions.append(f)
return self
def __lshift__(self, f):
self._functions.appendleft(f)
return self
compose = Compose
sqrt_abs = (compose() << sqrt << abs)
sqrt_abs2 = (compose() >> abs >> sqrt)
print(sqrt_abs(-4)) # 2.0
print(sqrt_abs2(-4)) # 2.0
Объяснение:
<< — добавляет функцию в начало цепочки (выполняется первой).
>> — добавляет функцию в конец цепочки (выполняется последней).
В примере sqrt_abs(-4) сначала берёт abs(-4) → 4, а затем sqrt(4) → 2.0.
sqrt_abs2(-4) делает то же самое, но функции добавлены в другом порядке.
📲 Мы в MAX
👉@BookPython__class__:
>>> [1, 2].__class__
<class 'list'>
Однако более привычный способ получить класс — использовать функцию type.
Кроме того, это единственный способ, который работает со старыми стилями классов.
>>> type([1, 2])
<class 'list'>
Если вы хотите проверить, является ли объект экземпляром заданного класса, следует использовать isinstance, а не сравнение:
>>> class A:
... pass
...
>>> class B(A):
... pass
...
>>> type(B())
<class '__main__.B'>
>>> isinstance(B(), A)
True
📲 Мы в MAX
👉@BookPythonmath.nan.
NaN не равен ничему, включая самого себя:
>>> math.nan == math.nan
False
Кроме того, объект NaN не является уникальным — можно получить несколько разных объектов NaN из разных источников:
>>> float('nan')
nan
>>> float('nan') is float('nan')
False
Это означает, что обычно нельзя использовать NaN в качестве ключа словаря:
>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}
📲 Мы в MAX
👉@BookPythonNone равен None, поэтому может показаться, что проверку на None можно делать через ==:
ES_TAILS = ('s', 'x', 'z', 'ch', 'sh')
def make_plural(word, exceptions=None):
if exceptions == None: # ← ← ←
exceptions = {}
if word in exceptions:
return exceptions[word]
elif any(word.endswith(t) for t in ES_TAILS):
return word + 'es'
elif word.endswith('y'):
return word[0:-1] + 'ies'
else:
return word + 's'
exceptions = dict(
mouse='mice',
)
print(make_plural('python'))
print(make_plural('bash'))
print(make_plural('ruby'))
print(make_plural('mouse', exceptions=exceptions))
Однако так делать неправильно. Действительно, None равен None, но не только он может быть равен None. Пользовательские объекты тоже могут вернуть True при сравнении с None через ==:
class A:
def __eq__(self, other):
return True
print(A() == None) # True
print(A() is None) # False
Правильный способ проверки на None — использовать is None.
📲 Мы в MAX
👉@BookPython+:
>>> [1, 2] + [2, 3]
[1, 2, 2, 3]
Кортежи и строки также используют +:
>>> (1, 2) + (2, 3)
(1, 2, 2, 3)
>>> "12" + "23"
'1223'
Deque (двусторонняя очередь) тоже поддерживает +:
>>> deque([1, 2]) + deque([2, 3])
deque([1, 2, 2, 3])
Множества объединяются с помощью оператора |:
>>> {1, 2} | {2, 3}
{1, 2, 3}
Словари объединяются по-другому, и порядок важен, если ключи пересекаются:
>>> {**dict(a=1, b=2), **dict(b=3, c=4)}
{'a': 1, 'b': 3, 'c': 4}
>>> {**dict(b=3, c=4), **dict(a=1, b=2)}
{'b': 2, 'c': 4, 'a': 1}
Counter (счётчик) можно сложить с помощью +, при этом значения суммируются:
>>> Counter(dict(a=1, b=2)) + Counter(dict(b=3, c=4))
Counter({'b': 5, 'c': 4, 'a': 1})
📲 Мы в MAX
👉@BookPythondict, хотя выглядят они абсолютно одинаково:
>>> from sys import getsizeof
>>> class A:
... pass
...
>>> a = dict()
>>> b = A().__dict__
>>> type(a)
<class 'dict'>
>>> type(b)
<class 'dict'>
>>> a
{}
>>> b
{}
>>> getsizeof(a)
240
>>> getsizeof(b)
112
Чтобы уменьшить потребление памяти, словари для __dict__ реализованы иначе. Они разделяют ключи между всеми экземплярами класса A. Однако важно понимать, что b на самом деле не меньше, чем a, - это просто особенность работы getsizeof.
📲 Мы в MAX
👉@BookPythontime.monotonic() никогда не идёт назад, даже если системные часы были изменены:
from contextlib import contextmanager
import time
@contextmanager
def timeit():
start = time.monotonic()
yield
print(time.monotonic() - start)
def main():
with timeit():
time.sleep(2)
main()
📲 Мы в MAX
👉@BookPython@property:
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
Ниже приведён пример того, как можно определить функцию, которая использует дополнительные функции для особых случаев:
from functools import wraps
def make_case_decorator(func):
def case_decorator(*case_decorator_args):
def decorator(special_case_func):
@wraps(func)
def decorated(*args):
if case_decorator_args == args:
return special_case_func(*args)
return func(*args)
decorated.case = make_case_decorator(decorated)
return decorated
return decorator
return case_decorator
def special_cases(func):
@wraps(func)
def decorated(*args):
return func(*args)
decorated.case = make_case_decorator(decorated)
return decorated
@special_cases
def fact(x):
return x * fact(x - 1)
@fact.case(0)
def fact(x):
return 1
@fact.case(10)
def fact(x):
print(f'(сработала оптимизация для {x})')
return 3628800
📲 Мы в MAX
👉@BookPython2:
def make_closure(x):
def closure():
print(x)
return closure
make_closure(2)()
Вызывает UnboundLocalError: local variable 'x' referenced before assignment:
def make_closure(x):
def closure():
print(x)
x *= 2
print(x)
return closure
make_closure(2)()
Чтобы это заработало, нужно использовать nonlocal.
Оно явно сообщает интерпретатору, что присваивание не создает новую локальную переменную, а работает с переменной из замыкания:
def make_closure(x):
def closure():
nonlocal x
print(x)
x *= 2
print(x)
return closure
make_closure(2)()
📲 Мы в MAX
👉@BookPythontry ... except Exception, которая предназначена для отлова «любых ошибок». Чтобы безопасно это обработать внутри корутины, приходится писать примерно так:
try:
await action()
except asyncio.CancelledError:
raise
except Exception:
logging.exception('action failed')
📲 Мы в MAX
👉@BookPythonwith внутри блока if, не заключив туда весь блок with. Это часто приводит к дублированию кода:
def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj
if path:
with open(path) as f:
print(f.read(), end='')
else:
print(file_obj.read(), end='')
Способ борьбы с этой проблемой — использовать ExitStack и вызывать enter_context внутри if:
def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj
with ExitStack() as stack:
if path:
file_obj = stack.enter_context(
open(path)
)
print(file_obj.read(), end='')
Однако более очевидный способ достичь того же — использовать тривиальные менеджеры контекста, которые ничего не делают, когда они не нужны, вместо «настоящих». Начиная с Python 3.7, их можно получить с помощью contextlib.nullcontext:
def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj
if path:
context = open(path)
else:
context = nullcontext(file_obj)
with context as f:
print(f.read(), end='')
📲 Мы в MAX
👉@BookPythonrange(2, 10) математически означает [2, 10),
или, говоря на языке Python: [2, 3, 4, 5, 6, 7, 8, 9].
Несмотря на асимметрию, это не ошибка и не случайность.
В этом есть логика: такой подход позволяет "склеивать" два соседних интервала без риска ошибиться на единицу:
[a, c) = [a, b) + [b, c)
Для сравнения, если бы использовались закрытые интервалы, получалось бы так:
[a, c] = [a, b] + [b+1, c]
Эта же идея объясняет, почему индексация начинается с нуля:
[0, N) содержит ровно N элементов.
Эдсгер Дейкстра написал на эту тему отличную статью ещё в 1982 году.
📲 Мы в MAX
👉@BookPythonfor и условия if:
In : [(x, y) for x in range(3) for y in range(3)]
Out: [
(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)
]
In : [
(x, y)
for x in range(3)
for y in range(3)
if x != 0
if y != 0
]
Out: [(1, 1), (1, 2), (2, 1), (2, 2)]
Кроме того, любое выражение внутри for и if может использовать все переменные, которые были определены ранее:
In : [
(x, y)
for x in range(3)
for y in range(x + 2)
if x != y
]
Out: [
(0, 1),
(1, 0), (1, 2),
(2, 0), (2, 1), (2, 3)
]
Можно смешивать if и for в любом порядке:
In : [
(x, y)
for x in range(5)
if x % 2
for y in range(x + 2)
if x != y
]
Out: [
(1, 0), (1, 2),
(3, 0), (3, 1), (3, 2), (3, 4)
]
📲 Мы в MAX
👉@BookPython