Python

Python — это прекрасный язык с множеством встроенных модулей, цель которых — помочь нам писать более красивый и качественный код.

Цель

В данной статье мы будем работать с малоизвестными методами и модулями, которые могут улучшить эффективность и внешний вид кода.

NamedTuple

Некоторые из вас уже слышали о популярном namedtupleиз модуляcollections(если нет, — читайте здесь). Но, начиная с Python 3.6, в модулеtyping появился новый класс: NamedTuple. Оба они позволяют быстро создавать читабельные неизменяемые объекты.

Вообще, NamedTuple — это типизированная версия namedtupleс лучшей читабельностью:

In [2]: import typing

In [3]: class BetterLookingArticle(typing.NamedTuple):
...: title: str
...: id: int
...: description: str = "No description given."
...:

In [4]: BetterLookingArticle(title="Python is cool.", id=1)
BetterLookingArticle(title='Python is cool.', id=1, description='No description given.')

Вот альтернатива namedtuple:

In [6]: import collections

In [7]: Article = collections.namedtuple("Article", ["title", "description", "id"])

In [8]: Article(title="Python is cool.", id=1, description="")
Article(title='Python is cool.', description='', id=1)

array.array

Эффективные массивы числовых значений. Массивы — это типы последовательности. Они очень похожи на списки, однако ограничены хранимым в них типом объектов. — Python docs

При работе с модулем array сначала нужно создать его экземпляры через typecode. Typecode — это тип, который будут использовать все элементы массива. Давайте сравним эффективность его выполнения с обычным списком. Для этого запишем в файл множество целых чисел (для обычного списка подойдет модуль pickle):

In [9]: import array
In [10]: import pickle
In [11]: double_array = array.array("i", range(10 ** 6))
...: start_time = time.time()
...: with open("array_temp.bin", "wb") as f:
...: double_array.tofile(f)
...: array_end_time = time.time() - start_time
In [12]: int_list = list(range(10 ** 6))
...: start_time = time.time()
...: with open("list_temp.bin", "wb") as f:
...: pickle.dump(int_list, f)
...: list_end_time = time.time() - start_time
In [13]: print(f"It took {array_end_time} for int_array to complete")
...: print(f"It took {list_end_time} for int_list to complete")
It took 0.006399869918823242 for int_array to complete
It took 0.03600811958312988 for int_list to complete
In [14]: 0.03600811958312988 / 0.006399869918823242
Out[14]: 5.62638304213389

В 14 раз быстрее. Это много. Конечно же, результат зависит и от самого модуля pickle. Тем не менее, массив компактнее списка. Так что если вы работаете с простыми числовыми значениями, то присмотритесь к модулю array.

itertools.combinations

itertools— это отличный модуль. В нем присутствует множество методов для экономии времени. Полный список см. здесь. Есть даже целый GitHub репозиторий с еще большим количеством itertools!

На этой неделе я поработал с методом combinations и решил им поделиться. Этот метод принимает в качестве аргументов iterable и integer, а затем создает генератор со всеми возможными сочетаниями iterable и максимальной длиной из integer, без задвоения:

In [16]: import itertools
In [17]: list(itertools.combinations([1, 2, 3, 4], 2))
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

dict.fromkeys

Быстрый и красивый способ создания dict со стандартными значениями:

In [18]: dict.fromkeys(["key1", "key2", "key3"], "DEFAULT_VALUE")
{'key1': 'DEFAULT_VALUE', 'key2': 'DEFAULT_VALUE', 'key3': 'DEFAULT_VALUE'}

Последний, но не менее важный — модуль dis

Модуль dis поддерживает анализ байт-кода CPython путем его обратного разложения.

Быть может, вы уже знаете(или не знаете) о том, что Python компилирует исходный код в набор инструкций. Они и называются «байт-кодом». А модуль dis позволяет их обрабатывать. Это отличный инструмент для отладки.

Вот пример из книги Fluent Python:

In [22]: t = (1, 2, [3, 4])
In [23]: t[2] += [30, 40]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-25-af836a8d44a2> in <module>
----> 1 t[2] += [30, 40]

TypeError: 'tuple' object does not support item assignment

In [24]: t
Out[24]: (1, 2, [3, 4, 30, 40])

Выскочила ошибка, но операция все-таки завершилась. Как так? Давайте внимательнее изучим байт-код (см. комментарии рядом с важными строками):

In [25]: dis.dis("t[a] += b")
  1           0 LOAD_NAME                0 (t)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD --> (value in t[a]) += b --> успешно выполняется, поскольку это изменяемый список
             12 ROT_THREE
             14 STORE_SUBSCR --> Assign t[a] = our list --> выдает ошибку, поскольку t[a] не изменяемый.
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

Перевод статьи Adam Goldschmidt: Awesome Python modules you probably aren’t using (but should be)