Очень распространенная вещь, которую я вижу среди моих новых учеников 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):
Вывод
Я надеюсь, что вы узнали что-то новое и вдохновились попробовать методы, которые я продемонстрировал здесь. Освоение этих инструментов действительно жизненно важно для написания потрясающих питонических циклов, но эти инструменты также могут быть использованы в других местах, чтобы сделать ваш код более явным и читаемым.
В следующий раз, когда вы будете использовать индекс в цикле, просто подумайте: “Есть ли лучший способ сделать это?” Чаще всего ответ звучит так: “Да, абсолютно.”