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

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

range а также len внутри для петель

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

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

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

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

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

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

Более Pythonic способ написания цикла в примере может выглядеть так:

for player in players:
  print(player)

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

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

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

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

В курсе 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 value, чтобы они могли распечатать всех выигравших игроков и их выигрыши. Вот как они это сделали:

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 list в том же цикле, поэтому, если мы сгенерируем список индексов, мы можем полагаться на стабильный порядок списков для доступа к правильным значениям в обеих коллекциях.

Но опять же, нам не нужно ничего этого делать. Вместо этого мы можем использовать 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):

Вывод

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

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *