Никита Волков
При программировании на питоне первые полезные результаты начинают получаться минут через 5 после начала работы над проектом. Не надо изобретать никаких велосипедов: всё, что может понадобиться, доступно или в стандартной библиотеке, или в одном из многочисленных дополнительных пакетов. Программы получаются элегантные и лаконичные, их легко и приятно писать и читать. Имеется широкий выбор высококачественных инструментов программиста: интегрированные среды разработки, отладчики и средства тестирования и т.д. Питон прекрасно приспособлен для написания больших и сложных программных комплексов. И всё это совершенно не зависит от железа и операционной системы (конечно, если не вызывать низкоуровневых системно-зависимыех библиотек).
Главный минус - питон, мягко говоря, не славится потрясающим быстродействием. Писать программы сложных вычислений на питоне, конечно, глупо. Но есть ряд способов обойти это ограничение. Если программа занимается регулярной обработкой больших массивов чисел с плавающей точкой, то использование numpy радикально повышает её быстродействие. Пакет numpy и его расширение scipy фактически делают matlab ненужным - дают ту же функциональность с более приятным языком программирование. Есть ряд пакетов для построения высококачественных графиков, например, matplotlib. Другое средство повышения быстродействия - cython. Пишется программа, выглядящая почти как питонская, но со вставленными статичестими декларациями типов. cython транслирует её в исходный код на C, что часто даёт быстродействие, сравнимое с вручную написанным C. Программа на cython-е может свободно вызывать функции из библиотек на C; её можно использовать из обычного питона. Ну и наконец можно написать критически важные для быстродействия системы в целом вычисления на другом языке (C например), и вызывать эти внешние программы из питона. Питон при этом выполняет роль клея - реализует логику высокого уровня, системно-независимый GUI и т.д.
Питон - сильно типизированный язык с динамической типизацией. Всё, с чем работает программа - объекты; каждый из них принадлежит определённому типу. Если программа пытается выполнить какую-то операцию над объектом такого типа, который не поддерживает эту операцию, произойдёт ошибка времени выполнения. Описаний переменных в питоне нет. Одна и та же переменная может в разные моменты иметь значения - объекты разных типов. Ошибка в типах может проявиться много времени спустя после того, как программа сдана в эксплуатацию, особенно если она происходит в редко используемом участке кода. Язык допускает ситуацию, когда мы сначала присвоим какой-то переменной целое число, потом строку, потом ещё что-то, но это плохой стиль. Переменная заводится для одного конкретного использования, и естественно, чтобы её значения в любой момент подходили для этого использования и имели одинаковый тип. В исходный текст на питоне 3.5 можно включить (необязательные) аннотации типов переменных и функций, и прогнать её через программу статической проверки типов.
Обязательно прочитайте инструкцию по стилю кода: http://pep8.ru/doc/pep8/
Желательно соблюдать стиль кода. Однако, поскольку у нас не курс программирование, то полное соблюдение стиля не требуется. Перечислим кратко основные правила:
{
и }
в C и begin
и end
в Паскале. Блоки кода разделяются пробелами.X
, лучше назвать ее sample
. В качестве разделителей используйте подчеркивания. В редких случаях можно и отступать от этого правила, если обозначения понятны из решаемой задачи.a=b
в аргументах функции. Примеры будут далее.код [ровно 2 пробела] # [ровно 1 пробел] комментарий
Отдельно стоит отметить, что код должен быть понятен проверяющему. В частности, должны использоваться понятные названия переменных и присутствовать подробные комментарии. Если проверяющий не поймет код, оценка за задание может быть снижена, и будет пичалька.
Арифметические операции имеют ожидаемые приоритеты. При необходимости используются скобки.
In [1]:
1 + 2 * 3
Out[1]:
In [2]:
(1 + 2) * 3
Out[2]:
Возведение целого числа в целую степень даёт целое число, если показатель степени $\ge0$, и число с плавающей точкой, если он $<0$. Так что тип результата невозможно определить статически, если значение переменной n
неизвестно.
In [3]:
n = 3
2 ** n
Out[3]:
In [4]:
n = -3
2 ** n
Out[4]:
Арифметические операции можно применять к целым и числам с плавающей точкой в любых сочетаниях.
In [5]:
n + 1.0
Out[5]:
Деление целых чисел всегда даёт результат с плавающей точкой, даже если они делятся нацело. Операторы //
и %
дают целое частное и остаток.
In [6]:
7 / 4
Out[6]:
In [7]:
7 // 4
Out[7]:
In [8]:
7 % 4
Out[8]:
In [9]:
4 / 2
Out[9]:
Если Вы попытаетесь использовать переменную, которой не присвоено никакого значения, то получите сообщение об ошибке.
In [10]:
x
x+=1
означает x=x+1
, аналогично для других операций. В питоне строго различаются операторы (например, присваивание) и выражения, так что таких операций, как ++
в C, нет. Хотя вызов функции в выражении может приводить к побочным эффектам.
In [11]:
x = 1
x += 1
print(x)
In [12]:
x *= 2
print(x)
Удаление объекта
In [13]:
del x
x
In [14]:
x = 4
Любопытная особенность питона: можно использовать привычные из математики сравнения вроде $x<y<z$, которые в других языках пришлось бы записывать как x<y and y<z
.
In [15]:
1 < 2 <= 2
Out[15]:
In [16]:
1 < 2 < 2
Out[16]:
Логические выражения можно комбинировать с помощью and
и or
(эти операции имеют более низкий приоритет, чем сравнения). Если результат уже ясен из первого операнда, второй операнд не вычисляется. А вот так выглядит оператор if
.
In [17]:
if 1 < 2 and x < 3:
print('T')
else:
print('F')
In [18]:
if 1 < 2 or x < 3:
print('T')
else:
print('F')
После строчки, заканчивающейся :
, можно писать последовательность операторов с одинаковым отступом (больше, чем у строчки 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]:
Обычно в начале пишется основное выражение, оно защищается условием в if
, а после else
пишется исключительный случай.
В питоне немного встроенных функций. Большинство надо импортировать. Элементарные функции импортируют из модуля math
. Заниматься импортозамещением (писать свою реализацию синуса) не нужно.
In [21]:
from math import sin, pi
In [22]:
pi
Out[22]:
In [23]:
round(pi, 2)
Out[23]:
In [24]:
print('{:.2f}'.format(pi))
In [25]:
sin(pi / 6)
Out[25]:
Любой объект имеет тип.
In [26]:
type(2)
Out[26]:
In [27]:
type(int)
Out[27]:
In [28]:
type(type)
Out[28]:
In [29]:
type(2.1)
Out[29]:
In [30]:
type(True)
Out[30]:
Имена типов по совместительству являются функциями, преобразующими в этот тип объекты других типов (если такое преобразование имеет смысл).
In [31]:
float(2)
Out[31]:
In [32]:
int(2.0)
Out[32]:
In [33]:
int(2.9)
Out[33]:
In [34]:
int(-2.9)
Out[34]:
Преобразование числа с плавающей точкой в целое производится путём отбрасывания дробной части, а не округления.
Питон хорошо приспособлен для работы с текстовой информацией. В нём есть много операций для работы со строками, несколько способов записи строк (удобных в разных случаях). В современных версиях питона (3.x) строки юникодные, т.е. они могут содержать одновременно русские и греческие буквы, немецкие умляуты и китайские иероглифы.
In [35]:
s = 'Какая-нибудь строка \u00F6 \u03B1 \u2230 \u342A'
print(s)
In [36]:
'Эта строка может содержать " внутри'
Out[36]:
In [37]:
"Эта строка может содержать ' внутри"
Out[37]:
In [38]:
s = 'Эта содержит и \', и \"'
print(s)
In [39]:
s = """Строка,
занимающая
несколько
строчек
"""
print(s)
In [40]:
s == "Строка,\nзанимающая\nнесколько\nстрочек\n"
Out[40]:
Несколько строковых литералов, разделённых лишь пробелами, слипаются в одну строку. Подчеркнём ещё раз: это должны быть литералы, а не переменные со строковыми значениями. Такой способ записи особенно удобен, когда нужно передать длинную строку при вызове функции.
In [41]:
s = 'Такие ' 'строки ' 'слипаются'
print(s)
In [42]:
print('Такие\n'
'строки\n'
'слипаются')
В питоне нет специального типа char
, его роль играют строки длины 1. Функция ord
возвращает (юникодный) номер символа, а обратная ей функция chr
возвращает символ (строку длины 1).
In [43]:
n = ord('а')
n
Out[43]:
In [44]:
chr(n)
Out[44]:
Функция len
возвращает длину строки. Она применима не только к строкам, но и к спискам, словарям и многим другим типам, про объекты которых разумно спрашивать, какая у них длина.
In [45]:
s = '0123456789'
len(s)
Out[45]:
Символы в строке индексируются с 0. Отрицательные индексы используются для счёта с конца: s[-1]
- последний символ в строке, и т.д.
In [46]:
s[0]
Out[46]:
In [47]:
s[3]
Out[47]:
In [48]:
s[-1]
Out[48]:
In [49]:
s[-2]
Out[49]:
Можно выделить подстроку, указав диапазон индексов. Подстрока включает символ, соответствующий началу диапазона, но не включает соответствующий концу. Удобно представлять себе, что индексы соответствуют положениям между символами строки. Тогда подстрока s[n:m]
будет расположена между индексами n
и m
.
In [50]:
s[1:3]
Out[50]:
In [51]:
s[:3]
Out[51]:
In [52]:
s[3:]
Out[52]:
In [53]:
s[:-1]
Out[53]:
In [54]:
s[3:-2]
Out[54]:
In [55]:
s[::2]
Out[55]:
Если не указано начало диапазона, подразумевается от начала строки; если не указан его конец - до конца строки.
Строки являются неизменяемым типом данных. Построив строку, нельзя изменить в ней один или несколько символов. Операции над строками строят новые строки - результаты, не меняя своих операндов. Сложение строк означает конкатенацию, а умножение на целое число (с любой стороны) - повторение строки несколько раз.
In [56]:
s = 'abc'; t = 'def'
s + t
Out[56]:
In [57]:
s * 3
Out[57]:
Операция in
проверяет, содержится ли символ (или подстрока) в строке.
In [58]:
'a' in s
Out[58]:
In [59]:
'd' in s
Out[59]:
In [60]:
'ab' in s
Out[60]:
In [61]:
'b' not in s
Out[61]:
У объектов типа строка есть большое количество методов. Метод lstrip
удаляет все whitespace-символы (пробел, tab
, newline
) в начале строки; rstrip
- в конце; а strip
- с обеих сторон. Им можно передать необязательный аргумент - символы, которые нужно удалять.
In [62]:
' строка '.lstrip()
Out[62]:
In [63]:
' строка '.rstrip()
Out[63]:
In [64]:
' строка '.strip()
Out[64]:
lower
и upper
переводят все буквы в маленькие и заглавные.
In [65]:
'СтРоКа'.lower()
Out[65]:
In [66]:
'СтРоКа'.upper()
Out[66]:
Проверки: буквы (маленькие и заглавные), цифры, пробелы.
In [67]:
'АбВг'.isalpha()
Out[67]:
In [68]:
'абвг'.islower()
Out[68]:
In [69]:
'АБВГ'.isupper()
Out[69]:
In [70]:
'0123'.isdigit()
Out[70]:
In [71]:
' \t\n'.isspace()
Out[71]:
Строки имеют тип str
.
In [72]:
type(s)
Out[72]:
In [73]:
s = str(123)
s
Out[73]:
In [74]:
n = int(s)
n
Out[74]:
In [75]:
int('123x')
In [76]:
x = float('123.456E-7')
x
Out[76]:
Метод format
особенно полезен для вывода.
In [77]:
'str: {} int: {} float: {}'.format(s, n, x)
Out[77]:
Ширина поля 5 (больше, если не влезет); десятичный, шестнадцатиричный и двоичный форматы.
In [78]:
print('''{:5d}
{:5x}
{:5b}'''.format(n, n, n))
Ширина поля 10 (больше, если не влезет); после десятичной точки 5 цифр; формат с фиксированной точкой или экспоненциальный.
In [79]:
print('''{:10.5f}
{:10.5e}
{:10.5f}
{:10.5e}'''.format(x, x, 1 / x, 1 / x))
In [80]:
l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l
Out[80]:
In [81]:
len(l)
Out[81]:
In [82]:
l[0]
Out[82]:
In [83]:
l[3]
Out[83]:
In [84]:
l[10]
In [85]:
l[-1]
Out[85]:
In [86]:
l[-2]
Out[86]:
In [87]:
l[1:3]
Out[87]:
Обратите внимание, что l[:3]+l[3:]==l
.
In [88]:
l[:3]
Out[88]:
In [89]:
l[3:]
Out[89]:
In [90]:
l[3:3]
Out[90]:
In [91]:
l[3:-2]
Out[91]:
In [92]:
l[:-2]
Out[92]:
Списки являются изменяемыми объектами. Это сделано для эффективности. В списке может быть 1000000 элементов. Создавать его копию каждый раз, когда мы изменили один элемент, слишком дорого.
In [93]:
l[3] = 'три'
l
Out[93]:
Можно заменить какой-нибудь подсписок на новый список (в том числе другой длины).
In [94]:
l[3:3] = [0]
l
Out[94]:
In [95]:
l[3:3] = [10, 11, 12]
l
Out[95]:
In [96]:
l[5:7] = [0, 0, 0, 0]
l
Out[96]:
In [97]:
l[3:] = []
l
Out[97]:
In [98]:
l[len(l):] = [3, 4]
l
Out[98]:
Некоторые из этих операций могут быть записаны в другой форме.
In [99]:
l = [0, 1, 2, 3, 4, 5, 6, 7]
del l[3]
l
Out[99]:
In [100]:
del l[3:5]
l
Out[100]:
In [101]:
l.insert(3, 0)
l
Out[101]:
In [102]:
l.append(8)
l
Out[102]:
In [103]:
l.extend([9, 10, 11])
l
Out[103]:
Элементы списка могут быть разных типов.
In [104]:
l = [0, [1, 2, 3], 'abc']
l[1][1] = 'x'
l
Out[104]:
Когда мы пишем m=l
, мы присваиваем переменной m
ссылку на тот же объект, на который ссылается l
. Поэтому, изменив этот объект (список) через l
, мы увидим эти изменения и через m
- ведь список всего один.
In [105]:
l = [0, 1, 2, 3, 4, 5]
m = l
l[3] = 'три'
m
Out[105]:
Операция is
проверяет, являются ли m
и l
одним и тем же объектом.
In [106]:
m is l
Out[106]:
Если мы хотим видоизменять m
и l
независимо, нужно присвоить переменной m
не список l
, а его копию. Тогда это будут два различных списка, просто в начальный момент они состоят из одних и тех же элементов. Для этого в питоне есть идиома: l[:]
- это подсписок списка l
от начала до конца, а подсписок всегда копируется.
In [107]:
m = l[:]
Теперь m
и l
- два независимых объекта, имеющих равные значения.
In [108]:
m is l
Out[108]:
In [109]:
m == l
Out[109]:
Их можно менять независимо.
In [110]:
l[3] = 0
l
Out[110]:
In [111]:
m
Out[111]:
Как и для строк, сложение списков означает конкатенацию, а умножение на целое число - повторение списка несколько раз. Операция in
проверяет, содержится ли элемент в списке.
In [112]:
[0, 1, 2] + [3, 4, 5]
Out[112]:
In [113]:
2 * [0, 1, 2]
Out[113]:
In [114]:
l = [0, 1, 2]
l += [3, 4, 5]
l
Out[114]:
In [115]:
2 in l
Out[115]:
Простейший вид цикла в питоне - это цикл по элементам списка.
In [116]:
for x in l:
print(x)
Можно использовать цикл while
. В этом примере он выполняется, пока список l
не пуст. Этот цикл гораздо менее эффективен, чем предыдущий - в нём на каждом шаге меняется список l
. Он тут приведён не для того, чтобы ему подражали, а просто чтобы показать синтаксис цикла while
.
In [117]:
while l:
print(l[0])
l = l[1:]
In [118]:
l
Out[118]:
Очень часто используются циклы по диапазонам целых чисел.
In [119]:
for i in range(4):
print(i)
Функция range(n)
возвращает диапазон целых чисел от 0 до $n-1$ (всего $n$ штук) в виде специального объекта range
, который можно использовать в for
цикле. Можно превратить этот объект в список функцией list
. Но этого делать не нужно, если только такой список не нужен для проведения каких-нибудь списковых операций. Число n
может быть равно 1000000. Зачем занимать память под длинный список, если он не нужен? Для написания цикла достаточно короткого объекта range
, который хранит только пределы.
In [120]:
r = range(4)
r
Out[120]:
In [121]:
list(r)
Out[121]:
Функции range
можно передать первый параметр - нижний предел.
In [122]:
for i in range(2, 4):
print(i)
In [123]:
r = range(2, 4)
r
Out[123]:
In [124]:
list(r)
Out[124]:
Можно также передать шаг третьим аргументом
In [125]:
for i in range(1, 10, 2):
print(i)
In [126]:
r = range(1, 10, 2)
r
Out[126]:
In [127]:
list(r)
Out[127]:
Функция list
превращает строку в список символов.
In [128]:
l = list('абвгд')
l
Out[128]:
Как написать цикл, если в его теле нужно использовать и номера элементов списка, и сами эти элементы? Первая идея, которая приходит в голову по аналогии с C - это использовать range
.
In [129]:
for i in range(len(l)):
print(i, ' ', l[i])
Можно поступить наоборот - устроить цикл по элементам списка, а индексы вычислять.
In [130]:
i = 0
for x in l:
print(i, ' ', x)
i += 1
Оба этих способа не есть идиоматический питон. Более изящно использовать функцию enumerate
, которая на каждом шаге возвращает пару из индекса i
и i
-го элемента списка.
In [131]:
for i, x in enumerate(l):
print(i, ' ', x)
Про такие пары мы поговорим в следующем параграфе.
Довольно часто удобно использовать цикл while True:
, то есть пока рак на горе не свистнет, а выход (или несколько выходов) из него устраивать в нужном месте (или местах) при помощи break
.
In [132]:
while True:
print(l[-1])
l = l[:-1]
if l == []:
break
Этот конкретный цикл - отнюдь не пример для подражания, он просто показывает синтаксис.
Можно строить список поэлементно.
In [133]:
l = []
for i in range(10):
l.append(i ** 2)
l
Out[133]:
Но более компактно и элегантно такой список можно создать при помощи генератора списка (list comprehension). К тому же это эффективнее - размер списка известен заранее, и не нужно много раз увеличивать его. Опытные питон-программисты используют генераторы списков везде, где это возможно (и разумно).
In [134]:
[i ** 2 for i in range(10)]
Out[134]:
In [135]:
[[i, j] for i in range(3) for j in range(2)]
Out[135]:
В генераторе списков могут присутствовать некоторые дополнительные элементы, хотя они используются реже. Например, в список-результат можно включить не все элементы.
In [136]:
[i ** 2 for i in range(10) if i != 5]
Out[136]:
Создадим список случайных целых чисел.
In [137]:
from random import randint
In [138]:
l = [randint(0, 9) for i in range(10)]
l
Out[138]:
Функция sorted
возвращает отсортированную копию списка. Метод sort
сортирует список на месте. Им можно передать дополнительный параметр - функцию, указывающую, как сравнивать элементы.
In [139]:
sorted(l)
Out[139]:
In [140]:
l
Out[140]:
In [141]:
l.sort()
l
Out[141]:
Аналогично, функция reversed
возвращает обращённый список (точнее говоря, некий объект, который можно использовать в for
цикле или превратить в список функцией list
). Метод reverse
обращает список на месте.
In [142]:
list(reversed(l))
Out[142]:
In [143]:
l
Out[143]:
In [144]:
l.reverse()
l
Out[144]:
Метод split
расщепляет строку в список подстрок. По умолчанию расщепление производится по пустым промежуткам (последовательностям пробелов и символов tab
и newline
). Но можно передать ему дополнительный аргумент - подстроку-разделитель.
In [145]:
s = 'abc \t def \n ghi'
l = s.split()
l
Out[145]:
Чтобы напечатать элементы списка через запятую или какой-нибудь другой символ (или строку), очень полезен метод join
. Он создаёт строку из всех элементов списка, разделяя их строкой-разделителем. Запрограммировать это в виде цикла было бы существенно длиннее, и такую программу было бы сложнее читать.
In [146]:
s = ', '.join(l)
s
Out[146]:
In [147]:
s.split(', ')
Out[147]: