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