Программирование для всех (основы работы в Python)

Алла Тамбовцева, НИУ ВШЭ

Данный ноутбук частично основан на лекции Щурова И.В., курс «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ).

Вычисления в Python. Переменные.

Вычисления в Python

Привычные арифметические действия (сложение, вычитание, умножение, деление) в Python выглядят так же, как и в обычных калькуляторах:

In [1]:
2 + 5 # сложение
Out[1]:
7
In [2]:
2 * 8 - 9 # умножение и вычитание
Out[2]:
7
In [3]:
9 / 2 # деление
Out[3]:
4.5

Однако с делением всё не так просто: Python 3 всегда будет выдавать результат в виде числа с плавающей точкой (такой тип данных называется float), даже тогда, когда ожидается целочисленный ответ. Например:

In [4]:
8 / 2 # не 4
Out[4]:
4.0

Получился дробный результат, где дробная часть равна 0. Как быть, если нужен ответ в виде целого числа? Можно воспользоваться целочисленным делением — оператором //:

In [5]:
8 // 2 # теперь 4
Out[5]:
4

Тут важно помнить, что при использовании оператора // дробная часть всегда будет просто отбрасываться – никакого округления происходить не будет. Для округления (обычное арифметическое, в большую и в меньшую сторону) существуют специальные функции, и мы их обсудим позже.

In [6]:
9 // 2 # от 4.5 осталось 4
Out[6]:
4

Что еще можно делать с числами? Возводить в степень и извлекать из них корень. При расчетах на калькуляторе для возведения числа в степень мы обычно используем символ ^. Попробуем!

In [7]:
9 ^ 2
Out[7]:
11

Получилось что-то неожиданное. В Python оператор ^ используется для побитного сложения по модулю два (операция интересная, но нам она нигде не понадобится). Для возведения числа в степень потребуется оператор **:

In [8]:
9 ** 2
Out[8]:
81
In [9]:
10 ** 3
Out[9]:
1000
In [10]:
27 ** (1/3) # дробная степень - корень 3 степени
Out[10]:
3.0

Теперь попробуем извлечь квадратный корень из числа с помощью привычного sqrt.

In [11]:
sqrt(16) # не получается!
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-11-5ab95dccb11e> in <module>
----> 1 sqrt(16) # не получается!

NameError: name 'sqrt' is not defined

Python пишет, что не знает, что такое sqrt. В каких случаях Python может такое писать? Чаще всего в двух случаях:

  • если мы опечатались в названии функции (Python не понимает, что мы от него хотим);
  • если мы пытаемся обратиться к функции, которая не является базовой (Python не знает, откуда её брать).

В данном случае мы столкнулись со второй проблемой. Функция sqrt() для вычисления квадратного корня из числа хранится в специальном модуле math. Этот модуль стандартный, дополнительно устанавливать его не нужно. Но для того, чтобы воспользоваться этой функцией, нужно сначала импортировать модуль, а потом вызвать из него функцию sqrt. Давайте импортируем модуль math:

In [12]:
import math 

А теперь из этого модуля вызовем функцию sqrt():

In [13]:
math.sqrt(16) # теперь все работает
Out[13]:
4.0

Если из math нам нужна только одна функция sqrt , можно извлечь только её, и тогда прописывать название модуля перед функцией не понадобится:

In [14]:
from math import sqrt
sqrt(16) # так тоже работает
Out[14]:
4.0

В math есть много полезных функций для вычислений. Чтобы посмотреть, какие функции там есть, после импортирования всего модуля через import math можно набрать math. и нажать на Tab (табуляция, кнопка над Caps Lock). Помимо квадратного корня этот модуль поможет вычислять логарифмы, синусы, косинусы и так далее. Остановимся на логарифмах, так как именно операция логарифмирования часто встречается в обработке и анализе данных.

Напоминание. Выражение $\log_{a}(b)$ («логарифм от $b$ по основанию $a$») подразумевает следующий вопрос: в какую степень нужно возвести число $a$, чтобы получить $b$? Например, $\log_{4}(16)=2$, так как $4^2=16$, а $\log_{2}{32}=5$, так как $2^5=32$.

В анализе данных чаще используются натуральный (по основанию $e\approx2.718$) и десятичный (по основанию 10) логарифмы. Зачем логарифмирование нужно в анализе данных, мы обсудим позже, а пока посмотрим на их вычисление в Python:

In [15]:
math.log(27, 3) # логарифм по основанию 3
Out[15]:
3.0
In [16]:
math.log(12) # натуральный логарифм
Out[16]:
2.4849066497880004
In [17]:
math.log10(10000) # десятичный логарифм
Out[17]:
4.0

В модуле math также хранятся функции для округления в большую или меньшую сторону:

In [18]:
math.ceil(4.2) # от ceil - потолок
Out[18]:
5
In [19]:
math.floor(4.6) # от floor - пол
Out[19]:
4

Для обычного округления используется базовая, не встроенная в math функция round():

In [20]:
round(4.7)
Out[20]:
5

С чем ещё можно столкнуться, выполняя вычисления в Python? С такими вещами:

In [26]:
1 / 18 ** 25
Out[26]:
4.1513310942010236e-32

Результат выше – компьютерная форма экспоненциальной записи числа. Возможно, тот, кто считал что-то на научных или инженерных калькуляторах, уже сталкивался с такой записью. Здесь e-32 – это $10^{-32}$, а вся запись означает $4.1513310942010236 \cdot 10^{-32}$, то есть примерно $4.15 \cdot 10^{-32}$. Теоретически, если число было очень большим, e стояло бы в положительной степени. Но в Python такое не случается, обычно он выводит огромные числа, просто переходя на новую строку, если места на одной не хватает:

In [27]:
23 ** 990
Out[27]:
12899047957225248523006646539464335721979411301041340884781899303831010765027442196396594170642790934375007246578677182803636590164161819235523359334210795997877313526230138186880373768216363562984711930606834390635683889567066017501638286295454450223592921380025243612655929972891854670089005958782301313748919257409270999076443855743717129316401343809648755190213387432370099603517989905917859013302341878321325941570315088698234189444110362237214217846884135935952399097352427521852877620725021626938113437232848226058128334528859922677792198697568021708059256675191088006467069748109014817451375952598349790911535607651796493584493889427435570940502359773301622881259890983839926411232560174739455589741388073805529446667461505169110660161765843273557623843219490805708791092602475974648916336329253754550331716502327365416889998212147857020330942368277430427804385066546271943413689950822020931400593245046303758610973943882235592749794293155060009636680119916358947879472882782808870076229635495297702820410851089251120079429452529354919596272075046635023793735251154878700529432000342403111069160632592433095161936605646694694109530441088995393229754160740330575038216906404744009495445586324655891258721903641299986081018110880120922925030222636715904777957341225983442938451506259053249716900875692689602609775497863867095485915444153828849

Компьютерная форма записи числа отчасти помогает понять, почему дробные числа называются числами с плавающей точкой (тип float). Возьмем число попроще, например, $12.34$. Его можно записать как $12.34$, как $1.234 \cdot 10$, как $123.4 \cdot 10^{-1}$, $1234 \cdot 10^{-2}$ и так далее. Точка, отделяющая дробную часть от целой, будет «плавать», однако само число при этом меняться не будет, будут меняться только множители – разные степени десятки.

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

In [28]:
round(12.6) # округлим до целого - по умолчанию
Out[28]:
13
In [29]:
round(12.53, 1) # округлим до первого знака после запятой
Out[29]:
12.5

С другой стороны, могут возникнуть странности:

In [30]:
round(2.50) # не 3
Out[30]:
2
In [31]:
round(3.525, 2) # не 3.53
Out[31]:
3.52

Эти странности связаны с тем, что число, которое мы видим (например, 3.525), не совпадает с тем, которое хранится в компьютере, потому что оно при сохранении преобразовывается и превращается из точного 3.525 в такое:

