питон

При программировании на питоне первые полезные результаты начинают получаться минут через 5 после начала работы над проектом. Не надо изобретать никаких велосипедов: всё, что может понадобиться, доступно или в стандартной библиотеке, или в одном из многочисленных дополнительных пакетов. Программы получаются элегантные и лаконичные, их легко и приятно писать и читать. Имеется широкий выбор высококачественных инструментов программиста: интегрированные среды разработки, отладчики и средства тестирования и т.д. Питон прекрасно приспособлен для написания больших и сложных программных комплексов. И всё это совершенно не зависит от железа и операционной системы (конечно, если не вызывать низкоуровневых системно-зависимыех библиотек).

Главный минус - питон, мягко говоря, не славится потрясающим быстродействием. Писать программы сложных вычислений на питоне, конечно, глупо. Но есть ряд способов обойти это ограничение. Если программа занимается регулярной обработкой больших массивов чисел с плавающей точкой, то использование numpy радикально повышает её быстродействие. Пакет numpy и его расширение scipy фактически делают matlab ненужным - дают ту же функциональность с более приятным языком программирование. Есть ряд пакетов для построения высококачественных графиков, например, matplotlib. Другое средство повышения быстродействия - cython. Пишется программа, выглядящая почти как питонская, но со вставленными статичестими декларациями типов. cython транслирует её в исходный код на C, что часто даёт быстродействие, сравнимое с вручную написанным C. Программа на cython-е может свободно вызывать функции из библиотек на C; её можно использовать из обычного питона. Ну и наконец можно написать критически важные для быстродействия системы в целом вычисления на другом языке (C например), и вызывать эти внешние программы из питона. Питон при этом выполняет роль клея - реализует логику высокого уровня, системно-независимый GUI и т.д.

Питон - сильно типизированный язык с динамической типизацией. Всё, с чем работает программа - объекты; каждый из них принадлежит определённому типу. Если программа пытается выполнить какую-то операцию над объектом такого типа, который не поддерживает эту операцию, произойдёт ошибка времени выполнения. Описаний переменных в питоне нет. Одна и та же переменная может в разные моменты иметь значения - объекты разных типов. Ошибка в типах может проявиться много времени спустя после того, как программа сдана в эксплуатацию, особенно если она происходит в редко используемом участке кода. Язык допускает ситуацию, когда мы сначала присвоим какой-то переменной целое число, потом строку, потом ещё что-то, но это плохой стиль. Переменная заводится для одного конкретного использования, и естественно, чтобы её значения в любой момент подходили для этого использования и имели одинаковый тип. В исходный текст на питоне 3.5 можно включить (необязательные) аннотации типов переменных и функций, и прогнать её через программу статической проверки типов.

В этом курсе мы познакомимся с основными конструкциями языка питон и наиболее часто используемыми функциями из стандартной библиотеки. Мы не будем пытаться охватить всё - если потребуется, недостающие факты всегда легко найти в справочной документации. Затем мы рассмотрим основные пакеты, используемые в научных вычислениях - numpy и scipy, matplotlib, пакет символьных вычислений sympy. Мы также обсудим использование cython-а для повышения быстродействия критических участков программы и для написания интерфейсов к библиотекам на C.

Каждая лекция представляет собой Jupyter-блокнот, см. http://jupyter.org/. Блокнот содержит последовательность ячеек. Некоторые из них содержат текст (включая картинки, формулы и т.д.), а некоторые - код. Кодовая ячейка состоит из поля ввода, помеченного In[$n$], и поля вывода, помеченного Out[$n$] (поле вывода может отсутствовать). Встав в поле ввода и нажав shift-enter, Вы пошлёте этот код интерпретатору питона; результат появится в поле вывода. Вы можете изменять примеры, приведённые в лекциях, и смотреть, что получится. Можно вставлять новые ячейки.

Некоторые полезные ссылки

Числа

Арифметические операции имеют ожидаемые приоритеты. При необходимости используются скобки.

In [1]:
1+2*3
Out[1]:
7
In [2]:
(1+2)*3
Out[2]:
9

