Программирование для политологов

Щуров И.В., НИУ ВШЭ

Данный notebook распространяется на условиях лицензии Creative Commons Attribution-Share Alike 4.0. При использовании обязательно упоминание автора курса и аффилиации. При наличии технической возможности, необходимо также указать активную гиперссылку на страницу курса.

Исходный код этой лекции

Лекция 2. Типы данных, списки и цикл for

Данные в Python бывают разных типов. Например, есть числа, и есть строки. Числа, кстати, тоже бывают разные — целые числа (integer) и числа с плавающей точкой (float). (Ещё бывают комплексные числа, но мы про них не будем говорить.)

Числа

In [2]:
x=3.1415926 #число с плавающей точкой
In [3]:
print x
3.1415926

In [4]:
print "x is %i" % x 
# буква i в «%i» означает «integer» — целое. 
# Поэтому при выводе переменная x будет округлена. 
x is 3

In [5]:
print "x is %f" % x # так можно вывести число без округления до целого
x is 3.141593

In [6]:
print "x is %0.3f" % x # так можно округлить число до трёх знаков после запятой
x is 3.142

In [7]:
print "%07.2f" %x # а так — добавить нулей в начале
# это может быть полезно, если вы захотите сортировать числа как строки
0003.14

In [8]:
3./4 #напомним, что целые числа и числа с плавающей точкой делятся по-разному
Out[8]:
0.75
In [9]:
3/4
Out[9]:
0
In [10]:
x=4
In [11]:
x/5 #поэтому если вы делите какую-нибудь переменную на что-нибудь…
Out[11]:
0
In [12]:
# и хотите, чтобы деление было не целочисленным, нужно привести переменную
# к типу float
float(x)/5 
Out[12]:
0.8
In [13]:
pi=3.1415926
In [14]:
int(pi) 
# наоборот, функция int приводит число к целому типу, отбрасывая дробную часть
Out[14]:
3
In [15]:
int(3.9)
Out[15]:
3
In [16]:
int(-3.9)
Out[16]:
-3

Строки

А ещё бывают строки. Мы с ними уже сталкивались, когда выводили значения переменных выше. Строки можно присваивать различным переменным

In [17]:
s="Hello, World!"
In [18]:
print s
Hello, World!

In [19]:
template="This is %i"
In [20]:
template
Out[20]:
'This is %i'
In [21]:
template % 12
Out[21]:
'This is 12'

Списки

Про строки мы будем подробно говорить позже (там свои тонкости, связанные с представлениями символов в разных кодировках, юникоде и т.д.), а сейчас обратимся к спискам (list).

In [22]:
lst1=[2, 5, 1, 2, 3, 2, 4, 6]
# вот эта штука в квадратных скобках — это и есть список
In [23]:
lst1
Out[23]:
[2, 5, 1, 2, 3, 2, 4, 6]
In [24]:
lst1[0] #нумерация элементов списка начинается всегда с нуля!
Out[24]:
2
In [25]:
lst1[4]
Out[25]:
3
In [26]:
lst1[1:4] #это так называемый slice — кусок списка
Out[26]:
[5, 1, 2]
In [27]:
lst1[:4] #с начала до 4-го элемента
Out[27]:
[2, 5, 1, 2]
In [28]:
lst1[4:] #с пятого элемента до конца
Out[28]:
[3, 2, 4, 6]
In [29]:
lst1[-1] #если индекс отрицательный, он отсчитывается с конца
Out[29]:
6
In [30]:
lst1[-2]
Out[30]:
4
In [31]:
lst1
Out[31]:
[2, 5, 1, 2, 3, 2, 4, 6]
In [32]:
lst2=lst1
In [33]:
lst2
Out[33]:
[2, 5, 1, 2, 3, 2, 4, 6]
In [34]:
lst2[0]=100
In [35]:
lst2
Out[35]:
[100, 5, 1, 2, 3, 2, 4, 6]
In [36]:
lst1 # как думаете, что получится?
Out[36]:
[100, 5, 1, 2, 3, 2, 4, 6]