In [32]:
from decimal import Decimal
Decimal(3.525)
Out[32]:
Decimal('3.524999999999999911182158029987476766109466552734375')

И такое число будет законно округляться до 3.52 по правилам арифметического округления. В прикладном анализе данных такие сложности редко вызывают проблемы, но знать про нее полезно, чтобы не пугаться и не удивляться неожиданным результатам. Кроме того, полезно помнить, что числа с плавающей точкой (типа float) не рекомендуется использовать в финансовых вычислениях и вообще в вычислениях, требующих высокой точности, поскольку они «накапливают ошибку», то есть дают неточные результаты.

Переменные

Переменные в программировании похожи на переменные в математике. Кроме того, их можно рассматривать как хранилища значений – контейнеры или «коробки», в которые мы что-то кладем. Python, в отличие от некоторых языков программирования (C, C++, Java), сам распознает что мы «кладем в коробку»: число, целое число, текст, список чисел... Поэтому при создании переменной нам не нужно указывать ее тип.

In [33]:
x = 2 # Python поймет, что это целые числа
y = 3
In [34]:
print(x)
print(y)
2
3

Значения переменных мы можем обновлять – изменить значение и сохранить в переменную с тем же названием.

In [35]:
x = x + 1 # возьмем значение x, увеличим на 1 и сохраним изменения в переменной x
In [36]:
y = y * 2 # возьмем значение y, увеличим в 2 раза и сохраним изменения
In [37]:
print(x, y) # одновременно два значения
3 6

Названия переменных в Python могут быть почти любыми. Три общих правила: название переменной не должно начинаться с цифры, в названии не должно быть пробелов, название не должно совпадать со служебными (зарезервированными) словами в Python. Список зарезервированных слов можно узнать так:

In [38]:
import keyword
print(keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

Обычно рекомендуется давать переменным осмысленные названия: если речь идёт о доходе, называть переменную не x, а income, если речь идёт о данных по преступности, сохранять таблицу в переменную crimes, и так далее. Технически, Python 3 допускает названия на кириллице, но это будет выглядеть странно и неуместно.

Рассмотрим такую задачу. Пришла весна и решили мы заняться бегом по такой схеме: каждый день мы пробегаем столько, сколько в сумме за два предыдущих дня. При этом первые два дня мы морально готовимся: топчемся на месте и символически проходим по одному метру (полшага назад и полшага вперед). Если мы будем записывать все пройденные нами расстояния в ряд, мы получим последовательность из чисел Фибоначчи. Давайте напишем код, который будет считать, сколько метров мы будем пробегать в следующий день.

Сначала создадим переменные, в которые сохраним данные по первым двум дням.

In [39]:
b = 1 # день 1 - морально готовимся бегать, «бежим» 1 метр 
i = 1 # номер дня, когда начинаем бегать
bnext = 1 # день 2 - снова морально готовимся бегать, «бежим» 1 метр
i = i + 1 # перешли ко второму дню, увеличили i на 1
In [40]:
res = b + bnext # в следующий день пробегаем столько же, сколько за два предыдущих
i = i + 1 # перешли к следующему дню, увеличили i на 1
b = bnext   # значение b нам уже не нужно, сдвигаемся к следующему дню - записываем bnext
bnext = res   # запомнили полученное значение res
print(i, bnext) # выводим на экран номер дня и расстояние, которое нужно пробежать
3 2

Теперь можно прогонять предыдущую ячейку много раз (через Ctrl + Enter) и получать результат по каждому дню. Например, на 20 день мы будем пробегать уже нормальное расстояние — 6765 метров, почти 7 километров. Конечно, прогонять одну и ту ячейку много раз неудобно и странно, но о том, как считать числа Фибоначчи более рационально, мы поговорим, когда будем разбирать циклы.

Важно: если бы не разбили наш код на части (на две ячейки), ничего бы при повторном запуске ячейки не произошло — переменным b, bnext и i заново присваивались бы значения 1, и движения вперед бы не происходило.

Примечание: можно было последней строкой написать print(i, res), ничего бы не изменилось.