Возведение целого числа в целую степень даёт целое число, если показатель степени $\ge0$, и число с плавающей точкой, если он $<0$. Так что тип результата невозможно определить статически, если значение переменной n неизвестно.

In [3]:
n=3
2**n
Out[3]:
8
In [4]:
n=-3
2**n
Out[4]:
0.125

Арифметические операции можно применять к целым и числам с плавающей точкой в любых сочетаниях.

In [5]:
n+1.0
Out[5]:
-2.0

Деление целых чисел всегда даёт результат с плавающей точкой, даже если они делятся нацело. Операторы // и % дают целое частное и остаток.

In [6]:
7/4
Out[6]:
1.75
In [7]:
7//4
Out[7]:
1
In [8]:
7%4
Out[8]:
3
In [9]:
4/2
Out[9]:
2.0

Если Вы попытаетесь использовать переменную, которой не присвоено никакого значения, то получите сообщение об ошибке.

In [10]:
x
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-10-401b30e3b8b5> in <module>()
----> 1 x

NameError: name 'x' is not defined

x+=1 означает x=x+1, аналогично для других операций. В питоне строго различаются операторы (например, присваивание) и выражения, так что таких операций, как ++ в C, нет. Хотя вызов функции в выражении может приводить к побочным эффектам.

In [11]:
x=1
x+=1
print(x)
2
In [12]:
x*=2
print(x)
4
In [13]:
del x
x
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-726510e32795> in <module>()
      1 del x
----> 2 x

NameError: name 'x' is not defined
In [14]:
x=4

Любопытная особенность питона: можно использовать привычные из математики сравнения вроде $xx<y and y<z.

In [15]:
1<2<=2
Out[15]:
True
In [16]:
1<2<2
Out[16]:
False

Логические выражения можно комбинировать с помощью and и or (эти операции имеют более низкий приоритет, чем сравнения). Если результат уже ясен из первого операнда, второй операнд не вычисляется. А вот так выглядит оператор if.

In [17]:
if 1<2 and x<3:
    print('T')
else:
    print('F')
F
In [18]:
if 1<2 or x<3:
    print('T')
else:
    print('F')
T

После строчки, заканчивающейся :, можно писать последовательность операторов с одинаковым отступом (больше, чем у строчки if). Никакого признака конца такой группы операторов не нужно. Первая строчка после else:, имеющая тот же уровень отступа, что и if и else: - это следующий оператор после if.

Оператора, аналогичного case или switch, в питоне нет. Используйте длинную последовательность if ... elif ... elif ... else.

In [19]:
n=4
if n==1:
    print('один')
elif n==2:
    print('два')
elif n==3:
    print('три')
else:
    print('много')
много

Есть и условные выражения:

In [20]:
(0 if x<0 else 1)+1
Out[20]:
2

Обычно в начале пишется основное выражение, оно защищается условием в if, а после else пишется исключительный случай.

В питоне немного встроенных функций. Большинство надо импортировать. Элементарные функции импортируют из модуля math. Заниматься импортозамещением (писать свою реализацию синуса) не нужно.

In [21]:
from math import sin,pi
In [22]:
pi
Out[22]:
3.141592653589793
In [23]:
sin(pi/6)
Out[23]:
0.49999999999999994

Любой объект имеет тип.

In [24]:
type(2)
Out[24]:
int
In [25]:
type(int)
Out[25]:
type
In [26]:
type(2.1)
Out[26]:
float
In [27]:
type(True)
Out[27]:
bool

Имена типов по совместительству являются функциями, преобразующими в этот тип объекты других типов (если такое преобразование имеет смысл).

In [28]:
float(2)
Out[28]:
2.0
In [29]:
int(2.0)
Out[29]:
2
In [30]:
int(2.9)
Out[30]:
2
In [31]:
int(-2.9)
Out[31]:
-2

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

Строки

Питон хорошо приспособлен для работы с текстовой информацией. В нём есть много операций для работы со строками, несколько способов записи строк (удобных в разных случаях). В современных версиях питона (3.x) строки юникодные, т.е. они могут содержать одновременно русские и греческие буквы, немецкие умляуты и китайские иероглифы.

