Рубрики
Без рубрики

Прекратите использовать индексы!

Узнайте, как избежать индексов в циклах и заменить их описательными именами переменных. Используйте такие инструменты, как zip и enumerate, чтобы писать потрясающие питонические циклы.

Автор оригинала: Phil Best.

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

диапазон и лен внутри для петель

Кто из вас писал подобный код раньше?

players = ["John", "Anne", "Hannah"]

for i in range(len(player)):
  print(players[i])

В этом есть смысл, верно? Мы выясняем длину списка player , создаем объект range , содержащий коллекцию индексов, а затем перебираем эту коллекцию. Затем мы получаем доступ к именам в players , используя эти индексы.

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

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

Более питонический способ написания цикла в этом примере может выглядеть следующим образом:

for player in players:
  print(player)

Здесь вместо i у нас есть хорошее описательное имя переменной , а код в целом намного короче и менее сложен.

Просто для полноты понимания Python работает точно так же, поэтому все, что упоминается в этом посте, также применимо к этим структурам.

Однако это довольно простой пример, поэтому давайте перейдем к чему-то более сложному.

Работа с несколькими коллекциями

В курсе Python, для которого я помогаю наставнику, у нас есть упражнение, в котором студенты кодируют своего рода лотерею.

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

В рамках их решения у студента был список словарей, в которых содержались все имена игроков и их лотерейные номера, которые выглядели следующим образом:

players = [
    {'name': 'Rolf', 'numbers': {1, 3, 5, 7, 9, 11}},
    {'name': 'Charlie', 'numbers': {2, 7, 9, 22, 10, 5}},
    {'name': 'Anna', 'numbers': {13, 14, 15, 16, 17, 18}},
    {'name': 'Jen', 'numbers': {19, 20, 12, 7, 3, 5}}
]

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

Затем они выяснили , какое наибольшее число совпадающих чисел использовало max , и сохранили результат в max_correct .

Пока все идет хорошо.

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

for i in range(len(players)):
    if count_correct_list[i] == max_correct:
        print(f"{players[i]['name']} won {100 ** max_correct}")

Опять же, это имеет большой смысл. Нам нужно получить доступ как к списку count_correct_list , так и к списку players в одном цикле, поэтому, если мы создадим список индексов, мы можем полагаться на стабильное упорядочение списков для доступа к правильным значениям в обеих коллекциях.

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

Итак, вместо того чтобы делать всю эту работу с индексами, давайте создадим объект zip , где мы объединим две коллекции, к которым мы пытаемся получить доступ:

for player, amount_correct in zip(players, count_correct_list):
    if amount_correct == max_correct:
        print(f"{player['name']} won {100 ** amount_correct}.")

Как вы можете видеть выше, мы можем деструктурировать кортежи zip-объектов в переменные цикла, и теперь мы можем получить доступ к значениям из обеих коллекций, используя хорошие описательные имена. Намного лучше, чем count_correct_list[i] .

Если вы не используете zip , вам нужно начать использовать zip .

Использование enumerate

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

Лучший способ сделать это-использовать функцию enumerate . enumerate возвращает объект enumerate , содержащий ряд кортежей. Первое значение в каждом кортеже-это счетчик, который по умолчанию начинается с 0 . Второй элемент в кортеже-это некоторое значение из итеративного объекта, которое мы передаем функции.

Давайте рассмотрим пример, где мы печатаем номер рядом с именем игрока.

players = ["John", "Anne", "Hannah"]

for counter, player in enumerate(players):
  print(f"{counter}: {player}")

Результат будет выглядеть следующим образом:

0: John
1: Anne
2: Hannah

Если бы мы хотели, чтобы счетчик начинался с 1 вместо этого 0 , мы могли бы указать значение для параметра start следующим образом:

for counter, player in enumerate(players, start=1):

Вывод

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

В следующий раз, когда вы будете использовать индекс в цикле, просто подумайте: “Есть ли лучший способ сделать это?” Чаще всего ответ звучит так: “Да, абсолютно.”