Основы программирования в Python

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

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

Python как калькулятор, переменные и типы переменных

Python как калькулятор

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

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

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

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

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

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

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

In [6]:
7 // 2 # от 3.5 осталось 3
Out[6]:
3

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

Примечание: для того, чтобы сравнивать исполнение одного и того же кода в Python 3 и Python 2 совсем необязательно устанавливать обе версии на компьютер. Можно воспользоваться онлайн-компиляторами (например, http://rextester.com: выбирать Python или Python 3) и запускать код прямо в браузере. Речь идет, конечно, о небольших фрагментах кода. Полноценно работать в них, не устанавливая Python, будет неудобно. Но для нескольких строк для сравнения как раз.

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

In [7]:
6 ^ 2
Out[7]:
4

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

In [8]:
6 ** 2 # как нужно
Out[8]:
36
In [9]:
6 ** 3
Out[9]:
216
In [10]:
6 ** (1/3) # дробная степень - корень 3 степени
Out[10]:
1.8171205928321397

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

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

NameError: name 'sqrt' is not defined

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

In [12]:
import math # импортируем модуль math
In [13]:
math.sqrt(9) # теперь все работает
Out[13]:
3.0

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

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

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

In [15]:
import math as ma # сократили до ma
ma.sqrt(9)
Out[15]:
3.0

Примечание: то же будет верно и для библиотек. Пока мы столкнулись с двумя модулями – os и math, в темах по обработке данных мы будем активно работать с разными библиотеками. Если совсем упростить, модуль – это набор функций, а библиотека – это набор модулей, то есть что-то более сложное по структуре и более насыщенное по функционалу.

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

In [16]:
math.log(2) # натуральный логарифм
Out[16]:
0.6931471805599453
In [17]:
math.log10(100) # десятичный логарифм (логарифм по основанию 10)
Out[17]:
2.0
In [18]:
math.sin(0) # синус
Out[18]:
0.0

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

In [20]:
math.ceil(8.7) # ceil - потолок
Out[20]:
9
In [21]:
math.floor(8.7) # floor - пол
Out[21]:
8

А ещё из math можно импортировать константы $\pi$ и $e$:

In [22]:
from math import pi, exp # можно сразу несколько - перечислить через запятую
In [23]:
pi
Out[23]:
3.141592653589793
In [24]:
exp(1) # e = e^1 = exp(1)
Out[24]:
2.718281828459045

Просто константу e можно также получить, вызвав e:

In [25]:
math.e
Out[25]:
2.718281828459045

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

In [1]:
from math import *

Однако в общем случае так делать нежелательно, потому что это нерационально и увеличивает время исполнения кода (Python подгружает все функции, а мы пользуемся только двумя, например).

С чем ещё можно столкнуться, выполняя вычисления в 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), ничего бы не изменилось.

Типы переменных и преобразование типов

Типы переменных (не путать с data types и data scales):

  • числовой c плавающей точкой (float);
  • целочисленный (integer);
  • строковый или текстовый (string);
  • логический (boolean): только два значения True и False.

Посмотрим, как определить тип переменной:

In [41]:
x = 2.34
type(x)  # функция type
Out[41]:
float
In [42]:
y = 2
type(y)
Out[42]:
int
In [43]:
r = 'hello'
type(r)
Out[43]:
str
In [44]:
l = True
type(l)
Out[44]:
bool

Иногда требуется преобразовать тип переменной, например, из числа с плавающей точкой сделать целое число. Зачем это бывает нужно? Для удобства и для более корректной выдачи результатов. Например, у нас есть база данных по респондентам, в которой указан их год рождения, и мы хотим добавить столбец с возрастом респондентов (числом полных лет). Из-за того, что кто-то ввел год в виде 1993.0, возраст при вычислениях тоже получится числом с плавающей точкой — 25.0. Так как мы знаем, что возраст всегда будет целым, чтобы дробная часть не смущала, можно привести все значения к целочисленному типу.

In [45]:
int(25.0) # int - от integer
Out[45]:
25

Вообще функции для изменения типа переменных называются так же, как и сами типы или их сокращённые названия.

In [46]:
float(23)
Out[46]:
23.0
In [47]:
str(2) # str - от string
Out[47]:
'2'
In [48]:
int("23")  # int - от integer
Out[48]:
23

Однако, если преобразование невозможно, Python выдаст ошибку (а точнее, исключение ValueError, про исключения поговорим позже):

In [49]:
float('23,56')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-49-b3dd92f20a26> in <module>()
----> 1 float('23,56')

ValueError: could not convert string to float: '23,56'

Так как запятая в Python не воспринимается как десятичный разделитель (в качестве разделителя используется точка), превратить строку '23,56' в число не получится, нужно будет сначала заменить запятую на точку. Как работать со строками, мы обсудим позже, но если интересно, можно сделать следующее: создать любую строковую переменную, а потом после её названия поставить точку и нажать Tab. Так же, как и в случае с модулем math, выпадет список всех возможных методов, которые можно применять к строке.