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)