Функции модуля operator. Классы itemgetter и attrgetter

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

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

Функции модуля operator можно разбить на следующие категории:

Арифметические операции

add(a, b)

a + b

сложение

sub(a, b)

a - b

вычитание

mul(a, b)

a * b

умножение

truediv(a, b)

a / b

обычное деление

floordiv(a, b)

a // b

целочисленное деление

pow(a, b)

a ** b

возведение в степень

mod(a, b)

a % b

вычисление остатка от деления

neg(a)

-a

отрицание (унарный минус)

Битовые операции

and_(a, b)

a & b

битовое И

or_(a, b)

a | b

битовое ИЛИ

xor(a, b)

a ^ b

исключающее ИЛИ (XOR)

invert(a)

~a

битовое НЕ

Логические операции

truth(obj)

obj

проверка на истинность

not_(a)

not a

отрицание

is_(a, b)

a is b

идентичность

is_not(a, b)

a is not b

не идентичность

lt(a, b)

a < b

меньше

le(a, b)

a <= b

меньше либо равно

eq(a, b)

a == b

равно

ne(a, b)

a != b

не равно

ge(a, b)

a >= b

больше либо равно

gt(a, b)

a > b

больше

Работа с последовательностями

concat(seq1, seq2)

seq1 + seq2

объединение

contains(seq, obj)

seq in obj

вхождение

setitem(obj, k, v)

obj[k] = v

присвоение по индексу

delitem(obj, k)

del obj[k]

удаление по индексу

getitem(obj, k)

obj[k]

получение значения по индексу

getitem(seq, slice(i, j))

seq[i:j]

взятие среза

Существуют и некоторые другие функции в модуле operator. Со всеми можно подробно ознакомиться на странице официальной документации:

https://docs.python.org/3/library/operator.html

Зачем же понадобилось вводить все эти функции, которые дублируют существующие операторы языка Python? Дело в том, что иногда в программах вместо явного оператора требуется его аналог в виде функции. Например, ранее мы с вами рассматривали модули functools и itertools. И в частности для вычисления суммы последовательностей применяли функцию reduce следующим образом:

d = [5, 4, 2, 7, 4]
res = functools.reduce(lambda a, b: a + b, d)
print(res)  # 22

Здесь лямбда-функция определяет простую операцию сложения. И это не редкий случай. Вместо нее удобнее прописать встроенную функцию add() из модуля operator:

import functools
import operator as op
 
d = [5, 4, 2, 7, 4]
res = functools.reduce(op.add, d)
print(res)  # 22

Видите, это и короче и визуально воспринимается гораздо лучше. Или же нам нужно числа в последовательности d разделить по модулю 2:

res = itertools.starmap(op.mod, zip(d, itertools.repeat(2)))
print(list(res))  # [1, 0, 0, 1, 0]

Или же выделить только четные числа:

res = filter(lambda x: op.mod(x, 2) == 0, d)  # [4, 2, 4]

И так далее. Встроенные функции модуля operator в ряде случаев позволяют несколько упростить написание и понимание текста программы.

Функции доступа к элементам и атрибутам

Помимо функций, повторяющих работу стандартных операторов, модуль operator также содержит функции (точнее классы) для извлечения значений атрибутов объектов, создаваемых непосредственно в процессе работы программы.

Функция itemgetter()

Первая такая функция itemgetter() служит для удобного извлечения элементов из последовательностей, таких как списки, кортежи и словари, основываясь на указанных индексах или ключах.

Пусть в программе имеется следующий кортеж:

cities = ("Уфа", "Москва", "Рязань")

С помощью itemgetter() можно создать объект-геттер, который будет возвращать первый элемент любой индексированной упорядоченной коллекции:

get_first_elem = op.itemgetter(0)

А, затем, через get_first_elem извлекаем первый элемент:

res = get_first_elem(cities)  # "Уфа"

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

res = cities[0]  # "Уфа"

Объект get_first_elem предполагается применять, как аналог функции для выбора первого элемента. Например, ранее нам нужно было отсортировать список имен:

names = ["Антон", "Алексей", "Николай", "Никита", "Андрей", "Сергей", "Афанасий", "Степан"]

по первому символу. И для этого использовалась конструкция:

names.sort(key=lambda x: x[0])

Так вот, здесь лямбда-функцию можно заменить на get_first_elem:

names.sort(key=get_first_elem)

Результат будет тем же самым. Получаем более простую и понятную запись сортировки по первому элементу.

По аналогии itemgetter можно определить для выделения какого-либо атрибута объекта или ключа словаря. Например, возьмем список словарей:

trips = [
    {'name': 'Сергей', 'city': 'Москва', 'duration': 10},
    {'name': 'Федор', 'city': 'Сочи', 'duration': 7},
    {'name': 'Кирилл', 'city': 'Сочи', 'duration': 10},
    {'name': 'Алексей', 'city': 'Питер', 'duration': 7},
    {'name': 'Елена', 'city': 'Москва', 'duration': 10},
]

и сформируем геттер для извлечения атрибута 'duration':

get_duration = op.itemgetter('duration')
res = get_duration(trips[0])  # 10

Затем, с его помощью достаточно удобно можно выполнить сортировку этих словарей по данному ключу:

s_trips = sorted(trips, key=get_duration)

Функция attrgetter()

Следующая аналогичная функция attrgetter() позволяет извлекать значения атрибутов произвольных объектов, а не итерируемых (списки, кортежи, словари), как это делает itemgetter().

Пусть, например, имеется следующий класс:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

И создается его объект:

p = Person("Sergey", 30)

У объекта p есть атрибуты name и age. Давайте попробуем получить их значения с помощью itemgetter и attrgetter:

get_age = op.itemgetter('age')
get_name = op.attrgetter('name')
 
print(get_age(p))  # TypeError
print(get_name(p))  # "Sergey"

Смотрите, вызов через itemgetter() приводит к ошибке, тогда как вызов через attrgetter() выдает значение атрибута.

Другой пример. Пусть у нас имеется словарь:

d = {'name': 'Сергей', 'city': 'Москва', 'age': 10}

и мы попробуем извлечь значения ключей name и age с помощью созданных геттеров. Получим:

print(get_age(d))  # 10
print(get_name(d))  # AttributeError

То есть обращение к ключам словаря через attrgetter() приводит к ошибке, так как атрибут name у словаря d отсутствует.

Учитывая, что методы классов также являются атрибутами, и к ним можно обратиться по имени метода, то вполне допустима следующая программа:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def show_obj(self):
        print(f"{self.name}: {self.age}")
 
 
p = Person("Sergey", 30)
 
get_method = op.attrgetter('show_obj')
method = get_method(p)
method() # Sergey: 30

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме