Библиотека Python разработчика | Книги по питону
Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍 По всем вопросам @evgenycarter РКН clck.ru/3Ko7Hq
Show more📈 Analytical overview of Telegram channel Библиотека Python разработчика | Книги по питону
Channel Библиотека Python разработчика | Книги по питону (@bookpython) in the Russian language segment is an active participant. Currently, the community unites 17 970 subscribers, ranking 7 269 in the Technologies & Applications category and 36 632 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 17 970 subscribers.
According to the latest data from 29 October, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -98 over the last 30 days and by -6 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 5.47%. Within the first 24 hours after publication, content typically collects 2.71% reactions from the total number of subscribers.
- Post reach: On average, each post receives 983 views. Within the first day, a publication typically gains 487 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 4.
- Thematic interests: Content is focused on key topics such as numbers, yield, модуль, none, декоратор.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍
По всем вопросам @evgenycarter
РКН clck.ru/3Ko7Hq”
Thanks to the high frequency of updates (latest data received on 30 October, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
+:
>>> [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})
👉@BookPythonEllipsis, которую также можно записать как .... Эта константа не имеет особого значения для интерпретатора, но используется в тех местах, где подобный синтаксис уместен.
numpy поддерживает Ellipsis как аргумент для __getitem__. Например, x[...] возвращает все элементы массива x.
PEP 484 задаёт дополнительный смысл: Callable[..., type] — способ определить тип вызываемых объектов без указания типов аргументов.
Наконец, ... можно использовать, чтобы показать, что функция ещё не реализована. Это полностью корректный Python-код:
def x():
...
👉@BookPython
>>> class A:
... x = 2
... def f():
... print(x)
... f()
...
[...]
NameError: name 'x' is not defined
Обычно это не проблема: методы объявляются внутри класса только для того, чтобы стать методами и вызываться позже:
>>> class A:
... x = 2
... def f(self):
... print(self.x)
...
>>> A().f()
2
Что немного неожиданно — то же самое верно и для генераторов и списковых включений (comprehensions).
Они имеют свою собственную область видимости и не могут обращаться к области видимости класса.
Это особенно логично для генераторов, так как они выполняются уже после того, как создание класса завершено.
>>> class A:
... x = 2
... y = [x for _ in range(5)]
...
[...]
NameError: name 'x' is not defined
Comprehensions при этом не имеют доступа к self.
Единственный способ заставить это работать - добавить ещё один уровень области видимости, например, через lambda (да, это выглядит не слишком красиво):
>>> class A:
... x = 2
... y = (lambda x=x: [x for _ in range(5)])()
...
>>> A.y
[2, 2, 2, 2, 2]
👉@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.
👉@BookPythonrepr для других объектов внутри собственного метода __repr__ заключается в том, что нельзя гарантировать, что ни один из этих объектов не равен self, и вызов не окажется рекурсивным:
In : p = Pair(1, 2)
In : p
Out: Pair(1, 2)
In : p.right = p
In : p
Out: [...]
RecursionError: maximum recursion depth exceeded while calling a Python object
Чтобы легко решить эту проблему, можно использовать декоратор reprlib.recursive_repr:
@reprlib.recursive_repr()
def __repr__(self):
class_name = type(self).__name__
return f'{class_name}({self.left!r}, {self.right!r})'
Теперь всё работает корректно:
In : p = Pair(1, 2)
In : p.right = p
In : p
Out: Pair(1, ...)
👉@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()
👉@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
👉@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)()
👉@BookPythontry ... except Exception, которая предназначена для отлова «любых ошибок». Чтобы безопасно это обработать внутри корутины, приходится писать примерно так:
try:
await action()
except asyncio.CancelledError:
raise
except Exception:
logging.exception('action failed')
👉@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='')
👉@BookPythonitertools.tee() создаёт несколько итераторов из одного. Это может быть полезно, если нескольким потребителям нужно читать один и тот же поток.
Пример:
In : a, b, c = tee(iter(input, ''), 3)
In : next(a), next(c)
FIRST
Out: ('FIRST', 'FIRST')
In : next(a), next(b)
SECOND
Out: ('SECOND', 'FIRST')
In : next(a), next(b), next(c)
THIRD
Out: ('THIRD', 'SECOND', 'SECOND')
Данные, которые ещё не были использованы всеми итераторами, сохраняются в памяти. Если часть созданных итераторов ещё не была начата в тот момент, когда другой уже дошёл до конца, это означает, что все сгенерированные элементы будут храниться в памяти для будущего использования.
В таком случае проще и эффективнее использовать list(iter(input, '')), чем tee.
👉@BookPython