Небольшое вступление
Сегодня я хочу поговорить о трёх прекрасных библиотеках Python, которые очень сильно облегчают жизнь в разных сценариях жизни. Возможно не всем они приглянутся, не всем понравятся такие методы или решения, но это мой выбор и не вижу в нём ничего плохого. Моё желание заключается лишь в том, чтобы познакомить с этими библиотеками начинающих, которые только втягиваются в работу с Python.
Хочу сказать, во-первых, это моя вторая статья на данной площадке, я ещё только начинаю писать и, так скажем, это всё пока что ещё проба пера. Во-вторых, я приветствую любое мнение окружающих, но ещё лучше, если это будет конструктивная критика по делу, а не просто так, и да, примеры мои сугубо учебные, придуманные на ровном месте, не обязательно из каких-то реальных проектов и задач, поэтому не стоит придерживаться к ним в комментариях. А теперь к сути дела.
Itertools – полезным итераторам нет конца
Как можно догадаться из названия, itertools – это кладезь полезных итераторов, которым можно найти огромное количество разных вариантов применений. Пройдёмся по всему порядку:
-
itertools.count(start, step) — бесконечная арифметическая прогрессия На вход принимаем до двух аргументов: start и step. Start – начало прогрессии, step – шаг, с которым будет двигаться прогрессия. Вот пример с обращением к подобному итератора, простой и наглядный:
arithmetic_progression = count(2, 5) print(next(arithmetic_progression), end=" ") print(next(arithmetic_progression), end=" ") print(next(arithmetic_progression), end=" ") # Вывод: 2 7 12
-
itertools.cycle(iterable) — возвращает по одному значению из последовательности, повторенной бесконечное количество раз, то есть зацикленной. На вход данная функция принимает любое итерируемое значение.
list_of_books_names = ["Война и мир", 'Отцы и дети', 'На дне'] infinity_iteration = cycle(list_of_books_names) print(next(infinity_iteration)) print(next(infinity_iteration)) print(next(infinity_iteration)) print(next(infinity_iteration))
-
itertools.repeat(element, n) — создает итератор, который многократно возвращает один и тот же объект. На вход принимает два значения: какой-нибудь элемент, а также сколько раз его надо повторить. По умолчанию значение n равно бесконечности.
result = list(repeat('Привет', 4)) print(result) # Вывод: ['Привет', 'Привет', 'Привет', 'Привет']
-
itertools.accumulate(iterable) — аккумулирует суммы, но если объяснять проще, то он берёт все итерируемые числа по порядку и складывает их с предыдущим числом, но уже из нового списка. Проще показать на примере:
original_list = [4, 1, 7, 3, 10] new_list = list(accumulate([4, 1, 7, 3, 10])) print(new_list) # Вывод: [4, 5, 12, 15, 25]
Давайте разберём как это работает, первый элемент исходного списка(original_list) суммируется с предыдущим элементом нового списка, но так как такового нет, то остаётся просто четыре. Далее следующий элемент единица суммируется с предыдущим элементом нового списка, то есть с четвёркой и вот уже второй элемент нового списка равный пяти.
-
itertools.chain(iterable) — принимает на вход некоторое количество итерируемых объектов, последовательно берёт из каждого элемента по одному с самого начала и формирует новый итерируемый объект.
bin_number = list(chain([1, 0, 0, 1], [0, 1, 1, 1], [0, 1, 1, 0])) print(bin_number) # Вывод: [1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0]
-
itertools.combinations(iterable, r) — возвращает все возможные комбинации нужной длины без повторяющихся элементов. На вход принимает итерируемый объект и длину комбинации.
list_of_combinations = list(combinations("ABCD", 2)) print(list_of_combinations) # Вывод: [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
-
itertools.combinations_with_replacement(iterable, r) — возвращает все возможные комбинации нужной длины только уже с повторяющимися элементами.
list_of_combinations = list(combinations_with_replacement("ABCD", 2)) print(list_of_combinations) # Вывод: [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
-
itertools.compress(iterable, mask) — этот метод позволяет фильтровать итерируемый объект по заданной маске. На вход принимаем сначала итерируемый объект, а потом маску состоящую из 0/1 или из логических значений (True/False).
a = compress("BBABCCA", [1,0,0,1,1,1,0]) print(list(a)) # Вывод: ['B', 'B', 'C', 'C']
-
itertools.filterfalse(func, iterable) — возвращает итератор из значений, которые при постановке в функцию вернули значение False.
def is_perfect_power(n): """ Проверяет, является ли число степенью """ if n == 1: return True if n <= 0: return False max_exponent = math.floor(math.log2(n)) + 1 for m in range(2, max_exponent + 1): k = round(n ** (1 / m)) if k ** m == n: return True return False no_power_of_nums = filterfalse(is_perfect_power, [1, 4, 9, 81, 16, 99, 100, 110, 101, 43, 25, 15]) # Вывод: [99, 110, 101, 43, 15]
-
itertools.dropwhile(func, iterable) — работает почти также, как и itertools.filterfalse, но с небольшим отличием. Оно заключается в том, что dropwhile вернёт ту часть итерируемого объекта, которая идёт после элемента, который выдал значения False при использовании в func. Возьму тот же пример:
def is_perfect_power(n): """ Проверяет, является ли число степенью """ if n == 1: return True if n <= 0: return False max_exponent = math.floor(math.log2(n)) + 1 for m in range(2, max_exponent + 1): k = round(n ** (1 / m)) if k ** m == n: return True return False no_power_of_nums = dropwhile(is_perfect_power, [1, 4, 9, 81, 16, 99, 100, 110, 15]) # Вывод: [99, 100, 110, 15]
-
itertools.groupby(iterable, key=None) — эта функция создает итератор, который возвращает последовательность ключей и групп из итерируемой последовательности
iterable. Под ключом(key) здесь подразумевается функция. Например:
# Создаём повторяющийся список x = list('AaBbCc' * 3) # Промежуточный вывод: ['A', 'a', 'B', 'b', 'C', 'c', 'A', 'a', 'B', 'b', 'C', 'c', 'A', 'a', 'B', 'b', 'C', 'c'] x.sort() for key, elements in groupby(x): print(key, list(elements)) # Итоговый вывод: A ['A', 'A', 'A'] B ['B', 'B', 'B'] C ['C', 'C', 'C'] a ['a', 'a', 'a'] b ['b', 'b', 'b'] c ['c', 'c', 'c']
-
itertools.islice(iterable, start, stop, step=1) — это итератор, состоящий из среза другого итерируемого объекта.
a = islice("Hello, world!", 7, 13) print(list(a)) # Вывод: ['w', 'o', 'r', 'l', 'd', '!']
Важно сказать, что если вы укажете лишь один параметр, кроме самого итерируемого объекта, то это будет считаться концом среза:
a = islice("Hello, world!", 7) print(list(a)) # Вывод: ['H', 'e', 'l', 'l', 'o', ',', ' ']
-
itertools.permutations(iterable, R=None) — возвращает итератор, состоящий из перестановок размером с параметр R. Пример 1:
a = permutations("ABC") print(list(a)) # Вывод: [('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
Пример 2:
a = permutations("ABC", 2) print(list(a)) # Вывод: [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
-
itertools.product(
*iterables, repeat=1) — возвращает итератор, состоящий из перестановок с длиной repeat, но в отличии от permutations здесь итерируемые объекты повторяются. Можно указать несколько итерируемый объектов. Пример 1:
a = product("AC", repeat=3) print(list(a)) # Вывод: [('A', 'A', 'A'), ('A', 'A', 'C'), ('A', 'C', 'A'), ('A', 'C', 'C'), ('C', 'A', 'A'), ('C', 'A', 'C'), ('C', 'C', 'A'), ('C', 'C', 'C')]
Пример 2:
a = product("AB", "12", repeat=2) print(list(a)) # Вывод: [('A', '1', 'A', '1'), ('A', '1', 'A', '2'), ('A', '1', 'B', '1'), ('A', '1', 'B', '2'), ('A', '2', 'A', '1'), ('A', '2', 'A', '2'), ('A', '2', 'B', '1'), ('A', '2', 'B', '2'), ('B', '1', 'A', '1'), ('B', '1', 'A', '2'), ('B', '1', 'B', '1'), ('B', '1', 'B', '2'), ('B', '2', 'A', '1'), ('B', '2', 'A', '2'), ('B', '2', 'B', '1'), ('B', '2', 'B', '2')]
-
itertools.starmap(func, iterable) — применяет функцию к каждому элементу итерируемого объекта
data = [(2, 5), (3, 2), (10, 3)] result = starmap(pow, data) print(list(result)) # Вывод: [32, 9, 1000]
-
itertools.takewhile(func, iterable) — тоже самое, что и dropwhile, только наоборот. Возвращает итератор из бывшего итерируемого объекта, пока элементы возвращают True из функции. Пример всё тот же:
def is_perfect_power(n): """ Проверяет, является ли число степенью """ if n == 1: return True if n <= 0: return False max_exponent = math.floor(math.log2(n)) + 1 for m in range(2, max_exponent + 1): k = round(n ** (1 / m)) if k ** m == n: return True return False power_of_nums = takewhile(is_perfect_power, [1, 4, 9, 81, 16, 99, 100, 110, 101, 43, 25, 15]) # Вывод: [1, 4, 9, 81, 16]
-
itertools.tee(iterable, n=2) — возвращает кортеж из n итераторов.
a = tee("AB", 3) print([list(x) for x in a]) # Вывод: [['A', 'B'], ['A', 'B'], ['A', 'B']]
-
itertools.zip_longest(
*iterables, fillvalue=None) — работает примерно как встроенная функция zip, но берёт самый длинный итератор, объединяет с самым коротким попарно, а оставшиеся элементы без пары дополняет символом указанном в параметре fillvalue.
a = zip_longest('ABCDE', '01', fillvalue='*') print(list(a)) # Вывод: [('A', '0'), ('B', '1'), ('C', '*'), ('D', '*'), ('E', '*')]
С библиотекой itertools на этом всё. Как по мне весьма полезная библиотека, сам ей пользуюсь периодически, хоть и не всеми возможными функциями, лишь некоторыми, но всё равно использую, а пока писал эту статью узнал большое количество новых для себя функций и возможно буду применять их в своей работе.
Collections — предоставляет новые типы данных на основе уже имеющихся
-
collections.Counter — особый вид словаря, который может посчитать количество элементов в итерируемом объекте.
data = ["Aabb", "AAbb", "AaBB", "Aabb", "Aabb", "AAbb", "AaBb"] result = Counter(data) # Вывод: Counter({'Aabb': 3, 'AAbb': 2, 'AaBB': 1, 'AaBb': 1})
У Counter есть парочку своих особых методов:
1. elements() — возвращает все элементы в алфавитном порядке.
2. most_common(n) — возвращает n самых часто встречающихся элементов в словаре Counter, выводит всё в порядке убывания. Если n не указать, то вернёт все элементы.
3. subtract(iterable or mapping) — позволяет вычитать из Counter либо же словари такого же типа Counter, либо обычные словари.
Также словари типа Counter поддерживают обычное сложение, вычитание, объединение(&) и пересечение(|).
-
collections.deque(iterable, maxlen) — создаёт очередь из итерируемого объекта, где максимальная длина задаётся переменной maxlen. Очереди похожи на списки, но элементы добавляются справкой стороны очереди, либо с левой, то же самое и с удалением элементов.
Очереди поддерживают большинство методов списков, такие как: append, clear, count, pop, extend, remove, reverse. Также к обычным методам добавляются всё те же, только, которые производят действия с левой стороны очереди: appendleft, extendleft, popleft. Помимо этого есть ещё один новый метод – rotate(n). Данный метод переносит n элементов из начала очереди в конец. Если указать n отрицательным, то переносит наоборот из конца в начало. -
collections.defaultdict(type) — тип данных, почти ничем не отличающийся от обычного словаря (dict), за исключением того, что он не выдаёт ошибку типа KeyError, так как при попытке обратиться
defdict = defaultdict(list) defdict[1].append("Ivan") print(defdict) # Вывод:
-
collections.OrderedDict — ещё одна разновидность словарь, которая в первую очередь отличается тем, что запоминает порядок ключей, записанных в этот словарь. Конечно, классический dict это тоже делает, но только начиная с версии Python 3.7, до этого вывод был не упорядоченным. Методы:
-
popitem(last=True) — Удаляет последний элемент словаря, но если указать аргумент last = False, то наоборот удалит первый элемент словаря
-
move_to_end(key, last=True) — перемещает существующий ключ в конец словаря, но если указать аргумент last = False, то в начало словаря.
-
collections.namedtuple() — это своего рода класс, состоящий только из полей данных, без методов, и который ведёт себя как кортеж, то есть не изменяется.
Man = namedtuple('Ivan', ["Grade", "Surname"]) man1 = Man(5, "Pupkin") print(man1) print(man1.Grade) # Вывод: Ivan(Grade=5, Surname='Pupkin') 5
Functools — это своего рода сборник функций более высокого уровня, пригодится конечно не всегда, но есть пару полезных моментов
Начнём с моего любимого:
-
@lru_cache(maxsize=128, typed=False) — это декоратор, который кэширует данные функции. Такое может пригодиться, чтобы сэкономить время при очень дорогих вычислениях, если вы вызываете функцию с одними и теми же аргументами. Параметр maxsize отвечает за максимальный размер кэша, если установить значение None, то кэш будет возрастать бесконечно. Параметр typed отвечает за кэширование аргументов функции в том случаи, если у них отличаются типы. Например, если у вас функция запускается с аргументом 3(int) и 3.0(float), то при значении аргумента typed=True, эти случаи будут кэшироваться отдельно, так как у них разные типы данных.
@lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) print([fib(n) for n in range(10)])
Самый наглядный и распространённый пример это числа Фибоначчи. Если хотите убедиться, то попробуйте запустить этот же алгоритм, но без декоратора и с большими числами (100, 500, 1000…), увидите, что это будет слишком длительные вычисления.
Для этого декоратора также существуют две дополнительные функции:
1. cache_info() — возвращающая namedtuple, отображающий: попадания в кэш, промахи, максимальный размер и текущий размер кэша.
2. cache_clear() — очищает данные кэша.
-
functools.cmp_to_key(func) — Используется с пользовательскими инструментами, которые в качестве ключа сортировки принимают подобные функции:
sorted(),max,min,itertools.groupby(о которой как раз-таки говорили ранее),heapq.nlargest,heapq.nsmallest. Эта функция в основном используется в качестве инструмента перехода для программ, конвертируемых из Python 2. Сейчас местами это конечно не очень актуально, но как по мне знать такое полезно. Пояснение: В версии Python 2.4 или более ранних версиях, как таковой функцииsorted()не было, а обычныйlist.sort()не принимал аргументов с ключевыми словами для сортировки. Вместо этого, все версии Pyton 2.X поддерживали параметрcmpдля обработки пользовательских функций сравнения.
В Pyton 3.0 параметр cmp был удален для устранения конфликта между «богатыми сравнениями» и «магическим» методом __cmp__().
В Python 2 можно было писать:
numbers = [5, 2, 8, 1, 3] sorted_numbers = sorted(numbers, cmp=lambda a, b: b - a) # Вывод: [8, 5, 3, 2, 1]
В Python 3 подобное реализовывалось бы так:
def compare(a, b): if a < b: return 1 elif a == b: return 0 else: return -1 numbers = [5, 2, 8, 1, 3] sorted_numbers = sorted(numbers, key=cmp_to_key(compare)) # Вывод: [8, 5, 3, 2, 1]
В целом данная функция нужна для сортировок со сложными условиями, например, если нужно сравнивать объекты по нескольким полям с разной логикой, а также данная функция необходима при портировании кода с Python 2 на Python 3.
-
@functools.total_ordering — это декоратор, который помогает создавать объекты с «богатым сравнениям», то есть, если вы хотите создать класс, который можно будет сравнивать обычными знаками сравнения (<, >, =, <=, >=, !=), то вам пришлось бы вручную определить все шесть соответствующих «магических» методов:
__lt__, __le__, __gt__, __ge__, __eq__, __ne__. Но этот декоратор делает это за вас. Основное правило для использования @total_ordering простое:
-
Примените декоратор @total_ordering к вашему классу.
-
Обязательно определите в классе метод
__eq__(self, other)для проверки на равенство. -
Определите хотя бы один из следующих методов:
-
__lt__(self, other) для операции «меньше» (<) -
__le__(self, other) для операции «меньше или равно» (<=) -
__gt__(self, other) для операции «больше» (>) -
__ge__(self, other) для операции «больше или равно» (>=) Этого достаточно. Декоратор проанализирует существующие методы и доопределит все остальные, используя логические связи между ними. Например, если вы определили__eq__и__lt__, то декоратор создаст__gt__,__le__и__ge__на их основе .
-
-
functools.partial(func,
*args,**kwargs) — это функция, которая позволяет создавать новые функции путем «замораживания» или фиксации некоторых аргументов существующей функции . Этот механизм называется частичным применением (partial application). Проще говоря, partial берет функцию и часть ее аргументов, а на выходе дает новую, более простую функцию, которая при вызове уже будет знать об этих «замороженных» аргументах. Например:
base_two = partial(int, base=2) # Функция конвертирует строку с записью двоичного кода в десятичное число типа int print(basetwo('11001011')) # Вывод: 203
-
functools.reduce(function, iterable) — эта функция которая работает по принципу сворачивания всего итерируемого объекта в одно значения. То есть, оно берёт первые два значения итерируемого объекта, применяет к ним функцию и получает новое одно значения, далее берёт третье значение из итерируемого объекта и к нему и ранее полученному значения применяет функцию и так далее, пока не получит одно единственное значение.
result = reduce(lambda a, b: a**2+b**2, [1,6, 8, 0, 2]) print(result) # Вывод: 4216817073125
-
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) — используется для корректного копирования метаданных (например, имени, документации, аннотаций) из оригинальной функции в её обёртку (декоратор или другую функцию-заменитель). Когда вы создаёте декоратор или любую обёртку вокруг функции, Python по умолчанию теряет метаданные оригинальной функции (например,
__name__,__doc__,__module__,__annotations__).
Функция обновляет у обёртки (wrapper) следующие атрибуты из исходной функции:
-
__module__ -
__name__ -
__qualname__(в Python 3.3+) -
__doc__ -
__annotations__(в Python 3+) -
__dict__(дополнительные атрибуты)
Например:
def my_decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result update_wrapper(wrapper, func) # Копируем метаданные из func в wrapper return wrapper @my_decorator def greet(name): print(f"Привет, {name}!") print(greet.__name__) # 'greet' (корректно!) print(greet.__doc__) # 'Приветствует пользователя.' (корректно!)
-
@wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) — тоже самое, что и функция functools.update_wrapper, описанная выше. Это своего рода «синтаксически сахар» для упрощения жизни.
def my_decorator(func): @wraps(func) # Автоматически вызывает update_wrapper def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result return wrapper
На этом всё. Я постарался максимально подробно объяснить всё своими словами, используя документации этих библиотек. Хочется верить, что кому-то это было полезно, конечно же данная статья в первую очередь ориентирована больше на начинающих ребят-питонистов, ну, как минимум мне так кажется 🙂
Благодарю за прочтение!
ссылка на оригинал статьи https://habr.com/ru/articles/938242/
Добавить комментарий