Текст лекции: Щуров И.В., НИУ ВШЭ
Данный notebook является конспектом лекции по курсу «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ, 2015-16). Он распространяется на условиях лицензии Creative Commons Attribution-Share Alike 4.0. При использовании обязательно упоминание автора курса и аффилиации. При наличии технической возможности необходимо также указать активную гиперссылку на страницу курса. Фрагменты кода, включенные в этот notebook, публикуются как общественное достояние.
Другие материалы курса, включая конспекты и видеозаписи лекций, а также наборы задач, можно найти на странице курса.
s = "hello world this is a\ntest"
print(s)
Если мы хотим работать с отдельными словами, входящими в эту строку, то её нужно разделить на отдельные слова — то есть получить список, состоящий из слов, которые входят в эту строку. Для этого можно использовать метод split()
.
s.split()
['hello', 'world', 'this', 'is', 'a', 'test']
Итак, мы получили ровно то, что хотели. Заметим, что в качестве разделителя в этом случае использовались пробелы (один или несколько), а также любые пробельные символы, в число которых входит символ табуляции (у нас его не было) и символ перевода строки \n
. Разделители в элементы получающегося списка не попадают.
Важно! Метод split()
не меняет строку (вообще говоря строку в принципе нельзя поменять):
s = "hello world this is a\ntest"
s.split()
print(s)
hello world this is a test
В коде выше вторая строка не приводит ни к какому эффекту — вы создали список и мгновенно его забыли, а строка осталась неизменной. Если вы хотите что-то дальше делать со списком, полученным в результате применения split()
, а не просто на него полюбоваться и забыть навсегда, то нужно, например, сохранить его в виде какой-то переменной:
words = s.split()
# теперь результат выполнения s.split() сохранён в переменную words
for word in words:
print(word)
hello world this is a test
У метода split()
есть необязательный параметр — разделитель. Если он указан, то строчка разбивается по тому разделителю, который ему передан.
s.split(" ")
['hello world', 'this is', 'a\ntest']
Мы передали в качестве разделителя четыре пробела и теперь строка разделяется по ним, а один пробел или символ перевода строки не воспринимается как разделитель.
Допустим, мы хотим ввести несколько чисел через запятую и вывести каждое из них, увеличенное на 1. Можно попробовать такой код:
s = input("Введите несколько чисел, разделённых запятыми: ")
elements = s.split(",")
for n in elements:
print(n+1)
Введите несколько чисел, разделённых запятыми: 2, 5, 10
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-111-b3a06a7daa1d> in <module>() 2 elements = s.split(",") 3 for n in elements: ----> 4 print(n+1) TypeError: Can't convert 'int' object to str implicitly
Как нетрудно догадаться, он не работает: после разделения строки получается список, состоящий из строк. Чтобы превратить эти строки в числа, нужно использовать int
. Вообще говоря, есть методы, которые позволяют довольно просто превратить все элементы списка в числа, но пока мы до них не дошли, будем обрабатывать в цикле каждый элемент в отдельности.
s = input("Введите несколько чисел, разделённых запятыми: ")
elements = s.split(",")
for n in elements:
n = int(n)
print(n+1)
Введите несколько чисел, разделённых запятыми: 2, 3, 99 3 4 100
Теперь работает!
Решим теперь обратную задачу: есть список, мы хотим его вывести в каком-то виде. Есть разные способы это сделать. Например, можно просто распечатать:
elements = ["one", "two", "three"]
print(elements)
['one', 'two', 'three']
Тут элементы списка заключатся в скобки, а каждая строчка дополнительно ещё и в кавычки. Можно распечатать поэлементно в цикле, как в примере выше:
for el in elements:
print(el)
one two three
Так он выводится в столбик, потому что print()
по умолчанию добавляет символ перевода строки \n
в конце каждой выводимой строчки. Если мы хотим вывести элементы списка как-то иначе, например, в строчку, но без скобок, запятых и кавычек, то нужно использовать другие способы.
Первым из них является применение метода join()
. Это метод строки, который позволяет склеить элементы списка с помощью какоего-то «клея». В некотором роде, это такой «split()
наоборот».
print(" ".join(elements))
one two three
print(":".join(elements))
one:two:three
print("----".join(elements))
one----two----three
Это удобный метод, но у него есть ограничение: он требует, чтобы список, который ему подали на вход, состоял из строк. Если среди его элементов есть другие объекты, он ломается.
numbers = [6, 9, 10]
", ".join(numbers)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-124-b20deed5e87c> in <module>() 1 numbers = [6, 9, 10] ----> 2 ", ".join(numbers) TypeError: sequence item 0: expected str instance, int found
Эту проблему можно обойти (мы позже будем говорить про map()
и списочные включения (list comprehensions)), но сейчас обсудим другой подход к выводу списков, основнный на функции print()
.
Напомним, что если передать функции print
список, то он его распечатает в квадратных скобках через запятую.
print(numbers)
[6, 9, 10]
А если передать отдельные элементы списка, то он их распечатает без скобок и разделяя пробелом (вообще говоря, любым символом — это настраивается с помощью параметра sep
).
print(numbers[0], numbers[1], numbers[2])
6 9 10
Строчка выше даёт тот результат, который мы хотели, но она будет работать только в том случае, когда в списке ровно три элемента: если элементов больше, выведены будут только три, если меньше, то возникнет ошибка. К счастью, в Python есть конструкция, которая позволяет «раздеть» список — передать все его элементы какой-то функции через запятую. Это делается с помощью звёздочки (*
).
print(*numbers)
6 9 10
Звёздочка как бы убирает квадратные скобки вокруг элементов списка. Сравните:
print(*[5, 8, 9, 11, "Hello"])
5 8 9 11 Hello
print(5, 8, 9, 11, "Hello")
5 8 9 11 Hello
Если вы хотите использовать не пробел, а какой-нибудь разделитель, то это тоже возможно:
print(*numbers, sep = ', ')
6, 9, 10
Строки могут вести себя почти как списки. Например, можно обращаться к отдельным элементам строки (к отдельным символам) или делать срезы.
s = "This is a test"
s[6]
's'
s[5]
'i'
s[3:8]
's is '
Позже мы ещё поговорим про строки (это отдельная большая история).
Рассмотрим пример алгоритма, в котором используется цикл.
Напомним, как в прошлый раз мы искали числа Фибоначчи.
Сначала мы выполняем инициализацию — устанавливаем начальные значения переменных, которые нам понадобятся в дальнейшем. Исходно мы знаем значения первых двух чисел Фибоначчи (это единицы); мы запишем их в переменные $a$ и $b$ и в дальнейшем будем хранить очередные два найденных числа Фибоначчи, необходимые для нахождения следующего числа.
# инициализация (выполняется только один раз в начале алгоритма)
a = 1 # первое число
b = 1 # второе число
Затем мы пишем код, осуществляющий переход к следующему числу. Мы будем выполнять его несколько раз, каждый раз получая новое число Фибоначчи.
c = a + b #нашли следующее число
a = b # значение a нам уже не нужно, а вот значение b ещё пригодится
b = c # запомнили вычисленное значение
print(b)
2
Выполнив эту ячейку несколько раз, мы будем получать последовательные числа Фибоначчи.
Этот подход работает довольно неплохо, но если бы нам нужно было найти 115-е число Фибоначчи, мы бы замучались перезапускать ячейку. Вместо этого используем цикл for
, который будет автоматически выполнять фрагмент кода, вычисляющий следующее число, столько раз, сколько нам нужно. Например, 10:
# инициализация (выполняется только один раз в начале алгоритма)
a = 1 # первое число
b = 1 # второе число
for i in range(10):
# тело цикла: выполнится 10 раз
c = a + b
a = b
b = c
print(i, c)
0 2 1 3 2 5 3 8 4 13 5 21 6 34 7 55 8 89 9 144
Другой пример: допустим, мы бы хотели не выводить найденные числа Фибоначчи на экран, а записать их в некоторый список. Для этого немного модифицируем код выше: вместо команды print()
нужно подставить команду, которая добавит найденное число в некоторый список. Однако, чтобы было, куда добавлять, этот список должен быть создан заранее. Изначально он может быть и пустым — такой список обозначается пустыми квадратными скобками.
a = 1 # первое число
b = 1 # второе число
fib = []
# создали пустой список fib
for i in range(25):
c = a + b
a = b
b = c
fib.append(b) # записали элемент в конец списка fib
print(fib)
[2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418]
В ходе выполнения программы иногда требуется в зависимости от каких-то условий выполнять тот или иной фрагмент кода. Например, если пользователь ввёл не те данные, которые от него просили (хотели положительное число, а получили отрицательное), то надо вывести ошибку и попросить ввести данные снова. Решение этой задачи разбивается на несколько шагов: сначала нужно проверить некоторое условие, а потом в зависимости от результата этой проверки выбрать, какой код выполнять. Давайте начнём с проверки условий.
6 < 8
True
Здесь мы спросили «Правда ли, что 6 меньше 8?». «Воистину так» — ответил Python на своём заморском языке. Слово True
, которое он выдал — это не просто слово, означающее «истина», а специальное логическое значение. Его ещё называют «булевским» (по имени одного из основателей математической логики Джоджа Буля). Оно бывает всего двух видов: либо истина (True
), либо ложь (False
). Третьего не дано.
8 > 9
False
Результат проверки можно записать в переменную.
condition = 6 < 8
condition
True
Говорят, что переменная condition
теперь булевская (bool
).
type(condition)
bool
Можно проверять равенство двух величин. Правда ли, что 7 равно 7?
7 == 7
True
Обратите внимание: здесь нужно написать символ равенства два раза, потому что один знак равно — это операция присвоения («присвоить то, что справа, тому, что слева»), а операция проверки равенства — это совсем другая штука. Например.
a = 5
Положили в a
число 5
. Такая операция ничего не вернула.
a = 7
Теперь положили в a
число 7.
a == 5
False
Теперь спросили, правда ли, что a
равняется пяти. Получили False
.
Надо сказать, что сравнение работает достаточно разумным образом. Например, число 7
и число 7.0
— это, строго говоря, разные объекты (первое — это целое число, второе — число с плавающей запятой), но понятно, что как числа это один и тот же объект. Поэтому сравнение выдаст True
.
7 == 7.0
True
Хорошо, мы научились проверять разнообразные условия. Теперь нужно менять поведение программы в зависимости от результатов такой проверки. Например, мы хотим ввести число с клавиатуры и в случае, если оно оказалось отрицательным, сообщить об ошибке. Для этого нужно использовать конструкцию if
.
a = int(input("Введите положительное число: "))
if a < 0:
print("Ошибка!")
print("Число не является положительным!")
print("Вы ввели", a)
Введите положительное число: -4 Ошибка! Число не является положительным! Вы ввели -4
Нужно обратить внимание на несколько вещей: во-первых, после if
указывается условие, а после условия обязательно ставится двоеточие (как и в циклах), дальше идёт блок команд, которые выполняются в том случае, если условие верно (то есть является True
). Как и в циклах, этот блок команд должен быть выделен отступом. Команды, не входящие в блок (в данном случае это последняя строчка) выполняются в любом случае.
Допустим, мы хотим обработать отдельно обе ситуации: когда условие выполняется и когда оно не выполняется. Для этого нужно использовать ключевое слово else
.
a = int(input("Введите положительное число: "))
if a < 0:
print("Ошибка!")
print("Число не является положительным!")
else:
print("Как хорошо!")
print("Вы ввели положительное число!")
print("Вы ввели", a)
Введите положительное число: 22 Как хорошо! Вы ввели положительное число! Вы ввели 22
Конструкция if-else
работает как альтернатива: выполняется либо один фрагмент кода (после if
— если условие верно), либо другой (после else
— если неверно). Иногда нужно проверить несколько условий подряд.
a = int(input("Введите какое-нибудь число: "))
if a > 100:
print("Это очень большое число")
elif a > 10:
print("Это больше число")
else:
print("Это маленькое число")
Введите какое-нибудь число: 123 Это очень большое число
Здесь используется ключевое слово elif
, являющееся объединением слов else
и if
. Логика такая: сначала выполняется первое условие (a > 100
), если оно верно, то выполняется код после if
, если неверно, то проверяется следующее условие (a > 10
), если оно верно, то выполняется код после elif
, если неверно, то выполняется код после else
. Команда else
, если она есть, всегда должна идти в конце. Блоков elif
может быть много. Условия проверяются по очереди, начиная от первого; как только какое-то из условий оказывается верным, выполняется соответствующий блок и проверка остальных условий не производится.
Допустим, нам нужно проверить выполнение нескольких условий. Скажем, мы хотим получить число от 0 до 100 — числа меньше 0 или больше 100 нас не устраивают. Это можно было бы сделать с помощью нескольких вложенных операторов if
примерно так.
a = int(input("Пожалуйста, введите число от 0 до 100: "))
if a <= 100:
if a >= 0:
print("Спасибо, мне нравится ваше число")
else:
print("Вы ошиблись, это не число от 0 до 100")
else:
print("Вы ошиблись, это не число от 0 до 100")
Пожалуйста, введите число от 0 до 100: 101 Вы ошиблись, это не число от 0 до 100
Этот код довольно громоздок, строчку с сообщением об ошибке пришлось скопировать дважды. Не очень хорошо. Оказывается, можно реализовать тот же функционал проще.
a = int(input("Пожалуйста, введите число от 0 до 100: "))
if a <= 100 and a >= 0:
print("Спасибо, мне нравится ваше число")
else:
print("Вы ошиблись, это не число от 0 до 100")
Пожалуйста, введите число от 0 до 100: 22 Спасибо, мне нравится ваше число
Здесь используется ключевое слово and
, обозначающее операцию логического И. Оно делает следующее: проверяет левое условие (в данном случае a <= 100
), проверяет правое условие (a >= 100
) и если оба этих условия выполняются (то есть имеют значение True
), то и результат выполнения and
оказывается True
; если же хотя бы одно из них не выполняется (то есть имеет значение False
), то и результат выполнения and
является False
. Таким образом мы можем проверить в точности интересующее нас условие.
Строго говоря, если левый аргумент
and
оказывается ложью, то правый даже не вычисляется: зачем тратить время, если уже понятно, что возвращать надо ложь?
Можно было бы переписать этот код другим способом, используя логическое ИЛИ (or
):
a = int(input("Пожалуйста, введите число от 0 до 100: "))
if a > 100 or a < 0:
print("Вы ошиблись, это не число от 0 до 100")
else:
print("Спасибо, мне нравится ваше число")
Пожалуйста, введите число от 0 до 100: 22 Спасибо, мне нравится ваше число
Результат выполнения or
является истиной в том случае, если хотя бы один аргумент является истиной. Наконец, есть третий логический оператор — это отрицание (not
). Он имеет всего один аргумент и возвращает истину, если этот аргумент является ложью, и наоборот.
a = int(input("Пожалуйста, введите число от 0 до 100: "))
if not (a <= 100 and a >= 0):
print("Вы ошиблись, это не число от 0 до 100")
else:
print("Спасибо, мне нравится ваше число")
Пожалуйста, введите число от 0 до 100: 22 Спасибо, мне нравится ваше число
Можно проверить, как работают логические команды, просто подставляя в качестве аргументов True
или False
:
True or False
True
False and True
False
Можно даже задать Python известный вопрос: быть или не быть?
to_be = False
to_be or not to_be
True
Что будет, если to_be
сделать равным True
?
До сих пор получив неправильное число (не удовлетворяющее нашим условиям) мы могли лишь поругать пользователя и развести руками. На самом деле программы обычно ведут себя иначе: они заставляют пользователя ввести правильное число, требуя этого снова и снова, пока он не капитулирует.
Чтобы добиться такого эффекта (а заодно довести пользователя до белого каления) нужно использовать цикл while
. В прошлый раз мы обсуждали цикл for
, используемый для того, чтобы перебрать элементы некоторого списка, или, в качестве частного случая — выполнить некоторую операцию какое-то конечное (заранее известное) число раз. Цикл while
нужен, если мы хотим выполнять что-то до тех пор, пока выполняется некоторое условие. Например, пока рак на горе не свистнет. Это делается следующим образом.
a = int(input("Введите число от 0 до 100: "))
while a>100 or a<0:
print("Неверно! Это не число от 0 до 100")
a = int(input("Введите число от 0 до 100: "))
print("Ну хорошо")
Введите число от 0 до 100: -2 Неверно! Это не число от 0 до 100 Введите число от 0 до 100: -5 Неверно! Это не число от 0 до 100 Введите число от 0 до 100: 101 Неверно! Это не число от 0 до 100 Введите число от 0 до 100: 88 Ну хорошо
Другой пример: проверка пароля.
correct_passwd = ';ugliugliug'
passwd = input("Please, enter password: ")
while passwd != correct_passwd:
print("Access denied")
passwd = input("Please, enter password: ")
print("Access granted")
Please, enter password: 12345 Access denied Please, enter password: mypassword Access denied Please, enter password: ;ugliugliug Access granted
Этот код не очень изящный, потому что нам приходится дважды писать строку с input()
. Ситуация, при которой нам приходится копировать какие-то строчки кода, обычно означает, что допущена ошибка в проектировании. Можно сделать это более изящно с помощью команды break
— она позволяет выйти из цикла. Следующий пример также демонстрирует бесконечный цикл: по идее while True:
должен выполняться до тех пор, пока True
это True
, то есть вечно. Но мы выйдем раньше с помощью break
.
correct_passwd = ';ugliugliug'
while True:
passwd = input("Please, enter password: ")
if passwd == correct_passwd:
print("Access granted")
break
else:
print("Access denied")
Please, enter password: ;ugliugliug Access granted
Команду break
можно использовать для выхода из любого цикла. Вот пример для цикла for
:
numbers = [6, 8, 9, 6, -7, 9]
for i in numbers:
if i<0:
print("Negative number detected!!!!111111odin")
break
print(i+1)
7 9 10 7 Negative number detected!!!!111111odin
Напишем простую игру: в ней компьютер будет загадывать число от 1 до 100, а пользователь должен его угадать с нескольких попыток. В ответ на каждую попытку компьютер сообщает, какое число больше: загаданное или предложенное пользователем.
Для начала нам нужно подключить генератор случайных чисел, чтобы иметь возможность «загадать» число.
from random import randrange
Функция randrange()
возвращает случайные (строго говоря, пседослучайные) числа из заданного диапазона.
randrange(1,101)
42
Теперь можно приступить к нашей программе.
a = randrange(1, 101)
while True:
guess = int(input("Ваша попытка: "))
if guess == a:
print("Победа!")
break
elif guess > a:
print("Слишком большое число")
else:
print("Слишком маленькое число")
Ваша попытка: 70 Слишком маленькое число Ваша попытка: 80 Слишком большое число Ваша попытка: 75 Слишком маленькое число Ваша попытка: 78 Слишком большое число Ваша попытка: 77 Победа!
Это, конечно, не Half Life, но тоже играть можно. (Кстати, какой оптимальный алгоритм игры?)
В качестве небольшого отступления обсудим такую задачу: есть список, нужно вывести его элементы и их индексы. Эту задачу можно было бы решать так:
numbers = [7, 8, 9, 43]
i = 0 # здесь будет храниться индекс
for n in numbers:
print(i, n)
i += 1
# эта строчка эквивалентна i = i + 1
0 7 1 8 2 9 3 43
Не самое изящное решение — приходится вводить какую-то переменную i
, инициализировать её до входа в цикл и ещё не забывать прибавить к ней единицу внутри цикла.
Другой вариант:
numbers = [7, 8, 9, 43]
for i in range(len(numbers)):
print (i, numbers[i])
0 7 1 8 2 9 3 43
Тоже не является верхом изящества: здесь нам приходится каждый раз писать numbers[i]
и вообще понятность страдает: глядя на цикл for
неясно, что мы собираемся перебирать элементы списка numbers
.
Правильный Python'овский подход выглядит вот так:
numbers = [7, 8, 9, 43]
for i, n in enumerate(numbers, 2):
print(i,n)
2 7 3 8 4 9 5 43
Что здесь произошло. Главная магия кроется в команде enumerate()
. Давайте посмотрим, как она работает:
enum = list(enumerate(numbers))
enum
[(0, 7), (1, 8), (2, 9), (3, 43)]
В результате выполнения enumerate
возвращается штука, которая ведёт себя как список, элементами которого являются пары чисел (вообще-то это кортежи, tuple
, то есть неизменяемые списки). В каждой паре первое число — это индекс, а второе — элемент исходного списка.
Дальше, при выполнении цикла for
используется механизм списочного присваивания. Работает он так.
a, b = (7, 9)
print(a)
print(b)
7 9
Если в левой части от знака равенства стоит несколько переменных, разделённых запятыми, а в правой — какой-нибудь объект, ведущий себя как список, причём число его элементов равно число переменных, то присваивание идёт поэлементно: первый элемент списка в первую переменную, второй во вторую и т.д.
a, b, c = (1, 2, 3)
print(c)
3
Теперь обратите внимание, что в цикле for
у нас в качестве переменной цикла указаны две переменные (через запятую). Что произойдёт, когда мы попадём в цикл в первый раз? for
возьмёт первый элемент от enumerate(numbers)
. Это пара чисел:
enum[0]
(0, 7)
Теперь он приравняет эту пару чисел к паре переменных: i
и n
:
i, n = enum[0]
Теперь в i
лежит 0
(индекс первого элемента numbers
, а в n
— сам первый элемент numbers
. И так далее, на каждом шаге в i
будет лежать соответствующий индекс, а в n
соответствующее число.
При этом результат получается гораздо более изящным, чем предыдущие: нет необходимости как-то явно описывать происходящее с i
, и смысл происходящего понятен при взгляде на строчку с for
.
На сегодня всё.