Библиотека Python разработчика | Книги по питону
19K subscribers
1.06K photos
403 videos
82 files
1.05K links
Полезные материалы для питониста по Фреймворкам Django, Flask, FastAPI, Pyramid, Tornado и др.

По всем вопросам @evgenycarter

РКН clck.ru/3Ko7Hq
Download Telegram
Когда корутина asyncio хочет остановиться и взаимодействовать с циклом событий (event loop), она использует await obj (или yield from obj до Python 3.6). Объект obj должен быть другой корутиной, объектом asyncio.Future или любым пользовательским объектом, похожим на Future (любой объект, у которого определен метод __await__).


async def coroutine():
await another_coroutine()

async def another_coroutine():
future = asyncio.Future()
await future

loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())


Когда корутина ожидает (await) другую корутину, вторая начинает выполняться вместо первой. Если она ожидает третью, то выполняется третья. Это продолжается до тех пор, пока какая-нибудь корутина не ожидает объект Future. Объект Future фактически возвращает значение, и тогда цикл событий (event loop) получает управление.

Какое значение возвращает Future? Оно возвращает сам себя. Можете ли вы напрямую использовать yield для Future? Нет, это внутренняя деталь, о которой вам обычно не нужно беспокоиться.


class Awaitable:
def __await__(self):
future = asyncio.Future()
yield future
# RuntimeError: yield was used
# instead of yield from in task

async def coroutine():
await Awaitable()

loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())


Почему возникает эта ошибка? Как asyncio понимает, что это вы используете yield для Future, а не сам Future? Есть простая защита: Future устанавливает внутренний флаг перед тем, как вернуть управление.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Рассмотрим следующую иерархию классов:


class GrandParent:
pass

class Parent1(GrandParent):
pass

class Parent2(GrandParent):
pass

class Child(Parent1, Parent2):
pass


В каком порядке будет производиться поиск метода Child.x()? Наивный подход заключается в рекурсивном поиске через все родительские классы, что даст порядок: Child, Parent1, GrandParent, Parent2. Такой метод используется во многих языках программирования, однако он не совсем логичен, так как Parent2 более специфичен, чем GrandParent, и его нужно проверять раньше.

Чтобы исправить эту проблему, Python использует линеаризацию C3 (C3 superclass linearization), алгоритм, который всегда ищет метод сначала во всех дочерних классах, а затем уже в родительских.

Пример вывода MRO (Method Resolution Order):


In : Child.__mro__
Out:
(__main__.Child,
__main__.Parent1,
__main__.Parent2,
__main__.GrandParent,
object)


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍53
Прямой доступ к атрибутам объекта может быть не самой лучшей идеей. Если клиенты взаимодействуют с объектом через методы, вы всегда можете изменить способ обработки каждого запроса, в то время как при прямом доступе к атрибутам это может быть невозможно.

Разные языки решают эту проблему по-разному. В 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

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
С версии 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

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Профайлинг и Оптимизация Производительности

Когда ваш код уже не просто работает, а должен летать, на первый план выходит умение профилировать и оптимизировать производительность. Для 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

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2