Мы поменяли элемент списка lst2, но при этом поменялся и lst1! Почему так произошло? Дело в том, что списки живут в своём собственном мире платоновских идей. Когда мы присваиваем список переменной, то есть пишем что-нибудь вроде lst1=[2, 5, 1, 4, 6] мы говорим, что теперь переменная lst1 будет указывать на этот список. После этого в lst1 хранится не сам список, а указатель (ссылка) на него. Когда мы присваиваем значение lst1 новой переменной lst2, мы не производим копирование списка, мы копируем только указатель. То есть lst2 просто стала другим именем для того же самого списка, что и lst1. Поэтому изменение элементов lst2 приведет к изменению lst1, и наоборот.

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

In [37]:
lst2=lst1[:] 
# в правой части — slice списка lst1, 
# начинающийся с первого элемента и заканчивающийся последним
In [38]:
lst2
Out[38]:
[100, 5, 1, 2, 3, 2, 4, 6]
In [39]:
lst1
Out[39]:
[100, 5, 1, 2, 3, 2, 4, 6]
In [40]:
lst2[0]=25
In [41]:
lst2
Out[41]:
[25, 5, 1, 2, 3, 2, 4, 6]
In [42]:
lst1
# теперь списки живут независимо друг от друга
Out[42]:
[100, 5, 1, 2, 3, 2, 4, 6]
In [43]:
lst1.append(123)
# приписать число 123 в конец списка lst1
In [44]:
lst1
Out[44]:
[100, 5, 1, 2, 3, 2, 4, 6, 123]

слово append — это так называемый «метод» — функция, «принадлежащая» некоторому объекту (в данном случае — объекту lst1 типа list (список)), и что-то делающая с этим объектом при запуске. У lst1, как у любого списка, есть много методов. Можно набрать lst1., нажать табуляцию, и получить список доступных методов. Например:

In [45]:
lst1.count(2) # посчитать число двоек в списке
Out[45]:
2
In [46]:
lst1.extend([2,3,4]) # приписать список в конец

В отличие от append, приписывающей один элемент, extend приписывает сразу целый список (и на вход получает список).

In [47]:
lst1
Out[47]:
[100, 5, 1, 2, 3, 2, 4, 6, 123, 2, 3, 4]
In [48]:
[1,2]+[3,4] # списки можно приписывать друг к другу 
Out[48]:
[1, 2, 3, 4]
In [49]:
M=[1,2,3]
N=[4,6]
M.extend(N)
M
Out[49]:
[1, 2, 3, 4, 6]

Возможно, у вас могло возникнуть желание написать этот код таким образом:

In [50]:
M=[1,2,3]
N=[4,6]
M=M+N
M
Out[50]:
[1, 2, 3, 4, 6]

Этот код, в принципе, сработал, но делает он это гораздо менее эффективно, чем с extend. Вместо того, чтобы приписать список N к списку M, мы создаем совсем новый список, который образуется путём конактенации (приписывания) M и N, потом уничтожаем старый список M и присваиваем той же переменной M ссылку на новый список. Если списки большие, это потребует большего времени и памяти, чем код выше.

Кортежи

Иногда нам нужно что-то вроде списка, но мы заранее знаем, что не будем менять элементы этого списка, не будем к нему ничего приписывать и т.д. Значит, нам нужен не список, а кортеж (tuple). Кортежи в Питоне обозначаются круглыми скобками.

In [51]:
(1,2,3)
Out[51]:
(1, 2, 3)
In [52]:
A=(1,2,3)
A[0]=10 # ничего не выйдет, это кортеж, а не список!
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-52-32030cb66306> in <module>()
      1 A=(1,2,3)
----> 2 A[0]=10 # ничего не выйдет, это кортеж, а не список!

TypeError: 'tuple' object does not support item assignment
In [63]:
A
Out[63]:
(1, 2, 3)
In [64]:
# если вам кто-то дал кортеж, а вы хотите список, сделайте приведение типов
B=list(A) 
In [65]:
B
Out[65]:
[1, 2, 3]
In [66]:
s="asdfg" # кстати, строки ведут себя как кортежи
In [67]:
s[2]
Out[67]:
'd'
In [68]:
s[1:3]
Out[68]:
'sd'

Сортировка

In [69]:
lst1=[12,3,4,10]
In [70]:
lst1.sort()
In [71]:
# казалось бы, ничего не произошло. Однако…
lst1
Out[71]:
[3, 4, 10, 12]
In [72]:
lst1.sort(reverse=True) # наоборот
lst1
Out[72]:
[12, 10, 4, 3]

Метод sort() сортирует список, что называется, in place, внутри себя, без создания нового списка. Это бывает удобно (быстро и память лишняя не расходуется), но иногда мы хотим наоборот, оставить исходный список в неприкосновенности, но получить его сортированную копию. Это делается так.

In [73]:
lst1=[1,4,3,62]
In [74]:
lst2=sorted(lst1)
In [75]:
lst2
Out[75]:
[1, 3, 4, 62]
In [76]:
sorted(lst1,reverse=True) # тоже работает
Out[76]:
[62, 4, 3, 1]
In [77]:
strlist=["Ann","Bob","Alice"] # списки могут содержать строки
In [78]:
sorted(strlist)
Out[78]:
['Alice', 'Ann', 'Bob']

Цикл for

Если вы изучали какие-нибудь языки программирования, вы могли сталкиваться с циклами со счётчиком — типа такого:

for i=1 to 10 do something;

или такого:

for(i=0;i<10;i++) {something()}

В Питоне цикл for устроен несколько нетипично — он приспособлен, в первую очередь, для перебора (iterating) элементов списков (и не только списков, но у дригх похожих объектов, которые называются «последовательностями» — это списки, кортежи и многие другие объекты, элементы которых можно перебирать.)

In [79]:
for name in strlist:
    print "Hello, %s!" % name
    print "Welcome here"
print "Okay"
Hello, Ann!
Welcome here
Hello, Bob!
Welcome here
Hello, Alice!
Welcome here
Okay

Как и в случае с циклом while, отступы показывают, когда заканчивается блок, выполняющийся в цикле.

In [80]:
for i in [0,1,2,3,4,5,6,7,8,9]:
    print i**2
0
1
4
9
16
25
36
49
64
81

In [81]:
range(10) # специальная встроенная функция, возвращающаяся последовательные числа
# правая граница не включается
Out[81]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [82]:
range(4,11)
Out[82]:
[4, 5, 6, 7, 8, 9, 10]
In [83]:
range(10,100,10)
Out[83]:
[10, 20, 30, 40, 50, 60, 70, 80, 90]
In [84]:
range(10,1,-2)
Out[84]:
[10, 8, 6, 4, 2]
In [85]:
range(10,1)
Out[85]:
[]
In [86]:
range(1,5,0.5) # не судьба — шаг должен быть целым
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-86-571aa916db6b> in <module>()
----> 1 range(1,5,0.5) # не судьба — шаг должен быть целым

TypeError: range() integer step argument expected, got float.
In [87]:
for i in range(10):
    print i**2
0
1
4
9
16
25
36
49
64
81

Функция range() возвращает полноценный список. Но если он нам нужен только для организации циклов, и больше ни для чего, нам совсем нет нужды тратить кучу памяти для запоминания каждого его элемента. Есть объект xrange(), который ведёт почти так же, как range(), но не кушает память.

In [88]:
xrange(10)
Out[88]:
xrange(10)
In [89]:
for i in xrange(10):
    print i**2
0
1
4
9
16
25
36
49
64
81

enumerate и zip

Допустим, у нас есть список из трёх имен:

In [90]:
M=["Alice","Bob","Ann"]

И мы хотим вывести его в нумерованном виде:

0. Alice
1. Bob
2. Ann
In [91]:
# это можно сделать так
for i in xrange(len(M)):
    print "%i. %s" % (i, M[i])
0. Alice
1. Bob
2. Ann

In [92]:
# но лучше — так
for i, name in enumerate(M):
    print "%i. %s" % (i, name)
0. Alice
1. Bob
2. Ann

Разберемся более подробно, как работает этот код.

Функция enumerate() нумерует элементы списка: возвращает список, состоящий из кортежей, первый элемент которых — номер, а второй — элемент исходного списка. Можно думать об этой штуке как о такой табличке, записанной по строчкам.

In [93]:
list(enumerate(M))
Out[93]:
[(0, 'Alice'), (1, 'Bob'), (2, 'Ann')]

Далее, есть такая штука, как операция приравнивания списком

In [94]:
a,b=(5,"Hello")
In [95]:
a
Out[95]:
5
In [96]:
b
Out[96]:
'Hello'

Например, первой переменной присваивается первое значение списка (или кортежа), второй — второе и т.д. С помощью этой штуки очень удобно, например, поменять значения двух переменных местами. (Подумайте, как бы вы стали это делать, если бы не было возможности приравнивать списками?)

In [97]:
a,b=(b,a)
In [98]:
a
Out[98]:
'Hello'
In [99]:
b
Out[99]:
5
In [100]:
a,b=b,a # можно опустить скобки
In [101]:
a
Out[101]:
5
In [102]:
b
Out[102]:
'Hello'

Теперь должно быть понятно, что делает код выше. Он создаёт список, состоящий из пар «номер, значение исходного списка» (с помощью enumerate), а потом последовательно присваивает каждую пару переменным i и name.

Рассмотрим ещё один пример.

In [103]:
years=[1980,1990,2000,2010]
gdp=[12.,15.1,10.3,16.]

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

1980 12.
1990 15.1
2000 10.3
2010 16.
In [104]:
# можно это сделать так
for i in xrange(len(years)):
    print "%i %f" % (years[i],gdp[i])
1980 12.000000
1990 15.100000
2000 10.300000
2010 16.000000

Выглядит не очень аккуратно — нужно вводить лишнюю сущность — счётчик элемента в массиве i. Вместо этого, воспользуемся функцией zip() — подобно застежки-молнии, она склеивает два массива в один массив пар.

In [105]:
zip(years,gdp)
Out[105]:
[(1980, 12.0), (1990, 15.1), (2000, 10.3), (2010, 16.0)]
In [106]:
for y, g in zip(years,gdp):
    print "%i %f" %(y,g)
1980 12.000000
1990 15.100000
2000 10.300000
2010 16.000000

Задание

  1. Перепишите код, выводящий первые 15 чисел Фибоначчи, используя for вместо while. Используйте только три переменные, включая счётчик цикла. (Чтобы сократить количество переменных, используйте приравнивание списком.)
  2. Создайте список, состоящий из 15 первых чисел Фибоначчи.
  3. Найдите наибольший и наименьший элемент, принадлежащий списку. Укажите их индексы.
  4. Найдите сумму квадратов чисел, принадлежащих данному (какому-нибудь) списку
  5. Найдите среднее арифметическое чисел, принадлежащих списку.
  6. Найдите среднеквадратичное и стандартное отклонение для всех элементов, принадлежащих списку
  7. Известно, что каждая следующая строчка в треугольнике Паскаля может быть построена из предыдущей (каждый элемент треугольника Паскаля равен сумме двух элементов над ним). Пользуясь этим фактом, постройте треугольник Паскаля до 15-й строчки, не вычислив ни одного факториала явно.
  8. Пусть матрица задана в виде списка списков. Например, матрица \[\begin{pmatrix} 1&2&3\\ 4&5&6\\ 7&8&9 \end{pmatrix}\] будет записана таким образом:

    M=[[1,2,3],[4,6,7],[7,8,9]]
    Найдите наибольший и наименьший элемент матрицы. Выведите их значения, а также номер строки и столбца.
  9. Транспонируйте матрицу, записанную в виде списка списков.
  10. Рассмотрим следующую последовательность действий:

In [1]:
t=(1,2,3)
l=list(t) # сделали список из кортежа t
l
Out[1]:
[1, 2, 3]
In [2]:
list=[10,20,30]
l=list(t) # ой
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-92abe3aaeffe> in <module>()
      1 list=[10,20,30]
----> 2 l=list(t) # ой

TypeError: 'list' object is not callable

Мы переопределили слово list — теперь это список [10, 20, 30], а вовсе не функция, создающая список из чего угодно. Как же нам теперь сделать список из кортежа? Задача: найти ответ с помощью любой поисковой системы.