In [32]:
s='Какая-нибудь строка \u00F6 \u03B1 \u2230 \u342A'
print(s)
Какая-нибудь строка ö α ∰ 㐪
In [33]:
'Эта строка может содержать " внутри'
Out[33]:
'Эта строка может содержать " внутри'
In [34]:
"Эта строка может содержать ' внутри"
Out[34]:
"Эта строка может содержать ' внутри"
In [35]:
s='Эта содержит и \', и \"'
print(s)
Эта содержит и ', и "
In [36]:
s="""Строка,
занимающая
несколько
строчек
"""
print(s)
Строка,
занимающая
несколько
строчек

In [37]:
s=="Строка,\nзанимающая\nнесколько\nстрочек\n"
Out[37]:
True

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

In [38]:
s='Такие ' 'строки ' 'слипаются'
print(s)
Такие строки слипаются
In [ ]:
print('Такие\n'
     'строки\n'
     'слипаются')

В питоне нет специального типа char, его роль играют строки длины 1. Функция ord возвращает (юникодный) номер символа, а обратная ей функция chr возвращает символ (строку длины 1).

In [39]:
n=ord('а')
n
Out[39]:
1072
In [40]:
chr(n)
Out[40]:
'а'

Функция len возвращает длину строки. Она применима не только к строкам, но и к спискам, словарям и многим другим типам, про объекты которых разумно спрашивать, какая у них длина.

In [41]:
s='0123456789'
len(s)
Out[41]:
10

Символы в строке индексируются с 0. Отрицательные индексы используются для счёта с конца: s[-1] - последний символ в строке, и т.д.

In [42]:
s[0]
Out[42]:
'0'
In [43]:
s[3]
Out[43]:
'3'
In [44]:
s[-1]
Out[44]:
'9'
In [45]:
s[-2]
Out[45]:
'8'

Можно выделить подстроку, указав диапазон индексов. Подстрока включает символ, соответствующий началу диапазона, но не включает соответствующий концу. Удобно представлять себе, что индексы соответствуют положениям между символами строки. Тогда подстрока s[n:m] будет расположена между индексами n и m.

индексы

In [46]:
s[1:3]
Out[46]:
'12'
In [47]:
s[:3]
Out[47]:
'012'
In [48]:
s[3:]
Out[48]:
'3456789'
In [49]:
s[:-1]
Out[49]:
'012345678'
In [50]:
s[3:-2]
Out[50]:
'34567'

Если не указано начало диапазона, подразумевается от начала строки; если не указан его конец - до конца строки.

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

In [51]:
s='abc'; t='def'
s+t
Out[51]:
'abcdef'
In [52]:
s*3
Out[52]:
'abcabcabc'

Операция in проверяет, содержится ли символ (или подстрока) в строке.

In [53]:
'a' in s
Out[53]:
True
In [54]:
'd' in s
Out[54]:
False
In [55]:
'ab' in s
Out[55]:
True
In [56]:
'b' not in s
Out[56]:
False

У объектов типа строка есть большое количество методов. Метод lstrip удаляет все whitespace-символы (пробел, tab, newline) в начале строки; rstrip - в конце; а strip - с обеих сторон. Им можно передать необязательный аргумент - символы, которые нужно удалять.

In [57]:
'   строка   '.lstrip()
Out[57]:
'строка   '
In [58]:
'   строка   '.rstrip()
Out[58]:
'   строка'
In [59]:
'   строка   '.strip()
Out[59]:
'строка'

lower и upper переводят все буквы в маленькие и заглавные.

In [ ]:
'СтРоКа'.lower()
In [ ]:
'СтРоКа'.upper()

Проверки: буквы (маленькие и заглавные), цифры, пробелы.

In [ ]:
'АбВг'.isalpha()
In [ ]:
'абвг'.islower()
In [ ]:
'АБВГ'.isupper()
In [ ]:
'0123'.isdigit()
In [ ]:
' \t\n'.isspace()

Строки имеют тип str.

In [ ]:
type(s)
In [ ]:
s=str(123)
s
In [ ]:
n=int(s)
n
In [ ]:
int('123x')
In [ ]:
x=float('123.456E-7')
x

Метод format особенно полезен для вывода.

In [ ]:
'str: {}  int: {}  float: {}'.format(s,n,x)

Ширина поля 5 (больше, если не влезет); десятичный, шестнадцатиричный и двоичный форматы.

In [ ]:
print('''{:5d}
{:5x}
{:5b}'''.format(n,n,n))

Ширина поля 10 (больше, если не влезет); после десятичной точки 5 цифр; формат с фиксированной точкой или экспоненциальный.

In [ ]:
print('''{:10.5f}
{:10.5e}
{:10.5f}
{:10.5e}'''.format(x,x,1/x,1/x))

Списки

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

In [ ]:
l=[0,1,2,3,4,5,6,7,8,9]
l
In [ ]:
len(l)
In [ ]:
l[0]
In [ ]:
l[3]
In [ ]:
l[10]
In [ ]:
l[-1]
In [ ]:
l[-2]
In [ ]:
l[1:3]

Обратите внимание, что l[:3]+l[3:]==l.

In [ ]:
l[:3]
In [ ]:
l[3:]
In [ ]:
l[3:3]
In [ ]:
l[3:-2]
In [ ]:
l[:-2]

Списки являются изменяемыми объектами. Это сделано для эффективности. В списке может быть 1000000 элементов. Создавать его копию каждый раз, когда мы изменили один элемент, слишком дорого.

In [ ]:
l[3]='три'
l

Можно заменить какой-нибудь подсписок на новый список (в том числе другой длины).

In [ ]:
l[3:3]=[0]
l
In [ ]:
l[3:3]=[10,11,12]
l
In [ ]:
l[5:7]=[0,0,0,0]
l
In [ ]:
l[3:]=[]
l
In [ ]:
l[len(l):]=[3,4]
l

Некоторые из этих операций могут быть записаны в другой форме.

In [ ]:
l=[0,1,2,3,4,5,6,7]
del l[3]
l
In [ ]:
del l[3:5]
l
In [ ]:
l.insert(3,0)
l
In [ ]:
l.append(8)
l
In [ ]:
l.extend([9,10,11])
l

Элементы списка могут быть разных типов.

In [ ]:
l=[0,[1,2,3],'abc']
l[1][1]='x'
l

Когда мы пишем m=l, мы присваиваем переменной m ссылку на тот же объект, на который ссылается l. Поэтому, изменив этот объект (список) через l, мы увидим эти изменения и через m - ведь список всего один.

In [ ]:
l=[0,1,2,3,4,5]
m=l
l[3]='три'
m

Операция is проверяет, являются ли m и l одним и тем же объектом.

In [ ]:
m is l

Если мы хотим видоизменять m и l независимо, нужно присвоить переменной m не список l, а его копию. Тогда это будут два различных списка, просто в начальный момент они состоят из одних и тех же элементов. Для этого в питоне есть идиома: l[:] - это подсписок списка l от начала до конца, а подсписок всегда копируется.

In [ ]:
m=l[:]

Теперь m и l - два независимых объекта, имеющих равные значения.

In [ ]:
m is l
In [ ]:
m==l

Их можно менять независимо.

In [ ]:
l[3]=0
l
In [ ]:
m

Как и для строк, сложение списков означает конкатенацию, а умножение на целое число - повторение списка несколько раз. Операция in проверяет, содержится ли элемент в списке.

In [ ]:
[0,1,2]+[3,4,5]
In [ ]:
2*[0,1,2]
In [ ]:
l=[0,1,2]
l+=[3,4,5]
l
In [ ]:
2 in l

Простейший вид цикла в питоне - это цикл по элементам списка.

In [ ]:
for x in l:
    print(x)

Можно использовать цикл while. В этом примере он выполняется, пока список l не пуст. Этот цикл гораздо менее эффективен, чем предыдущий - в нём на каждом шаге меняется список l. Он тут приведён не для того, чтобы ему подражали, а просто чтобы показать синтаксис цикла while.

In [ ]:
while l:
    print(l[0])
    l=l[1:]
