Никита Волков
Кортежи (tuples) очень похожи на списки, но являются неизменяемыми. Как мы видели, использование изменяемых объектов может приводить к неприятным сюрпризам.
Кортежи пишутся в круглых скобках. Если элементов $>1$ или 0, это не вызывает проблем. Но как записать кортеж с одним элементом? Конструкция (x)
абсолютно легальна в любом месте любого выражения, и означает просто x
. Чтобы избежать неоднозначности, кортеж с одним элементом x
записывается в виде (x,)
.
In [1]:
(1, 2, 3)
Out[1]:
In [2]:
()
Out[2]:
In [3]:
(1,)
Out[3]:
Скобки ставить не обязательно, если кортеж - единственная вещь в правой части присваивания.
In [4]:
t = 1, 2, 3
t
Out[4]:
Работать с кортежами можно так же, как со списками. Нельзя только изменять их.
In [5]:
len(t)
Out[5]:
In [6]:
t[1]
Out[6]:
In [7]:
u = 4, 5
t + u
Out[7]:
In [8]:
2 * u
Out[8]:
В левой части присваивания можно написать несколько переменных через запятую, а в правой кортеж. Это одновременное присваивание значений нескольким переменным.
In [9]:
x, y = 1, 2
In [10]:
x
Out[10]:
In [11]:
y
Out[11]:
Сначала вычисляется кортеж в правой части, исходя из старых значений переменных (до этого присваивания). Потом одновременно всем переменным присваиваются новые значения из этого кортежа. Поэтому так можно обменять значения двух переменных.
In [12]:
x, y = y, x
In [13]:
x
Out[13]:
In [14]:
y
Out[14]:
Это проще, чем в других языках, где приходится использовать третью переменную.
В соответствии с математическими обозначениями, множества пишутся в фигурных скобках. Элемент может содержаться в множестве только один раз. Порядок элементов в множестве не имеет значения, поэтому питон их сортирует. Элементы множества могут быть любых типов.
In [15]:
s = {0, 1, 0, 5, 5, 1, 0}
s
Out[15]:
Принадлежит ли элемент множеству?
In [16]:
1 in s, 2 in s, 1 not in s
Out[16]:
Множество можно получить из списка, или строки, или любого объекта, который можно использовать в for
цикле (итерабельного).
In [17]:
l = [0, 1, 0, 5, 5, 1, 0]
set(l)
Out[17]:
In [18]:
set('абба')
Out[18]:
Как записать пустое множество? Только так.
In [19]:
set()
Out[19]:
Дело в том, что в фигурных скобках в питоне пишутся также словари (мы будем их обсуждать в следующем параграфе). Когда в них есть хоть один элемент, можно отличить словарь от множества. Но пустые фигурные скобки означают пустой словарь.
In [20]:
{}
Out[20]:
Работать с множествами можно как со списками.
In [21]:
len(s)
Out[21]:
In [22]:
for x in s:
print(x)
Это генератор множества (set comprehension).
In [23]:
{i for i in range(5)}
Out[23]:
Объединение множеств.
In [24]:
s2 = s | {2, 5}
s2
Out[24]:
Проверка того, является ли одно множество подмножеством другого.
In [25]:
s < s2, s > s2, s <= s2, s >= s2
Out[25]:
Пересечение.
In [26]:
s2 & {1, 2, 3}
Out[26]:
Разность и симметричная разность.
In [27]:
s2 - {1,3,5}
Out[27]:
In [28]:
s2 ^ {1,3,5}
Out[28]:
Множества (как и списки) являются изменяемыми объектами. Добавление элемента в множество и исключение из него.
In [29]:
s2.add(4)
s2
Out[29]:
In [30]:
s2.remove(1)
s2
Out[30]:
Как и в случае +=
, можно скомбинировать теоретико-множественную операцию с присваиванием.
In [31]:
s2 |= {1, 2}
s2
Out[31]:
Приведенные выше операции можно записывать и в другом стиле
In [32]:
x = set([1, 4, 2, 4, 2, 1, 3, 4])
print(x)
x.add(5) # добавление элемента
print(x)
x.pop() # удаление элемента
print(x)
print(x.intersection(set([2, 4, 6, 8]))) # Пересечение
print(x.difference(set([2, 4, 6, 8]))) # Разность
print(x.union(set([2, 4, 6, 8]))) # Объединение
print(x.symmetric_difference(set([2, 4, 6, 8]))) # Симметрическая разность
print(x.issubset(set([2, 4, 6, 8]))) # Является ли подмножеством
print(x.issubset(set(list(range(10)))))
print(x.issuperset(set([2, 4, 6, 8]))) # Является ли надмножеством
print(x.issuperset(set([2, 4])))
Существуют также неизменяемые множества. Этот тип данных называется frozenset
. Операции над такими множествами подобны обычным, только невозможно изменять их (добавлять и исключать элементы).
Словарь содержит пары ключ - значение (их порядок несущественен). Это один из наиболее полезных и часто используемых типов данных в питоне.
In [33]:
d = {'one': 1, 'two': 2, 'three': 3}
d
Out[33]:
Можно узнать значение, соответствующее некоторому ключу. Словари реализованы как хэш-таблицы, так что поиск даже в больших словарях очень эффективен. В языках низкого уровня (например, C) для построения хэш-таблиц требуется использовать внешние библиотеки и писать заметное количество кода. В скриптовых языках (perl, python, php) они уже встроены в язык, и использовать их очень легко.
In [34]:
d['two']
Out[34]:
In [35]:
d['four']
Можно проверить, есть ли в словаре данный ключ.
In [36]:
'one' in d, 'four' in d
Out[36]:
Можно присваивать значения как имеющимся ключам, так и отсутствующим (они добавятся к словарю).
In [37]:
d['one'] =- 1
d
Out[37]:
In [38]:
d['four'] = 4
d
Out[38]:
Длина - число ключей в словаре.
In [39]:
len(d)
Out[39]:
Можно удалить ключ из словаря.
In [40]:
del d['two']
d
Out[40]:
Метод get
, если он будет вызван с отсутствующим ключом, не приводит к ошибке, а возвращает специальный объект None
. Он используется всегда, когда необходимо указать, что объект отсутствует (в какой-то мере он аналогичен null
в C). Если передать методу get
второй аргумент - значение по умолчанию, то будет возвращаться это значение, а не None
.
In [41]:
d.get('one'), d.get('five')
Out[41]:
In [42]:
d.get('one', 0), d.get('five', 0)
Out[42]:
Словари обычно строят последовательно: начинают с пустого словаря, а затем добавляют ключи со значениями.
In [43]:
d = {}
d
Out[43]:
In [44]:
d['zero'] = 0
d
Out[44]:
In [45]:
d['one'] = 1
d
Out[45]:
А это генератор словаря (dictionary comprehension).
In [46]:
d = {i: i ** 2 for i in range(5)}
d
Out[46]:
Ключами могут быть любые неизменяемые объекты, например, целые числа, строки, кортежи.
In [47]:
d = {}
d[0, 0] = 1
d[0, 1] = 0
d[1, 0] = 0
d[1, 1] = -1
d
Out[47]:
In [48]:
d[0, 0] + d[1, 1]
Out[48]:
Словари, подобно спискам, можно использовать в for
циклах. Перебираются имеющиеся в словаре ключи (в каком-то непредсказуемом порядке).
In [49]:
d = {'one': 1, 'two': 2, 'three': 3}
for x in d:
print(x, ' ', d[x])
Метод keys
возвращает список ключей, метод values
- список соответствующих значений (в том же порядке), а метод items
- список пар (ключ,значение). Точнее говоря, это не списки, а некоторые объекты, которые можно использовать в for
циклах или превратить в списки функцией list
. Если хочется написать цикл по упорядоченному списку ключей, то можно использовать sorted(d.keys))
.
In [50]:
d.keys(), d.values(), d.items()
Out[50]:
In [51]:
for x in sorted(d.keys()):
print(x, ' ', d[x])
In [52]:
for x, y in d.items():
print(x, ' ', y)
In [53]:
del x, y
Что есть истина? И что есть ложь? Подойдём к этому философскому вопросу экспериментально.
In [54]:
bool(False), bool(True)
Out[54]:
In [55]:
bool(None)
Out[55]:
In [56]:
bool(0), bool(123)
Out[56]:
In [57]:
bool(''), bool(' ')
Out[57]:
In [58]:
bool([]), bool([0])
Out[58]:
In [59]:
bool(set()), bool({0})
Out[59]:
In [60]:
bool({}), bool({0: 0})
Out[60]:
На выражения, стоящие в булевых позициях (после if
, elif
и while
), неявно напускается функция bool
. Некоторые объекты интерпретируются как False
: число 0, пустая строка, пустой список, пустое множество, пустой словарь, None
и некоторые другие. Все остальные объекты интерпретируются как True
. В операторах if
или while
очень часто используется список, словарь или что-нибудь подобное, что означает делай что-то если этот список (словарь и т.д.) не пуст.
Заметим, что число с плавающей точкой 0.0 тоже интерпретируется как False
. Это использовать категорически не рекомендуется: вычисления с плавающей точкой всегда приближённые, и неизвестно, получите Вы 0.0 или 1.234E-12
.
Лучше напишите if abs(x)<epsilon:
.
Это простейшая в мире функция. Она не имеет параметров, ничего не делает и ничего не возвращает. Оператор pass
означает "ничего не делай"; он используется там, где синтаксически необходим оператор, а делать ничено не нужно (после if
или elif
, после def
и т.д.).
In [61]:
def f():
pass
In [62]:
f
Out[62]:
In [63]:
pass
In [64]:
type(f)
Out[64]:
In [65]:
r = f()
print(r)
Эта функция более полезна: она имеет параметр и что-то возвращает.
In [66]:
def f(x):
return x + 1
In [67]:
f(1), f(1.0)
Out[67]:
In [68]:
f('abc')
Если у функции много параметров, то возникает желание вызывать её попроще в наиболее часто встречающихся случаях. Для этого в операторе def
можно задать значения некоторых параметров по умолчанию (они должны размещаться в конце списка параметров). При вызове необходимо указать все обязательные параметры (у которых нет значений по умолчанию), а необязательные можно и не указывать. Если при вызове указывать параметры в виде имя=значение
, то это можно делать в любом порядке. Это гораздо удобнее, чем вспоминать, является данный параметр восьмым или девятым при вызове какой-нибудь сложной функции. Обратите внимание, что в конструкции имя=значение
не ставятся пробелы между символом =
.
In [69]:
def f(x, a=0, b='b'):
print(x, ' ', a, ' ', b)
In [70]:
f(1.0)
In [71]:
f(1.0, 1)
In [72]:
f(1.0, b='a')
In [73]:
f(1.0, b='a', a=2)
In [74]:
f(a=2, x=2.0)
Переменные, использующиеся в функции, являются локальными. Присваивание им не меняет значений глобальных переменных с такими же именами.
In [75]:
a = 1
In [76]:
def f():
a = 2
return a
In [77]:
f()
Out[77]:
In [78]:
a
Out[78]:
Если в функции нужно использовать какие-нибудь глобальные переменные, их нужно описать как global
.
In [79]:
def f():
global a
a = 2
return a
In [80]:
f()
Out[80]:
In [81]:
a
Out[81]:
Пространство имён устанавливает соответствие между именами переменных и объектами - их значениями. Есть пространство имён локальных переменных функции, пространство имён глобальных переменных программы и пространство имён встроенных функций языка питон. Для реализации пространств имён используются словари.
Если функции передаётся в качестве аргумента какой-нибудь изменяемый объект, и функция его изменяет, то это изменение будет видно снаружи после этого вызова. Мы уже обсуждали эту ситуацию, когда две переменные (в данном случае глобальная переменная и параметр функции) указывают на один и тот же изменяемый объект объект.
In [82]:
def f(x, l):
l.append(x)
return l
In [83]:
l = [1, 2, 3]
f(0, l)
Out[83]:
In [84]:
l
Out[84]:
Если в качестве значения какого-нибудь параметра по умолчанию используется изменяемый объект, то это может приводить к неожиданным последствиям. В данном случае исполнение определения функции приводит к созданию двух объектов: собственно функции и объекта-списка, первоначально пустого, который используется для инициализации параметра функции при вызове. Функция изменяет этот объект. При следующем вызове он опять используется для инициализации параметра, но его значение уже изменилось.
In [85]:
def f(x, l=[]):
l.append(x)
return l
In [86]:
f(0)
Out[86]:
In [87]:
f(1)
Out[87]:
In [88]:
f(2)
Out[88]:
Чтобы избежать таких сюрпризов, в качестве значений по умолчанию лучше использовать только неизменяемые объекты.
In [89]:
def f(x, l=None):
if l is None:
l = []
l.append(x)
return l
In [90]:
f(0)
Out[90]:
In [91]:
f(1)
Out[91]:
In [92]:
f(2, [0, 1])
Out[92]:
Эта функция имеет один обязательный параметр плюс произвольное число необязательных. При вызове все такие дополнительные аргументы объединяются в кортеж, который функция может использовать по своему усмотрению.
In [93]:
def f(x, *l):
print(x, ' ', l)
In [94]:
f(0)
In [95]:
f(0, 1)
In [96]:
f(0, 1, 2)
In [97]:
f(0, 1, 2, 3)
Звёздочку можно использовать и при вызове функции. Можно заранее построить список (или кортеж) аргументов, а потом вызвать функцию с этими аргументами.
In [98]:
l=[1, 2]
c=('a', 'b')
f(*l, 0, *c)
Такую распаковку из списков и кортежей можно использовать не только при вызове функции, но и при построении списка или кортежа.
In [99]:
(*l, 0, *c)
Out[99]:
In [100]:
[*l, 0, *c]
Out[100]:
In [101]:
[*l, 3]
Out[101]:
Эта функция имеет два обязательных параметра плюс произвольное число необязательных ключевых параметров. При вызове они должны задаваться в виде имя=значение
. Они собираются в словарь, который функция может использовать по своему усмотрению.
In [102]:
def f(x, y, **d):
print(x, ' ', y, ' ', d)
In [103]:
f(0, 1, foo=2, bar=3)
Двойную звёздочку можно использовать и при вызове функции. Можно заранее построить словарь аргументов, сопоставляющий значения именам параметров, а потом вызвать функцию с этими ключевыми аргументами.
In [104]:
d={'foo': 2, 'bar': 3}
f(0, 1, **d)
In [105]:
d['x'] = 0
d['y'] = 1
f(**d)
Вот любопытный способ построить словарь с ключами-строками.
In [106]:
def f(**d):
return d
In [107]:
f(x=0, y=1, z=2)
Out[107]:
Двойную звёздочку можно использовать не только при вызове функции, но и при построении словаря.
In [108]:
d={0: 'a', 1: 'b'}
{**d, 2: 'c'}
Out[108]:
Вот простой способ объединить два словаря.
In [109]:
d1 = {0: 'a', 1: 'b'}
d2 = {2: 'c', 3: 'd'}
{**d1, **d2}
Out[109]:
Если один и тот же ключ встречается несколько раз, следующее значение затирает предыдущее.
In [110]:
d2 = {1: 'B', 2: 'C'}
{**d1, 3: 'D', **d2, 3: 'd'}
Out[110]:
Это наиболее общий вид списка параметров функции. Сначала идут обязательные параметры (в данном случае два), затем произвольное число необязательных (при вызове они будут объединены в кортеж), а затем произвольное число ключевых параметров (при вызове они будут объединены в словарь).
In [111]:
def f(x, y, *l, **d):
print(x, ' ', y, ' ', l, ' ', d)
In [112]:
f(0, 1, 2, 3, foo=4, bar=5)
В питоне функции являются гражданами первого сорта. Они могут присутствовать везде, где допустимы объекты других типов - среди элементов списков, значений в словарях и т.д.
In [113]:
def f0(x):
return x + 2
In [114]:
def f1(x):
return 2 * x
In [115]:
l = [f0, f1]
l
Out[115]:
In [116]:
x = 2.0
n = 1
l[n](x)
Out[116]:
Если Вы пишете функцию не для того, чтобы один раз её вызвать и навсегда забыть, то нужна документация, объясняющая, что эта функция делает. Для этого сразу после строчки def
пишется строка. Она называется док-строкой, и сохраняется при трансляции исходного текста на питоне в байт-код (в отличие от комментариев, которые при этом отбрасываются). Обычно эта строка заключается в тройные кавычки и занимает несколько строчек. Док-строка доступна как атрибут __doc__
функции, и используется функцией help
. Вот пример культурно написанной функции, вычисляющей $n$-е число Фибоначчи.
Для проверки типов аргументов, переданных функции, удобно использовать оператор assert
. Если условие в нём истинно, всё в порядке, и он ничего не делает; если же оно ложно, выдаётся сообщение об ошибке.
In [117]:
def fib(n):
'''вычисляет n-е число Фибоначчи'''
assert type(n) is int and n>0
if n <= 2:
return 1
x, y = 1, 1
for i in range(n - 2):
x, y = y, x + y
return y
In [118]:
fib.__doc__
Out[118]:
In [119]:
help(fib)
В jupyter-ноутбуке к документации можно обращаться более удобным способом
In [120]:
fib?
In [121]:
[fib(n) for n in range(1, 10)]
Out[121]:
In [122]:
fib(-1)
In [123]:
fib(2.0)
In [124]:
x = zip(range(5), range(0, 10, 2))
print(list(x))
map применяет функию к каждому элементу массива
In [125]:
x = map(lambda tmp: tmp ** 2, range(5))
print(list(x))
sorted --- сортировка
In [126]:
x = list(zip([7, 3, 4, 4, 5, 3, 9], ['a', 'n', 'n', 'a', 'k', 'n', 'a']))
# сначала сортировка по букве по алфавиту, потом сортировка по убыванию по числу
x = sorted(x, key=lambda element: (element[1], -element[0]))
print(list(x))