Библиотека Python разработчика | Книги по питону
Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍 По всем вопросам @evgenycarter РКН clck.ru/3Ko7Hq
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام Библиотека Python разработчика | Книги по питону
تُعد قناة Библиотека Python разработчика | Книги по питону (@bookpython) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 18 329 مشتركاً، محتلاً المرتبة 7 307 في فئة التكنولوجيات والتطبيقات والمرتبة 36 869 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 18 329 مشتركاً.
بحسب آخر البيانات بتاريخ 04 يونيو, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -86، وفي آخر 24 ساعة بمقدار -1، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 6.07%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً 2.61% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 1 112 مشاهدة. وخلال اليوم الأول يجمع عادةً 479 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 2.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل numbers, yield, модуль, none, декоратор.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍
По всем вопросам @evgenycarter
РКН clck.ru/3Ko7Hq”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 05 يونيو, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.
__hash__. Этот метод возвращает целое число, но при этом важно соблюдать одно ключевое требование: равные объекты должны иметь одинаковый хэш (обратное утверждение необязательно).
👉 Не используйте изменяемые объекты в качестве ключей! Если объект изменяется после добавления в словарь, он становится "невидимым" для поиска, так как его хэш может измениться.
🌀 Странность с отрицательными хэшами
Есть интересная особенность, которая может вас удивить при отладке или написании юнит-тестов. Рассмотрим следующий пример:
class A:
def __init__(self, x):
self.x = x
def __hash__(self):
return self.x
Результаты хэширования экземпляров класса:
>>> hash(A(2))
2
>>> hash(A(1))
1
>>> hash(A(0))
0
>>> hash(A(-1)) # внимание!
-2
>>> hash(A(-2))
-2
💡 В CPython значение -1 зарезервировано для внутренних ошибок. Если хэш-значение равно -1, интерпретатор автоматически преобразует его в -2. Это может вызывать неожиданные проблемы при сравнении или использовании объектов в качестве ключей.
📲 Мы в MAX
👉@BookPythonencoding= в функции open. А чтобы работать с "чистыми" байтами, добавьте символ b к режиму открытия файла.
Пример:
# Кодирование строки в файл
with open('example.txt', 'w', encoding='utf-8') as f:
f.write('Привет, мир!')
# Чтение в байтовом режиме
with open('example.txt', 'rb') as f:
data = f.read()
print(data) # Вывод: b'\xd0\x9f\xd1\x80\xd0\xb8...'
📲 Мы в MAX
👉@BookPythonbisect_left возвращает самую левую позицию элемента в отсортированном списке, а bisect_right — самую правую.
from random import randrange
from bisect import bisect_left
n = 1000000
look_for = 555555
lst = sorted(randrange(0, n) for _ in range(n))
%timeit look_for in lst
# 69.7 ms ± 449 µs на цикл
%timeit look_for == lst[bisect_left(lst, look_for)]
# 927 ns ± 2.28 ns на цикл
Результаты демонстрируют, что использование бинарного поиска через bisect_left быстрее, чем стандартный поиск в списке с помощью оператора in.
📲 Мы в MAX
👉@BookPythonmultiprocessing в Python: потоки против процессов
Модуль multiprocessing позволяет создавать не только процессы, но и потоки. Однако стоит помнить о главной особенности CPython — GIL (Global Interpreter Lock). Этот механизм блокирует выполнение байт-кода Python несколькими потоками одновременно.
Это означает, что потоки полезны в основном в случаях, когда программа выполняет операции, не связанные с Python-интерпретатором, например, ожидание ввода-вывода (IO). К примеру, загрузка трёх различных статей из Википедии будет одинаково эффективной как с потоками, так и с процессами. Причём результат в три раза быстрее по сравнению с выполнением задачи в одном процессе:
from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import requests
def download_wiki_article(article):
url = 'http://de.wikipedia.org/wiki/'
return requests.get(url + article)
process_pool = Pool(3)
thread_pool = ThreadPool(3)
thread_pool.map(download_wiki_article, ['a', 'b', 'c'])
# ~376 ms
process_pool.map(download_wiki_article, ['a', 'b', 'c'])
# ~373 ms
[download_wiki_article(a) for a in ['a', 'b', 'c']]
# ~1.09 s
Однако использование потоков для задач, нагружающих CPU, практически бессмысленно:
import math
from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
def f(x):
return len(str(math.factorial(x)))
process_pool = Pool(4)
thread_pool = ThreadPool(4)
inputs = [i ** 2 for i in range(100, 130)]
[f(x) for x in inputs]
# ~1.48 s
thread_pool.map(f, inputs)
# ~1.48 s
process_pool.map(f, inputs)
# ~478 ms
При задачах, требующих интенсивных вычислений, использование процессов вместо потоков даст значительный прирост производительности благодаря распределению нагрузки между несколькими ядрами процессора.
📲 Мы в MAX
👉@BookPythonmultiprocessing.Pool
Когда речь заходит о ресурсоемких задачах, которые нагружают ваш CPU, стоит обратить внимание на библиотеку multiprocessing, а именно на класс Pool. Он позволяет задействовать все доступные ядра процессора, автоматически распределяя задачи между ними.
Вот простой пример:
import math
from multiprocessing import Pool
# Генерируем список входных данных
inputs = [i ** 2 for i in range(100, 130)]
# Функция для вычислений
def f(x):
return len(str(math.factorial(x)))
# Последовательное выполнение
%timeit [f(x) for x in inputs]
# Результат: ~1.44 сек
# Параллельное выполнение
p = Pool(4) # Создаем пул из 4 процессов
%timeit p.map(f, inputs)
# Результат: ~451 мс
📲 Мы в MAX
👉@BookPythonLIMIT, например:
SELECT *
FROM table
LIMIT 1001, 100;
Этот запрос действительно вернет 100 записей, с 1001-й по 1100-ю. Но проблема в том, что для базы данных это так же сложно, как и выборка всех первых 1001 записей. Чем дальше запрашиваемая страница, тем медленнее будет выполняться запрос.
Более оптимальным решением является использование фильтрации через WHERE, где клиент передает идентификатор последней записи текущей страницы ($last_seen_id в примере):
SELECT *
FROM table
WHERE id > $last_seen_id
ORDER BY id ASC
LIMIT 100;
Этот подход позволяет избежать сканирования всех предыдущих строк, что значительно ускоряет работу с большими объемами данных.
Если хотите разобраться подробнее, рекомендую почитать отличную статью на эту тему!
📲 Мы в MAX
👉@BookPythonlst = []. Однако на самом деле вы просто создаёте новый пустой список и присваиваете его переменной lst, а все другие переменные, которые ссылаются на исходный список, продолжают хранить его содержимое.
Пример:
lst = [1, 2, 3]
lst2 = lst
lst = []
print(lst2) # [1, 2, 3]
Хотя это кажется очевидным, правильное решение стало доступно только с введением метода lst.clear() в Python 3.3.
До этого для очистки списка приходилось использовать:
- del lst[:], или
- lst[:] = [].
Оба варианта работают, поскольку срезы позволяют модифицировать часть списка. Если вы берёте срез [:], он охватывает весь список.
Теперь же lst.clear() является более читаемым и современным решением.
📲 Мы в MAX
👉@BookPythonrange, поддерживают вызов len():
len(range(10000)) # 10000
Однако генераторы не имеют длины, и попытка вызвать len() вызовет ошибку:
gen = (x ** 2 for x in range(10000))
len(gen) # TypeError: object of type 'generator' has no len()
Стандартное решение: преобразование в список
Один из способов получить размер генератора — это преобразовать его в список:
gen = (x ** 2 for x in range(10000))
print(len(list(gen))) # 10000
Этот подход работает, но имеет серьёзный недостаток: он требует загрузить все значения генератора в память. Если генератор очень большой, это может привести к нехватке памяти.
Более эффективный подход: подсчёт с помощью sum
Чтобы избежать лишнего расхода памяти, можно подсчитать количество элементов в генераторе с использованием sum():
gen = (x ** 2 for x in range(10000))
print(sum(1 for _ in gen)) # 10000
Этот метод обходит генератор "лениво", не создавая дополнительных списков, что делает его идеальным для работы с большими потоками данных.
Резюме
- Используйте len() только для итераторов, поддерживающих его (например, range).
- Для генераторов избегайте преобразования в список, если важна экономия памяти.
- Используйте sum(1 for _ in gen) для эффективного подсчёта элементов генератора.
📲 Мы в MAX
👉@BookPythonrange() в Python использует полуоткрытые интервалы?
Функция range() в Python работает с полуоткрытыми интервалами. Например, range(2, 10) задаёт числа в диапазоне [2, 10), то есть [2, 3, 4, 5, 6, 7, 8, 9]. На первый взгляд это может показаться неочевидным или асимметричным, но у такого подхода есть свои преимущества.
Почему полуоткрытые интервалы?
Полуоткрытые интервалы позволяют легко "склеивать" смежные диапазоны без риска ошибок на единицу:
- Если a = 2, b = 5, и c = 10, то [a, c) можно выразить как:
[a, c) = [a, b) + [b, c)Это работает идеально, потому что конец одного интервала (`b`) автоматически становится началом следующего. В случае закрытых интервалов, такая "склейка" требует дополнительной обработки:
[a, c] = [a, b] + [b+1, c]Связь с индексацией с нуля Индексация с нуля в Python также связана с этим принципом. Рассмотрим диапазон
range(0, N):
- Этот диапазон включает ровно N элементов, что делает код более предсказуемым:
for i in range(0, N):
print(i)
Здесь i проходит значения от 0 до N-1, что логично и удобно.
Преимущества для работы с массивами
Полуоткрытые интервалы идеально подходят для работы с индексами массивов:
arr = [10, 20, 30, 40, 50]
print(arr[1:3]) # [20, 30]
Интервал [1:3) охватывает элементы с индексами 1 и 2, но не 3, что упрощает вычисления границ.
Исторический контекст
Этот подход имеет глубокие корни в компьютерной науке. Эдсгер Дейкстра, один из пионеров программирования, в 1982 году написал блестящую статью, в которой обосновал преимущества полуоткрытых интервалов. Это не просто удобство — это вопрос корректности и простоты работы с данными.
👉 @BookPython
def between(x, (start, stop)):
return start < x < stop
interval = (5, 10)
print(between(2, interval)) # False
print(between(7, interval)) # True
Более того, это работало даже рекурсивно:
def determinant_2_x_2(((a, b), (c, d))):
return a * d - c * b
matrix = [
(1, 2),
(3, 4),
]
print(determinant_2_x_2(matrix)) # -2
Но начиная с Python 3, эта возможность была удалена из языка. Чтобы добиться того же результата, теперь нужно распаковывать параметры вручную:
def determinant_2_x_2(matrix):
row1, row2 = matrix
a, b = row1
c, d = row2
return a * d - c * b
matrix = [
(1, 2),
(3, 4),
]
print(determinant_2_x_2(matrix)) # -2
Удаление этой функциональности сделало код более явным и читаемым, но для любителей компактности Python 2 по-прежнему вызывает лёгкую ностальгию.
📲 Мы в MAX
👉@BookPython*args и **kwargs для того, чтобы передать в функцию любое количество позиционных и именованных аргументов. Для того, чтобы понять как это работает, сначала познакомимся с тем, что такое распаковка.
📲 Мы в MAX
👉@BookPython
try:
lst = [1, 2, 3, 4, 5]
print(lst[10])
except IndexError:
pass
Это будет работать (ничего не выводя), но contextlib позволяет сделать то же самое более выразительно и семантически правильно:
from contextlib import suppress
with suppress(IndexError):
lst = [1, 2, 3, 4, 5]
lst[10]
📲 Мы в MAX
👉@BookPython
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
