Библиотека Python разработчика | Книги по питону
описание
Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍 По всем вопросам @evgenycarter РКН clck.ru/3Ko7Hq
18 166
подписчиков
Охват к подписчикам
5,3%
ERR
Реакции к просмотрам
0,38%
116 на 26 постов
Пересылки к просмотрам
0,63%
193
Постов в день
0,6
всего 26
Где отзываются чаще
доля реакций к просмотрам- 9 авг.Прямой доступ к атрибутам объекта может быть не самой лучшей идеей. Если клиенты взаимодействуют с объектом через методы, вы всегда можете изменить способ обработки каждого запроса, в то время как при прямом доступе к атрибутам это может быть невозможно. Разные языки решают эту проблему по-разному. В Ruby синтаксически невозможно получить прямой доступ к атрибуту: obj.x — это вызов метода x. В Java рекомендуется делать все атрибуты приватными и писать тривиальные геттеры, например: public int getX() { return this.x; }. Python предлагает решение, которое в некотором роде похоже на то, что есть в Ruby. Вы можете определить свойство (property), чтобы obj.x вызывал метод вместо прямого возврата атрибута x. class Example: def __init__(self, x): self._x = x @property def x(self): return self._x 📲 Мы в MAX 👉@BookPython0,87%
- 27 июл.Метод format в Python для строк — мощный инструмент, поддерживающий множество возможностей, о которых вы, возможно, даже не знали. Каждый заменяемый плейсхолдер ({...}) может содержать три части: имя поля, преобразование и спецификацию формата. Имя поля используется для указания, какой именно аргумент должен быть подставлен: >>> '{}'.format(42) '42' >>> '{1}'.format(1, 2) '2' >>> '{y}'.format(x=1, y=2) '2' Преобразование позволяет указать, что вместо str() следует использовать repr() (или ascii()) при преобразовании объектов в строки: >>> '{!r}'.format(datetime.now()) 'datetime.datetime(2018, 5, 3, 23, 48, 49, 157037)' >>> '{}'.format(datetime.now()) '2018-05-03 23:49:01.060852' Спецификация формата задаёт, как значения будут представлены: >>> '{:+,}'.format(1234567) '+1,234,567' >>> '{:>19}'.format(1234567) ' 1234567' Эта спецификация может быть применена и к отдельному объекту с помощью функции format (не метода str): >>> format(5000000, '+,') '+5,000,000' Функция format вызывает метод __format__ объекта, поэтому вы можете изменить его поведение для своих типов. 📲 Мы в MAX 👉@BookPython0,73%
- 14 авг.Иногда вам нужно очистить коллекцию в Python. Вы, вероятно, используете что-то вроде d = {} (для словарей), но на самом деле это не очистка, а создание новой коллекции и выбрасывание старой. Это может сработать для вас, но другие владельцы того же объекта всё ещё будут иметь ссылку на оригинальный. Правильный способ очистки словаря, множества, deque и других коллекций — вызвать x.clear(). 📲 Мы в MAX 👉@BookPython0,73%
- 20 июл.Как ускорить вычисления в Python с помощью multiprocessing.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 👉@BookPython0,61%
- 19 июл.Когда вам нужно очистить список в Python, вы, скорее всего, используете lst = []. Однако на самом деле вы просто создаёте новый пустой список и присваиваете его переменной lst, а все другие переменные, которые ссылаются на исходный список, продолжают хранить его содержимое. Пример: lst = [1, 2, 3] lst2 = lst lst = [] print(lst2) # [1, 2, 3] Хотя это кажется очевидным, правильное решение стало доступно только с введением метода lst.clear() в Python 3.3. До этого для очистки списка приходилось использовать: - del lst[:], или - lst[:] = []. Оба варианта работают, поскольку срезы позволяют модифицировать часть списка. Если вы берёте срез [:], он охватывает весь список. Теперь же lst.clear() является более читаемым и современным решением. 📲 Мы в MAX 👉@BookPython0,58%
- 17 июл.Почему range() в 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 году написал блестящую статью, в которой обосновал преимущества полуоткрытых интервалов. Это не просто удобство — это вопрос корректности и простоты работы с данными. 📲 Мы в MAX 👉@BookPython0,57%
- 21 июл.Модуль multiprocessing в 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 👉@BookPython0,55%
- 22 июл.📌 О представлении данных в байтах в Python Когда мы храним данные в памяти или на устройстве хранения, их необходимо представить в виде байтов. Python позволяет работать с абстракцией данных, не задумываясь об их байтовом представлении. Однако при записи строки в файл мы фактически работаем с физической структурой данных. Чтобы записать символы в файл, их нужно преобразовать в байты — это называется кодированием (encoding). Когда вы читаете байты из файла и хотите преобразовать их в понятные символы, этот процесс называется декодированием (decoding). 🔤 Кодировки и их применение Существует множество методов кодирования. Один из самых популярных — Unicode, но сам по себе Unicode не является кодировкой в традиционном смысле. Unicode определяет соответствие между символами и их числовыми кодами. Например, 🐍 имеет код 128 013. Однако, чтобы записать числа в файл, нужна настоящая кодировка. Unicode обычно используется с utf-8, которая (в большинстве случаев) является кодировкой по умолчанию в Python. При чтении из файла Python автоматически декодирует данные, используя utf-8. Если вы хотите использовать другую кодировку, просто укажите её с помощью параметра encoding= в функции 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 👉@BookPython0,50%
- 30 июл.В Python блок else можно использовать не только после if, но и после циклов for и while. Код внутри else выполняется только в том случае, если цикл завершился естественным образом, то есть не был прерван с помощью break. Наиболее распространённый случай использования этого — поиск элемента в цикле с прерыванием через break, если элемент найден: # Пример 1: Список содержит нечётное число first_odd = None for x in [2, 3, 4, 5]: if x % 2 == 1: # Проверяем, является ли число нечётным first_odd = x break # Прерываем цикл, так как элемент найден else: raise ValueError('No odd elements in list') # Выполнится, если цикл завершился без break print(first_odd) # Результат: 3 Если в списке нет подходящего элемента, цикл завершается естественным образом, и выполняется блок else: # Пример 2: Список не содержит нечётных чисел for x in [2, 4, 6]: if x % 2 == 1: first_odd = x break else: raise ValueError('No odd elements in list') # Исключение будет поднято # ValueError: No odd elements in list 📲 Мы в MAX 👉@BookPython0,50%
- 10 авг.С версии Python 3.0 выбрасывание исключения внутри блока except автоматически добавляет перехваченное исключение в атрибут __context__ нового исключения. Это приводит к тому, что оба исключения отображаются в traceback: try: 1 / 0 except ZeroDivisionError: raise ValueError('Zero!') Результат выполнения: Traceback (most recent call last): File "test.py", line 2, in <module> 1 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "test.py", line 4, in <module> raise ValueError('Zero!') ValueError: Zero! Вы также можете добавить __cause__ к любому исключению с помощью выражения raise ... from: division_error = None try: 1 / 0 except ZeroDivisionError as e: division_error = e raise ValueError('Zero!') from division_error Результат выполнения: Traceback (most recent call last): File "test.py", line 4, in <module> 1 / 0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 8, in <module> raise ValueError('Zero!') from division_error ValueError: Zero! 📲 Мы в MAX 👉@BookPython0,49%
- 12 авг.Профайлинг и Оптимизация Производительности Когда ваш код уже не просто работает, а должен летать, на первый план выходит умение профилировать и оптимизировать производительность. Для Python это не просто трюки, а глубокое понимание CPython и его инструментов. Шаг 1: Точное Измерение (Profiling) Прежде чем оптимизировать, мы должны знать, где именно тратятся ресурсы. Слепое улучшение кода — это путь к бессмысленным изменениям и появлению новых багов. - Модуль cProfile: Стандартный и самый надежный инструмент для нахождения горячих точек (hotspots) — функций, где тратится больше всего времени. Он показывает совокупное время (tottime) и общее время (cumtime) выполнения каждой функции, включая вызовы извне. import cProfile import re cProfile.run('re.compile("foo|bar")', filename='profile_data') # Анализируем результаты import pstats p = pstats.Stats('profile_data') p.sort_stats('cumulative').print_stats(10) - Line-by-line Profilers (line_profiler): Если cProfile показывает функцию, которая "тормозит", но вам нужно понять, какая именно строка внутри этой функции виновата, используйте внешние инструменты, такие как line_profiler. # Декоратор @profile над нужной функцией # Запуск: kernprof -l my_script.py # Анализ: python -m line_profiler my_script.py.lprof - Анализ Памяти (memory_profiler): Для задач, связанных с большими данными или длительными процессами, где утечки памяти или излишнее потребление критичны, используйте memory_profiler или pympler. Шаг 2: Реальная Оптимизация (Beyond Simple Fixes) Найдя узкое место, приступаем к устранению, используя знания об устройстве Python. 1. Уменьшение Накладных Расходов Интерпретатора (Interpreter Overhead) - Использование Встроенных Функций и Модулей C: Встроенные функции (например, sum, map, sorted) и функции из стандартной библиотеки, написанные на C (например, itertools, collections), работают значительно быстрее, чем их эквиваленты на чистом Python, поскольку избегают накладных расходов на GIL и байткод. - Пример: Используйте collections.deque вместо list для быстрого добавления/удаления с обоих концов. 2. Манипуляции с Данными NumPy/Pandas - Векторизация: Если вы работаете с числовыми данными, полностью переходите на NumPy и Pandas. Вместо циклов for в Python, обрабатывающих элементы по одному, используйте векторизованные операции. Это позволяет выполнять вычисления на уровне C/Fortran, эффективно используя процессор. 3. Параллелизм vs. Конкурентность - CPU-bound задачи (расчеты): Из-за GIL (Global Interpreter Lock) чистый Python не может эффективно использовать несколько ядер ЦП для одновременного выполнения кода на Python. Используйте модуль multiprocessing для распараллеливания задачи между процессами. - I/O-bound задачи (сеть, диск): Если код проводит много времени в ожидании (ввод-вывод), используйте конкурентность с помощью asyncio (асинхронный ввод-вывод) или threading. В этом случае GIL не мешает, так как Python "освобождает" его во время ожидания. 📲 Мы в MAX 👉@BookPython0,48%
- 5 авг.Иногда вам нужно запустить блок кода с несколькими контекстными менеджерами. Например: with open('f') as f: with open('g') as g: with open('h') as h: pass Начиная с Python 2.7 и 3.1, это можно сделать с помощью одного выражения with: o = open with o('f') as f, o('g') as g, o('h') as h: pass До этого можно было использовать функцию contextlib.nested: with nested(o('f'), o('g'), o('h')) as (f, g, h): pass Однако в современных версиях Python эта функция устарела и вызывает предупреждение. Вместо неё рекомендуется использовать более продвинутый инструмент — contextlib.ExitStack. Он позволяет войти в любое количество контекстов в произвольное время, но гарантирует их корректное завершение: from contextlib import ExitStack with ExitStack() as stack: f = stack.enter_context(o('f')) g = stack.enter_context(o('g')) other = [ stack.enter_context(o(filename)) for filename in filenames ] Это особенно полезно, когда количество контекстных менеджеров неизвестно заранее. 📲 Мы в MAX 👉@BookPython0,42%