In [ ]:
l

Очень часто используются циклы по диапазонам целых чисел.

In [ ]:
for i in range(4):
    print(i)

Функция range(n) возвращает диапазон целых чисел от 0 до $n-1$ (всего $n$ штук) в виде специального объекта range, который можно использовать в for цикле. Можно превратить этот объект в список функцией list. Но этого делать не нужно, если только такой список не нужен для проведения каких-нибудь списковых операций. Число n может быть равно 1000000. Зачем занимать память под длинный список, если он не нужен? Для написания цикла достаточно короткого объекта range, который хранит только пределы.

In [ ]:
r=range(4)
r
In [ ]:
list(r)

Функции range можно передать первый параметр - нижний предел.

In [ ]:
for i in range(2,4):
    print(i)
In [ ]:
r=range(2,4)
r
In [ ]:
list(r)

Функция list превращает строку в список символов.

In [ ]:
l=list('абвгд')
l

Как написать цикл, если в его теле нужно использовать и номера элементов списка, и сами эти элементы? Первая идея, которая приходит в голову по аналогии с C - это использовать range.

In [ ]:
for i in range(len(l)):
    print(i,'  ',l[i])

Можно поступить наоборот - устроить цикл по элементам списка, а индексы вычислять.

In [ ]:
i=0
for x in l:
    print(i,'  ',x)
    i+=1

Оба этих способа не есть идиоматический питон. Более изящно использовать функцию enumerate, которая на каждом шаге возвращает пару из индекса i и i-го элемента списка.

In [ ]:
for i,x in enumerate(l):
    print(i,'  ',x)

Про такие пары мы поговорим в следующем параграфе.

Довольно часто удобно использовать цикл while True:, то есть пока рак на горе не свистнет, а выход (или несколько выходов) из него устраивать в нужном месте (или местах) при помощи break.

In [ ]:
while True:
    print(l[-1])
    l=l[:-1]
    if l==[]:
        break

Этот конкретный цикл - отнюдь не пример для подражания, он просто показывает синтаксис.

Можно строить список поэлементно.

In [ ]:
l=[]
for i in range(10):
    l.append(i**2)
l

Но более компактно и элегантно такой список можно создать при помощи генератора списка (list comprehension). К тому же это эффективнее - размер списка известен заранее, и не нужно много раз увеличивать его. Опытные питон-программисты используют генераторы списков везде, где это возможно (и разумно).

In [ ]:
[i**2 for i in range(10)]
In [ ]:
[[i,j] for i in range(3) for j in range(2)]

В генераторе списков могут присутствовать некоторые дополнительные элементы, хотя они используются реже. Например, в список-результат можно включить не все элементы.

In [ ]:
[i**2 for i in range(10) if i!=5]

Создадим список случайных целых чисел.

In [ ]:
from random import randint
In [ ]:
l=[randint(0,9) for i in range(10)]
l

Функция sorted возвращает отсортированную копию списка. Метод sort сортирует список на месте. Им можно передать дополнительный параметр - функцию, указывающую, как сравнивать элементы.

In [ ]:
sorted(l)
In [ ]:
l
In [ ]:
l.sort()
l

Аналогично, функция reversed возвращает обращённый список (точнее говоря, некий объект, который можно использовать в for цикле или превратить в список функцией list). Метод reverse обращает список на месте.

In [ ]:
list(reversed(l))
In [ ]:
l
In [ ]:
l.reverse()
l

Метод split расщепляет строку в список подстрок. По умолчанию расщепление производится по пустым промежуткам (последовательностям пробелов и символов tab и newline). Но можно передать ему дополнительный аргумент - подстроку-разделитель.

In [ ]:
s='abc \t def \n ghi'
l=s.split()
l

Чтобы напечатать элементы списка через запятую или какой-нибудь другой символ (или строку), очень полезен метод join. Он создаёт строку из всех элементов списка, разделяя их строкой-разделителем. Запрограммировать это в виде цикла было бы существенно длиннее, и такую программу было бы сложнее читать.

In [ ]:
s=', '.join(l)
s
In [ ]:
s.split(', ')
In [ ]: