Работа с модулем оператора Python

Оглавление

Всякий раз, когда вы выполняете вычисления в Python, вы используете встроенные операторы, такие как +, %, и **. Знаете ли вы, что Python также предоставляет модуль operator? Хотя может показаться, что цель operator - предоставить альтернативу этим существующим операторам Python, на самом деле модуль имеет гораздо более специализированное назначение, чем это.

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

В этом руководстве вы узнаете, как:

  • Используйте любую из основных функций, эквивалентных оператору
  • Передавать operator функций в качестве аргументов
  • Сериализовать operator функции для последующего использования
  • Оценка operator функция производительность по сравнению с обычными альтернативами
  • Используйте более высокий порядок operator функции в ряде интересных примеров

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

Обладая этими знаниями, вы готовы погрузиться в работу и научиться использовать модуль, который часто неправильно понимают operator, в своих интересах.

Получите свой код: Нажмите здесь, чтобы загрузить бесплатный пример кода, который operator показывает, как использовать модуль <<<5> Python>>.

Использование основных функций модуля Python operator

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

Изучение принципов работы основных функций

Модуль Python operator содержит более сорока функций, многие из которых эквивалентны операторам Python, с которыми вы уже знакомы. Вот пример:

>>> import operator

>>> operator.add(5, 3)  # 5 + 3
8

>>> operator.__add__(5, 3)  # 5 + 3
8


Здесь вы добавляете 5 и 3 вместе, используя как add(), так и __add__(). Оба результата одинаковы. На первый взгляд, эти функции предоставляют вам ту же функциональность, что и оператор Python +, но их назначение не в этом.

Примечание: Большинство функций модуля operator содержат два названия: версия с расширением и версия без расширения. В предыдущем примере operator.__add__(5, 3) - это более простая версия, поскольку в ней присутствуют двойные подчеркивания. С этого момента вы будете использовать только версии без подчеркивания, такие как operator.add(5, 3). Версии dunder предназначены для обратной совместимости с версией Python 2 для operator.

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

>>> operator.truediv(5, 2)  # 5 / 2
2.5

>>> operator.ge(5, 2)  # 5 >= 2
True

>>> operator.is_("X", "Y")  # "X" is "Y"
False

>>> operator.not_(5 < 3)  # not 5 < 3
True

>>> bin(operator.and_(0b101, 0b110))  # 0b101 & 0b110
'0b100'


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

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

Передача операторов в качестве аргументов в Функции Более Высокого порядка

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

>>> def perform_operation(operator_string, operand1, operand2):
...     if operator_string == "+":
...         return operand1 + operand2
...     elif operator_string == "-":
...         return operand1 - operand2
...     elif operator_string == "*":
...         return operand1 * operand2
...     elif operator_string == "/":
...         return operand1 / operand2
...     else:
...         return "Invalid operator."
...


В вашей функции perform_operation() первым параметром является строка, представляющая одну из четырех основных арифметических операций. Чтобы протестировать функцию, вы вводите каждый из четырех операторов. Результаты будут такими, как вы и ожидали:

>>> number1 = 10
>>> number2 = 5
>>> calculations = ["+", "-", "*", "/"]

>>> for op_string in calculations:
...     perform_operation(op_string, number1, number2)
...
15
5
50
2.0


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

Именно здесь вы можете эффективно использовать функции operator. Передача их в функцию дает вам несколько преимуществ:

>>> def perform_operation(operator_function, operand1, operand2):
...     return operator_function(operand1, operand2)
...


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

Пересмотренный тестовый код аналогичен тому, что вы делали ранее, за исключением того, что вы передаете operator функции для вашей perform_operation() функции для использования:

>>> from operator import add, sub, mul, truediv

>>> number1 = 10
>>> number2 = 5
>>> calculations = [add, sub, mul, truediv]

>>> for op_function in calculations:
...     perform_operation(op_function, number1, number2)
...
15
5
50
2.0


На этот раз ваш список calculations содержит ссылки на сами функции. Обратите внимание, что вы передаете в функции имена, а не вызовы функции . Другими словами, вы передаете add в perform_operation(), а не add(). Вы передаете объект функции, а не результат ее выполнения. Помните, что имя функции на самом деле является ссылкой на ее код. Функция вызывается с использованием синтаксиса ().

У использования обновленной версии perform_operation() есть два преимущества. Первое - это возможность расширения. Вы можете использовать обновленный код с любыми другими функциями operator, для которых требуется ровно два аргумента. Действительно, вы могли бы поэкспериментировать, передав функции модуля operator mod(), pow(), и repeat() в обе версии вашей функции. Ваша обновленная версия работает должным образом, в то время как исходная версия возвращается "Invalid operator".

Вторым преимуществом является удобство чтения. Взгляните на обе версии вашей функции perform_operation(), и вы заметите, что ваша вторая версия не только значительно короче, но и более удобочитаема, чем оригинальная.

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

Сериализация operator Функций модуля

Одним из способов сохранения объектов, включая функции, на диске является их сериализация. Другими словами, ваш код преобразует их в потоки байтов и сохраняет на диске для последующего использования. И наоборот, когда вы считываете сериализованные объекты обратно с диска, вы десериализуете их, позволяя считывать их с диска в программу для использования.

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

Распространенным способом сериализации функций в Python является использование модуля pickle. Это, наряду с его словарной оболочкой shelve,, обеспечивает один из наиболее эффективных способов хранения данных. Однако, когда вы сериализуете функцию, используя pickle, вы сериализуете только ее полное полное имя, а не код в теле функции. Когда вы десериализуете функцию, среда должна предоставить доступ к коду функции. В противном случае функция не сможет работать.

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

>>> import operator
>>> operators = {
...     "+": operator.add,
...     "-": operator.sub,
...     "*": operator.mul,
...     "/": operator.truediv,
... }

>>> def perform_operation(op_string, number1, number2):
...     return operators[op_string](number1, number2)
...

>>> perform_operation("-", 10, 5)
5


Операции, поддерживаемые perform_operation(), определены в operators. В качестве примера, вы запускаете операцию "-", которая вызывает operator.sub() в фоновом режиме.

Одним из способов совместного использования поддерживаемых операторов между процессами является сериализация словаря operators на диск. Это можно сделать с помощью pickle следующим образом:

>>> import pickle
>>> with open("operators.pkl", mode="wb") as f:
...     pickle.dump(operators, f)
...


Вы открываете двоичный файл для записи. Чтобы сериализовать operators, вы вызываете pickle.dump() и передаете структуру, которую вы сериализуете, и дескриптор целевого файла.

При этом в вашем локальном рабочем каталоге будет создан файл operators.pkl. Чтобы продемонстрировать, как повторно использовать operators в другом процессе, перезапустите оболочку Python и загрузите выбранный файл:

>>> import pickle
>>> with open("operators.pkl", mode="rb") as f:
...     operators = pickle.load(f)
...
>>> operators
{'+': <built-in function add>, '-': <built-in function sub>,
 '*': <built-in function mul>, '/': <built-in function truediv>}


Сначала вы снова импортируете pickle и снова открываете двоичный файл для чтения. Чтобы прочитать структуру operator, вы используете pickle.load() и передаете дескриптор файла. Затем ваш код считывает сохраненное определение и присваивает его переменной с именем operators. Это имя не обязательно должно совпадать с вашим исходным именем. Эта переменная указывает на словарь, который ссылается на различные функции, при условии, что они доступны.

Обратите внимание, что вам не нужно явно импортировать operator, хотя модуль должен быть доступен для импорта Python в фоновом режиме.

Вы можете определить perform_operation() еще раз, чтобы убедиться, что он может ссылаться на восстановленный файл и использовать его operators:

>>> def perform_operation(op_string, number1, number2):
...     return operators[op_string](number1, number2)
...

>>> perform_operation("*", 10, 5)
50


Отлично! Ваш код обрабатывает умножение так, как вы и ожидали.

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

Однако вы не можете сериализовать анонимные лямбда-функции подобным образом. Если бы вы реализовали этот пример без использования модуля operator, то вы, вероятно, определили бы словарь следующим образом:

>>> operators = {
...     "+": lambda a, b: a + b,
...     "-": lambda a, b: a - b,
...     "*": lambda a, b: a * b,
...     "/": lambda a, b: a / b,
... }


Конструкция lambda - это быстрый способ определения простых функций, и они могут быть весьма полезны. Однако, поскольку pickle не сериализует тело функции, а только имя функции, вы не можете сериализовать безымянные лямбда-функции:

>>> import pickle
>>> with open("operators.pkl", mode="wb") as f:
...     pickle.dump(operators, f)
...
Traceback (most recent call last):
  ...
PicklingError: Can't pickle <function <lambda> at 0x7f5b946cfba0>: ...


Если вы попытаетесь сериализовать лямбда-функции с помощью pickle, то получите сообщение об ошибке. Это тот случай, когда вы часто можете использовать функции operator вместо лямбда-функций.

Оглянитесь на свой код сериализации и обратите внимание, что вы импортировали как operator, так и pickle, в то время как ваш код десериализации импортировал только pickle. Вам не нужно было импортировать operator, потому что pickle сделал это автоматически за вас, когда вы вызвали его функцию load(). Это работает, потому что встроенный модуль operator легко доступен.

Исследование эффективности operator Функций в сравнении с альтернативными вариантами

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

Если вы хотите синхронизировать функции модуля operator с их встроенными или лямбда-эквивалентами, то вы можете использовать timeit модуль. Лучший способ сделать это - запустить его непосредственно из командной строки:

PS> python -m timeit "(lambda a, b: a + b)(10, 10)"
5000000 loops, best of 5: 82.3 nsec per loop
PS> python -m timeit -s "from operator import add" "add(10, 10)"
10000000 loops, best of 5: 24.5 nsec per loop
PS> python -m timeit "10 + 10"
50000000 loops, best of 5: 5.19 nsec per loop

PS> python -m timeit "(lambda a, b: a ** b)(10, 10)"
1000000 loops, best of 5: 226 nsec per loop
PS> python -m timeit -s "from operator import pow" "pow(10, 10)"
2000000 loops, best of 5: 170 nsec per loop
PS> python -m timeit "10 ** 10"
50000000 loops, best of 5: 5.18 nsec per loop


В приведенном выше сеансе PowerShell используется модуль timeit для сравнения производительности различных реализаций сложения и возведения в степень. Ваши результаты показывают, что для обеих операций встроенный оператор работает быстрее, а модульная функция operator превосходит только лямбда-функцию. Сами значения фактического времени зависят от машины, но их относительные различия значительны.

Примечание: Модуль Python timeit позволяет вам определять время выполнения небольших фрагментов вашего кода. Обычно вы вызываете timeit из командной строки, используя python -m timeit, за которым следует строка, содержащая команду, которую вы хотите измерить. Вы используете переключатель -s, чтобы указать код, который вы хотите запустить один раз непосредственно за до начала отсчета времени. В приведенном выше примере вы использовали -s для импорта pow() и add() из модуля operator перед синхронизацией вашего кода.

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

Теперь вы знакомы с функциями, эквивалентными операторам, из модуля operator, но, возможно, вам захочется потратить некоторое время на изучение остальных этих функций. Как только вы будете готовы двигаться дальше, продолжайте читать, чтобы изучить некоторые другие способы использования operator.

Использование функций более высокого порядка модуля Python operator

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

Выбор Значений Из Многомерных Коллекций С Помощью itemgetter()

Первая функция, о которой вы узнаете, - это operator.itemgetter(). В ее базовой форме вы передаете ей единственный параметр, который представляет собой индекс. Затем itemgetter() возвращает функцию, которая при передаче коллекции возвращает элемент с этим индексом.

Для начала вы создаете список из словарей:

>>> musician_dicts = [
...     {"id": 1, "fname": "Brian", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 3, "fname": "Dennis", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
...     {"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"},
...     {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"},
...     {"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"},
... ]


В каждом словаре содержится запись о музыканте, принадлежащем к одной из двух групп: Beach Boys или Shadows. Чтобы узнать, как работает itemgetter(), предположим, вы хотите выбрать один элемент из musician_dicts:

>>> import operator

>>> get_element_four = operator.itemgetter(4)
>>> get_element_four(musician_dicts)
{"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"}


Когда вы передаете itemgetter() индекс 4, он возвращает функцию, на которую ссылается get_element_four, которая возвращает элемент в позиции индекса 4 в коллекции. Другими словами, get_element_four(musician_dicts) возвращает musician_dicts[4]. Помните, что элементы списка индексируются, начиная с 0, а не с 1. Это означает, что itemgetter(4) фактически возвращает пятый элемент в списке.

Далее предположим, что вы хотите выбрать элементы из позиций 1, 3, и 5. Для этого вы передаете itemgetter() несколько значений индекса:

>>> get_elements_one_three_five = operator.itemgetter(1, 3, 5)
>>> get_elements_one_three_five(musician_dicts)
({"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
 {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
 {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"})


Здесь itemgetter() создает функцию, которую вы используете для поиска всех трех элементов. Ваша функция возвращает кортеж, содержащий результаты.

Теперь предположим, что вы хотите вывести только значения имени и фамилии из словарей в позициях индекса 1, 3, и 5. Для этого вы передаете itemgetter() ключи "fname" и "lname":

>>> get_names = operator.itemgetter("fname", "lname")

>>> for musician in get_elements_one_three_five(musician_dicts):
...     print(get_names(musician))
...
("Carl", "Wilson")
("Bruce", "Johnston")
("Bruce", "Welch")


На этот раз itemgetter() предоставляет функцию для получения значений, связанных с ключами "fname" и "lname". Ваш код выполняет итерацию по набору словарей, возвращаемых get_elements_one_three_five(), и передает каждый из них вашей функции get_names(). Каждый вызов get_names() возвращает кортеж, содержащий значения, связанные с ключами словаря "fname" и "lname" словарей в позициях 1, 3, и 5 из musician_dicts.

Две функции Python, о которых вы, возможно, уже знаете, это min() и max(). Вы можете использовать их для поиска самого низкого и самого высокого элементов в списке:

>>> prices = [100, 45, 345, 639]
>>> min(prices)
45
>>> max(prices)
639


В приведенном выше коде вы получили наименьшее и наибольшее значения: min() возвращает самый дешевый товар, в то время как max() возвращает самый дорогой.

Функции min() и max() содержат параметр key, который принимает функцию. Если вы создадите функцию с помощью itemgetter(), то вы можете использовать ее для указания min() и max() анализировать определенные элементы в списке списков или словарях. Чтобы изучить это, вы сначала создаете список списков музыкантов:

>>> musician_lists = [
...     [1, "Brian", "Wilson", "Beach Boys"],
...     [2, "Carl", "Wilson", "Beach Boys"],
...     [3, "Dennis", "Wilson", "Beach Boys"],
...     [4, "Bruce", "Johnston", "Beach Boys"],
...     [5, "Hank", "Marvin", "Shadows"],
...     [6, "Bruce", "Welch", "Shadows"],
...     [7, "Brian", "Bennett", "Shadows"],
... ]


Содержимое musician_lists идентично musician_dicts, за исключением того, что каждая запись находится в списке. На этот раз предположим, что вы хотите найти элементы списка с наименьшим и наибольшим значениями id:

>>> get_id = operator.itemgetter(0)

>>> min(musician_lists, key=get_id)
[1, "Brian", "Wilson", "Beach Boys"]
>>> max(musician_lists, key=get_id)
[7, "Brian", "Bennett", "Shadows"]


Сначала вы создаете функцию, используя itemgetter(), чтобы выбрать первый элемент из списка. Затем вы передаете это как key параметр для min() и max(). Функции min() и max() вернут вам списки с наименьшими и наибольшими значениями в их индексе 0 позиций соответственно.

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

>>> get_lname = operator.itemgetter("lname")

>>> min(musician_dicts, key=get_lname)
{"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"}


На этот раз вы настраиваете функцию itemgetter(), которая выбирает ключ словаря "lname". Затем вы передаете это значение в качестве параметра функции min() key. Функция min() возвращает словарь с наименьшим значением "lname". Результатом является запись "Bennett". Почему бы не повторить это с помощью max()? Попробуйте предсказать, что произойдет, прежде чем запускать свой код для проверки.

Сортировка многомерных Коллекций С помощью itemgetter()

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

>>> star_wars_movies_release_order = [4, 5, 6, 1, 2, 3, 7, 8, 9]
>>> sorted(star_wars_movies_release_order)
[1, 2, 3, 4, 5, 6, 7, 8, 9]


Теперь элементы нового списка расположены в порядке возрастания. Если вы хотите отсортировать список на месте, то вместо этого вы можете использовать метод .sort(). Возможно, вам захочется попробовать это в качестве упражнения.

Также можно использовать itemgetter() для сортировки списков. Это позволяет сортировать многомерные списки по определенным элементам. Чтобы сделать это, вы передаете функцию itemgetter() в параметр sorted() функции key.

Подумайте еще раз о своем musician_lists:

>>> musician_lists = [
...     [1, "Brian", "Wilson", "Beach Boys"],
...     [2, "Carl", "Wilson", "Beach Boys"],
...     [3, "Dennis", "Wilson", "Beach Boys"],
...     [4, "Bruce", "Johnston", "Beach Boys"],
...     [5, "Hank", "Marvin", "Shadows"],
...     [6, "Bruce", "Welch", "Shadows"],
...     [7, "Brian", "Bennett", "Shadows"],
... ]


Теперь вы отсортируете этот список, используя itemgetter(). Для начала вы решите отсортировать элементы списка в порядке убывания по значению id:

>>> import operator

>>> get_id = operator.itemgetter(0)
>>> sorted(musician_lists, key=get_id, reverse=True)
[[7, "Brian", "Bennett", "Shadows"],
 [6, "Bruce", "Welch", "Shadows"],
 [5, "Hank", "Marvin", "Shadows"],
 [4, "Bruce", "Johnston", "Beach Boys"],
 [3, "Dennis", "Wilson", "Beach Boys"],
 [2, "Carl", "Wilson", "Beach Boys"],
 [1, "Brian", "Wilson", "Beach Boys"]
 ]


Чтобы сделать это, вы используете itemgetter() для создания функции, которая будет выбирать позицию индекса 0, позицию музыканта id. Затем вы вызываете sorted() и передаете ему musician_lists плюс ссылку на функцию из itemgetter() в качестве ее key. Чтобы обеспечить сортировку по убыванию, вы задаете reverse=True. Теперь ваш список будет отсортирован в порядке убывания по id.

Также возможно выполнить более сложную сортировку многомерных списков. Предположим, вы хотите отсортировать каждый список в обратном порядке в пределах musician_lists. Сначала выполняется сортировка по фамилии, затем все списки с одинаковой фамилией сортируются по имени. Другими словами, вы выполняете сортировку по имени внутри фамилии:

>>> get_elements_two_one = operator.itemgetter(2, 1)
>>> sorted(musician_lists, key=get_elements_two_one, reverse=True)
[[3, "Dennis", "Wilson", "Beach Boys"],
 [2, "Carl", "Wilson", "Beach Boys"],
 [1, "Brian", "Wilson", "Beach Boys"],
 [6, "Bruce", "Welch", "Shadows"],
 [5, "Hank", "Marvin", "Shadows"],
 [4, "Bruce", "Johnston", "Beach Boys"],
 [7, "Brian", "Bennett", "Shadows"]]


На этот раз вы передаете itemgetter() два аргумента, позиции 2 и 1. Ваш список отсортирован в обратном алфавитном порядке, сначала по фамилии (2), затем по имени (1), где это применимо. Другими словами, сначала отображаются три записи Wilson, а затем Dennis, Carl, и Brian в порядке убывания. Не стесняйтесь повторно запустить этот код и использовать его для выбора других полей. Попробуйте предсказать, что произойдет, прежде чем запускать свой код, чтобы проверить свое понимание.

Те же принципы применимы и к словарям, при условии, что вы указываете ключи, значения которых хотите отсортировать. Опять же, вы можете использовать musician_dicts список словарей:

>>> musician_dicts = [
...     {"id": 1, "fname": "Brian", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 3, "fname": "Dennis", "lname": "Wilson", "group": "Beach Boys"},
...     {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
...     {"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"},
...     {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"},
...     {"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"},
... ]

>>> get_names = operator.itemgetter("lname", "fname")
>>> sorted(musician_dicts, key=get_names, reverse=True)
[{"id": 3, "fname": "Dennis", "lname": "Wilson", "group": "Beach Boys"},
 {"id": 2, "fname": "Carl", "lname": "Wilson", "group": "Beach Boys"},
 {"id": 1, "fname": "Brian", "lname": "Wilson", "group": "Beach Boys"},
 {"id": 6, "fname": "Bruce", "lname": "Welch", "group": "Shadows"},
 {"id": 5, "fname": "Hank", "lname": "Marvin", "group": "Shadows"},
 {"id": 4, "fname": "Bruce", "lname": "Johnston", "group": "Beach Boys"},
 {"id": 7, "fname": "Brian", "lname": "Bennett", "group": "Shadows"}
 ]


На этот раз вы передаете itemgetter() ключи "fname" и "lname". Результат аналогичен тому, что вы получали ранее, за исключением того, что теперь он содержит словари. В то время как в предыдущем примере вы создали функцию, которая выбирала элементы индекса 2 и 1, на этот раз ваша функция выбирает ключи словаря "lname" и "fname".

Получение атрибутов из Объектов с помощью attrgetter()

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

Чтобы понять, как работает attrgetter(), вам сначала нужно создать новый класс :

>>> from dataclasses import dataclass

>>> @dataclass
... class Musician:
...     id: int
...     fname: str
...     lname: str
...     group: str
...


Вы создали класс данных с именем Musician. Основное назначение вашего класса данных - хранить данные о различных объектах musician, хотя, как вы узнаете позже, он также может содержать методы. Программа @dataclass decorator позволяет вам напрямую определять атрибуты, указывая их имена и подсказку по типу для их типов данных. Ваш класс содержит четыре признака, которые описывают музыканта.

Примечание: Возможно, вам интересно, куда делся .__init__(). Одно из преимуществ использования класса данных заключается в том, что больше нет необходимости в явном инициализаторе. Чтобы создать объект, вы передаете значения для каждого из атрибутов класса. В приведенном выше классе Musician первому атрибуту присваивается значение .id, второму - значение .fname и так далее.

Далее вам понадобится список объектов для работы. Вы повторно используете musician_lists из предыдущего списка и используете его для создания списка объектов с именами group_members:

>>> musician_lists = [
...     [1, "Brian", "Wilson", "Beach Boys"],
...     [2, "Carl", "Wilson", "Beach Boys"],
...     [3, "Dennis", "Wilson", "Beach Boys"],
...     [4, "Bruce", "Johnston", "Beach Boys"],
...     [5, "Hank", "Marvin", "Shadows"],
...     [6, "Bruce", "Welch", "Shadows"],
...     [7, "Brian", "Bennett", "Shadows"],
... ]
>>> group_members = [Musician(*musician) for musician in musician_lists]


Вы заполняете group_members объектами Musician, преобразуя musician_lists с помощью списка..

Примечание: Возможно, вы заметили, что использовали *musician для передачи каждого списка при создании объектов класса. Звездочка указывает Python распаковать список на отдельные элементы при создании объектов. Другими словами, первый объект будет иметь атрибут .id, равный 1, атрибут .fname, равный "Brian", и так далее.

Теперь у вас есть group_members список, содержащий семь Musician объектов, четыре из которых принадлежат the Beach Boys и три из the Shadows. Далее вы узнаете, как вы можете использовать их с attrgetter().

Предположим, вы хотите получить атрибут .fname из каждого элемента group_members:

>>> import operator

>>> get_fname = operator.attrgetter("fname")

>>> for person in group_members:
...     print(get_fname(person))
...
Brian
Carl
Dennis
Bruce
Hank
Bruce
Brian


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

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

>>> get_id_lname = operator.attrgetter("id", "lname")

>>> for person in group_members:
...     print(get_id_lname(person))
...
(1, "Wilson")
(2, "Wilson")
(3, "Wilson")
(4, "Johnston")
(5, "Marvin")
(6, "Welch")
(7, "Bennett")


На этот раз, когда вы вызываете attrgetter() и запрашиваете оба атрибута .id и .lname, вы создаете функцию, способную считывать оба атрибута для любого объекта. При запуске ваш код возвращает как .id, так и .lname из списка переданных ему объектов Musician. Конечно, вы можете применить эту функцию к любому объекту, будь то встроенному или пользовательскому, при условии, что объект содержит как атрибут .id, так и атрибут .lname.

Сортировка и поиск в списках объектов по атрибуту с помощью attrgetter()

Функция attrgetter() также позволяет сортировать коллекцию объектов по их атрибутам. Вы можете попробовать это, отсортировав Musician объектов в group_members по .id для каждого объекта в обратном порядке.

Во-первых, убедитесь, что у вас есть доступ к group_members, как указано в предыдущем разделе руководства. Затем вы можете использовать возможности attrgetter() для выполнения пользовательской сортировки:

>>> get_id = operator.attrgetter("id")
>>> for musician in sorted(group_members, key=get_id, reverse=True):
...     print(musician)
...
Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')
Musician(id=6, fname='Bruce', lname='Welch', group='Shadows')
Musician(id=5, fname='Hank', lname='Marvin', group='Shadows')
Musician(id=4, fname='Bruce', lname='Johnston', group='Beach Boys')
Musician(id=3, fname='Dennis', lname='Wilson', group='Beach Boys')
Musician(id=2, fname='Carl', lname='Wilson', group='Beach Boys')
Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')


В этом фрагменте кода вы настраиваете функцию attrgetter(), которая может возвращать атрибут .id. Чтобы отсортировать список в обратном порядке по .id, вы присваиваете ссылку get_id параметру sorted() метода key и устанавливаете значение reverse=True. Когда вы печатаете объекты Musician, вывод показывает, что ваша сортировка действительно сработала.

Если вы хотите отобразить объект с наибольшим или наименьшим значением .id, то вы используете функцию min() или max() и передаете ей ссылку на ваш get_id() функционировать в качестве своего key:

>>> min(group_members, key=get_id)
Musician(id=1, fname='Brian', lname='Wilson', group='Beach Boys')
>>> max(group_members, key=get_id)
Musician(id=7, fname='Brian', lname='Bennett', group='Shadows')


Сначала вы создаете функцию attrgetter(), которая может найти атрибут .id. Затем вы передаете его в функции min() и max(). В этом случае ваш код возвращает объекты, содержащие наименьшее и наибольшее значения атрибутов .id. В данном случае это объекты с .id значениями 1 и 7. Возможно, вы захотите поэкспериментировать с этим дальше, выполнив сортировку по другим атрибутам.

Вызов методов для объектов с methodcaller()

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

Чтобы узнать о methodcaller(), вам сначала нужно расширить существующий класс данных Musician с помощью метода:

>>> from dataclasses import dataclass

>>> @dataclass
... class Musician:
...     id: int
...     fname: str
...     lname: str
...     group: str
...
...     def get_full_name(self, last_name_first=False):
...         if last_name_first:
...             return f"{self.lname}, {self.fname}"
...         return f"{self.fname} {self.lname}"
...


Вы добавляете метод .get_full_name() в Musician, который принимает единственный параметр с именем last_name_first и значением по умолчанию False. Это позволяет вам указать порядок, в котором возвращаются имена.

предположим, вы хотите вызвать .get_full_name() на каждый объект в предварительно определенной group_members список:

>>> import operator
>>> first_last = operator.methodcaller("get_full_name")
>>> for person in group_members:
...     print(first_last(person))
...
Brian Wilson
Carl Wilson
Dennis Wilson
Bruce Johnston
Hank Marvin
Bruce Welch


Здесь вы используете methodcaller() для создания функции с именем first_last(), которая будет вызывать метод .get_full_name() любого объекта, который вы ей передадите. Обратите внимание, что вы не передаете никаких дополнительных аргументов в first_last(), поэтому вы получаете обратно список имен, за которыми следуют фамилии всех объектов Musician.

Если вы хотите, чтобы имена следовали за фамилиями, то вы можете ввести значение True для last_name_first:

>>> last_first = operator.methodcaller("get_full_name", True)
>>> for person in group_members:
...     print(last_first(person))
...
Wilson, Brian
Wilson, Carl
Wilson, Dennis
Johnston, Bruce
Marvin, Hank
Welch, Bruce
Bennett, Brian


На этот раз вы используете methodcaller() для создания функции с именем last_first(), которая будет вызывать .get_full_name() метод любого переданного ей объекта, но также будет передавать True к параметру last_name_first. Теперь вы получите список фамилий, а затем имена всех объектов Musician.

Точно так же, как при использовании attrgetter() для получения атрибутов, объекты, передаваемые в methodcaller(), могут быть как встроенными, так и пользовательскими. Они должны содержать только метод, который вы хотите вызвать.

Часто задаваемые вопросы

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

Вы можете использовать эти вопросы, чтобы проверить свое понимание или обобщить и закрепить то, что вы только что узнали. После каждого вопроса вы найдете краткое объяснение, спрятанное в разборном разделе. Нажмите на переключатель Показать/скрыть, чтобы открыть ответ. Пора переходить к делу!

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

Модуль operator также предоставляет функции более высокого порядка attrgetter(), itemgetter() и methodcaller(). Вы можете передать функции, которые возвращают эти три параметра, многим обычным функциям, включая sorted(), max(), и min().

В отличие от встроенных операторов, вы можете сериализовать функцию модуля operator, что замечательно, поскольку pickle модуль является одним из наиболее эффективных способов сохранения данных. Недостатком является то, что он обычно не справляется с функциями. Однако вы можете использовать его с operator функциями модуля.

Встроенные операторы Python будут превосходить как версии модуля operator, так и лямбда-выражения, в то время как функции operator будут превосходить лямбда-выражения. Если вы сделаете один вызов лямбда-кода или эквивалентного модуля operator, то вы не заметите большой разницы. Однако при повторных вызовах разница становится существенной. Функции модуля operator не заменяют встроенных операторов, когда это все, что вам нужно.

Использование функций operator может сделать ваш код более читабельным, чем иногда приводящие в замешательство эквиваленты лямбда-функций.

У вас есть интересный пример использования модуля operator? Если у вас есть продуманный вариант использования, почему бы не поделиться им с коллегами-программистами? Не стесняйтесь добавить его в качестве комментария ниже.

Получите свой код: Нажмите здесь, чтобы загрузить бесплатный пример кода, который operator показывает, как использовать модуль <<<5> Python>>.

<статус завершения article-slug="python-operator-module" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-operator-module/bookmark/" данные-api-article-завершение-status-url="/api/версия 1/статьи/python-оператор-модуль/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0A Работа с модулем Python operator" email-subject="Статья о Python для вас" twitter-text="Интересная статья о Python от @realpython:" url="https://realpython.com/python-operator-module /" url-title="Работа с модулем Python operator"> Back to Top