питон

Кортежи

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

Кортежи пишутся в круглых скобках. Если элементов $>1$ или 0, это не вызывает проблем. Но как записать кортеж с одним элементом? Конструкция (x) абсолютно легальна в любом месте любого выражения, и означает просто x. Чтобы избежать неоднозначности, кортеж с одним элементом x записывается в виде (x,).

In [1]:
(1,2,3)
Out[1]:
(1, 2, 3)
In [2]:
()
Out[2]:
()
In [3]:
(1,)
Out[3]:
(1,)

Скобки ставить не обязательно, если кортеж - единственная вещь в правой части присваивания.

In [4]:
t=1,2,3
t
Out[4]:
(1, 2, 3)

Работать с кортежами можно так же, как со списками. Нельзя только изменять их.

In [5]:
len(t)
Out[5]:
3
In [6]:
t[1]
Out[6]:
2
In [7]:
u=4,5
t+u
Out[7]:
(1, 2, 3, 4, 5)
In [8]:
2*u
Out[8]:
(4, 5, 4, 5)

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

In [9]:
x,y=1,2
In [10]:
x
Out[10]:
1
In [11]:
y
Out[11]:
2

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

In [12]:
x,y=y,x
In [13]:
x
Out[13]:
2
In [14]:
y
Out[14]:
1

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

Множества

В соответствии с математическими обозначениями, множества пишутся в фигурных скобках. Элемент может содержаться в множестве только один раз. Порядок элементов в множестве не имеет значения, поэтому питон их сортирует. Элементы множества могут быть любых типов. Множества используются существенно реже, чем списки. Но иногда они бывают весьма полезны. Например, когда я собирался делать апгрейд системы на сервере, я написал на питоне программу, которая строила множество пакетов, установленных в системе до апгрейда; множество пакетов, имеющихся на инсталляционных CD; имеющихся на основных сайтах с дополнительными пакетами, и т.д. И она мне помогла восстановить функциональность после апгрейда.

In [15]:
s={0,1,0,5,5,1,0}
s
Out[15]:
{0, 1, 5}

Принадлежит ли элемент множеству?

In [16]:
1 in s, 2 in s, 1 not in s
Out[16]:
(True, False, False)

Множество можно получить из списка, или строки, или любого объекта, который можно использовать в for цикле (итерабельного).

In [17]:
l=[0,1,0,5,5,1,0]
set(l)
Out[17]:
{0, 1, 5}
In [18]:
set('абба')
Out[18]:
{'а', 'б'}

Как записать пустое множество? Только так.

In [19]:
set()
Out[19]:
set()

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

In [20]:
{}
Out[20]:
{}

Работать с множествами можно как со списками.

In [21]:
len(s)
Out[21]:
3
In [22]:
for x in s:
    print(x)
0
1
5

Это генератор множества (set comprehension).

In [23]:
{i for i in range(5)}
Out[23]:
{0, 1, 2, 3, 4}

Объединение множеств.

In [24]:
s2=s|{2,5}
s2
Out[24]:
{0, 1, 2, 5}

Проверка того, является ли одно множество подмножеством другого.

In [25]:
s<s2, s>s2, s<=s2, s>=s2
Out[25]:
(True, False, True, False)

Пересечение.

In [26]:
s2&{1,2,3}
Out[26]:
{1, 2}

Разность и симметричная разность.

In [27]:
s2-{1,3,5}
Out[27]:
{0, 2}
In [28]:
s2^{1,3,5}
Out[28]:
{0, 2, 3}

Множества (как и списки) являются изменяемыми объектами. Добавление элемента в множество и исключение из него.

In [29]:
s2.add(4)
s2
Out[29]:
{0, 1, 2, 4, 5}
In [30]:
s2.remove(1)
s2
Out[30]:
{0, 2, 4, 5}

Как и в случае +=, можно скомбинировать теоретико-множественную операцию с присваиванием.

In [31]:
s2|={1,2}
s2
Out[31]:
{0, 1, 2, 4, 5}

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

Словари

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

In [32]:
d={'one':1,'two':2,'three':3}
d
Out[32]:
{'one': 1, 'three': 3, 'two': 2}

Можно узнать значение, соответствующее некоторому ключу. Словари реализованы как хэш-таблицы, так что поиск даже в больших словарях очень эффективен. В языках низкого уровня (например, C) для построения хэш-таблиц требуется использовать внешние библиотеки и писать заметное количество кода. В скриптовых языках (perl, python, php) они уже встроены в язык, и использовать их очень легко.

In [33]:
d['two']
Out[33]:
2
In [34]:
d['four']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-34-a0944cd0c15b> in <module>()
----> 1 d['four']

KeyError: 'four'

Можно проверить, есть ли в словаре данный ключ.

In [35]:
'one' in d, 'four' in d
Out[35]:
(True, False)

Можно присваивать значения как имеющимся ключам, так и отсутствующим (они добавятся к словарю).

In [36]:
d['one']=-1
d
Out[36]:
{'one': -1, 'three': 3, 'two': 2}
In [37]:
d['four']=4
d
Out[37]:
{'four': 4, 'one': -1, 'three': 3, 'two': 2}

Длина - число ключей в словаре.

In [38]:
len(d)
Out[38]:
4

Можно удалит ключ из словаря.

In [39]:
del d['two']
d
Out[39]:
{'four': 4, 'one': -1, 'three': 3}

Метод get, если он будет вызван с отсутствующим ключом, не приводит к ошибке, а возвращает специальный объект None. Он используется всегда, когда необходимо указать, что объект отсутствует (в какой-то мере он аналогичен null в C). Если передать методу get второй аргумент - значение по умолчанию, то будет возвращаться это значение, а не None.

In [40]:
d.get('one'),d.get('five')
Out[40]:
(-1, None)
In [41]:
d.get('one',0),d.get('five',0)
Out[41]:
(-1, 0)

Словари обычно строят последовательно: начинают с пустого словаря, а затем добавляют ключи со значениями.

In [42]:
d={}
d
Out[42]:
{}
In [43]:
d['zero']=0
d
Out[43]:
{'zero': 0}
In [44]:
d['one']=1
d
Out[44]:
{'one': 1, 'zero': 0}

А это генератор словаря (dictionary comprehension).

In [45]:
d={i:i**2 for i in range(5)}
d
Out[45]:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Ключами могут быть любые неизменяемые объекты, например, целые числа, строки, кортежи.

In [46]:
d={}
d[0,0]=1
d[0,1]=0
d[1,0]=0
d[1,1]=-1
d
Out[46]:
{(0, 0): 1, (0, 1): 0, (1, 0): 0, (1, 1): -1}
In [47]:
d[0,0]+d[1,1]
Out[47]:
0

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

In [48]:
d={'one':1,'two':2,'three':3}
for x in d:
    print(x,'  ',d[x])
one    1
three    3
two    2

Метод keys возвращает список ключей, метод values - список соответствующих значений (в том же порядке), а метод items - список пар (ключ,значение). Точнее говоря, это не списки, а некоторые объекты, которые можно использовать в for циклах или превратить в списки функцией list. Если хочется написать цикл по упорядоченному списку ключей, то можно использовать sorted(d.keys)).

In [49]:
d.keys(),d.values(),d.items()
Out[49]:
(dict_keys(['one', 'three', 'two']),
 dict_values([1, 3, 2]),
 dict_items([('one', 1), ('three', 3), ('two', 2)]))
In [50]:
for x in sorted(d.keys()):
    print(x,'  ',d[x])
one    1
three    3
two    2
In [51]:
for x,y in d.items():
    print(x,'  ',y)
one    1
three    3
two    2
In [52]:
del x,y

Что есть истина? И что есть ложь? Подойдём к этому философскому вопросу экспериментально.

In [53]:
bool(False),bool(True)
Out[53]:
(False, True)
In [54]:
bool(None)
Out[54]:
False
In [55]:
bool(0),bool(123)
Out[55]:
(False, True)
In [56]:
bool(''),bool(' ')
Out[56]:
(False, True)
In [57]:
bool([]),bool([0])
Out[57]:
(False, True)
In [58]:
bool(set()),bool({0})
Out[58]:
(False, True)
In [59]:
bool({}),bool({0:0})
Out[59]:
(False, True)

На варажения, стоящие в булевых позициях (после 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 [60]:
def f():
    pass
In [61]:
f
Out[61]:
<function __main__.f>
In [62]:
pass
In [63]:
type(f)
Out[63]:
function
In [64]:
r=f()
print(r)
None

Эта функция более полезна: она имеет параметр и что-то возвращает.

In [65]:
def f(x):
    return x+1
In [66]:
f(1),f(1.0)
Out[66]:
(2, 2.0)
In [67]:
f('abc')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-67-410386031a44> in <module>()
----> 1 f('abc')

<ipython-input-65-e9c32f618734> in f(x)
      1 def f(x):
----> 2     return x+1

TypeError: Can't convert 'int' object to str implicitly

Если у функции много параметров, то возникает желание вызывать её попроще в наиболее часто встречающихся случаях. Для этого в операторе def можно задать значения некоторых параметров по умолчанию (они должны размещаться в конце списка параметров). При вызове необходимо указать все обязательные параметры (у которых нет значений по умолчанию), а необязательные можно и не указывать. Если при вызове указывать параметры в виде имя=значение, то это можно делать в любом порядке. Это гораздо удобнее, чем вспоминать, является данный параметр восьмым или девятым при вызове какой-нибудь сложной функции.

In [68]:
def f(x,a=0,b='b'):
    print(x,'  ',a,'  ',b)
In [69]:
f(1.0)
1.0    0    b
In [70]:
f(1.0,1)
1.0    1    b
In [71]:
f(1.0,b='a')
1.0    0    a
In [72]:
f(1.0,b='a',a=2)
1.0    2    a
In [73]:
f(a=2,x=2.0)
2.0    2    b

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

In [74]:
a=1
In [75]:
def f():
    a=2
    return a
In [76]:
f()
Out[76]:
2
In [77]:
a
Out[77]:
1

Если в функции нужно использовать какие-нибудь глобальные переменные, их нужно описать как global.

In [78]:
def f():
    global a
    a=2
    return a
In [79]:
f()
Out[79]:
2
In [80]:
a
Out[80]:
2

Пространство имён устанавливает соответствие между именами переменных и объектами - их значениями. Есть пространство имён локальных переменных функции, пространство имён глобальных переменных программы и пространство имён встроенных функций языка питон. Для реализации пространств имён используются словари.

Если функции передаётся в качестве аргумента какой-нибудь изменяемый объект, и функция его изменяет, то это изменение будет видно снаружи после этого вызова. Мы уже обсуждали эту ситуацию, когда две переменные (в данном случае глобальная переменная и параметр функции) указывают на один и тот же изменяемый объект объект.

In [81]:
def f(x,l):
    l.append(x)
    return l
In [82]:
l=[1,2,3]
f(0,l)
Out[82]:
[1, 2, 3, 0]
In [83]:
l
Out[83]:
[1, 2, 3, 0]

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

In [84]:
def f(x,l=[]):
    l.append(x)
    return l
In [85]:
f(0)
Out[85]:
[0]
In [86]:
f(1)
Out[86]:
[0, 1]
In [87]:
f(2)
Out[87]:
[0, 1, 2]

Чтобы избежать таких сюрпризов, в качестве значений по умолчанию лучше использовать только неизменяемые объекты.

In [88]:
def f(x,l=None):
    if l is None:
        l=[]
    l.append(x)
    return l
In [89]:
f(0)
Out[89]:
[0]
In [90]:
f(1)
Out[90]:
[1]
In [91]:
f(2,[0,1])
Out[91]:
[0, 1, 2]

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

In [92]:
def f(x,*l):
    print(x,'  ',l)
In [93]:
f(0)
0    ()
In [94]:
f(0,1)
0    (1,)
In [95]:
f(0,1,2)
0    (1, 2)
In [96]:
f(0,1,2,3)
0    (1, 2, 3)

Звёздочку можно использовать и при вызове функции. Можно заранее построить список (или кортеж) аргументов, а потом вызвать функцию с этими аргументами.

In [97]:
l=[1,2]
c=('a','b')
f(*l,0,*c)
1    (2, 0, 'a', 'b')

Такую распаковку из списков и кортежей можно использовать не только при вызове функции, но и при построении списка или кортежа.

In [98]:
(*l,0,*c)
Out[98]:
(1, 2, 0, 'a', 'b')
In [99]:
[*l,0,*c]
Out[99]:
[1, 2, 0, 'a', 'b']
In [100]:
[*l,3]
Out[100]:
[1, 2, 3]

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

In [101]:
def f(x,y,**d):
    print(x,'  ',y,'  ',d)
In [102]:
f(0,1,foo=2,bar=3)
0    1    {'bar': 3, 'foo': 2}

Двойную звёздочку можно использовать и при вызове функции. Можно заранее построить словарь аргументов, сопоставляющий значения именам параметров, а потом вызвать функцию с этими ключевыми аргументами.

In [103]:
d={'foo':2,'bar':3}
f(0,1,**d)
0    1    {'bar': 3, 'foo': 2}
In [104]:
d['x']=0
d['y']=1
f(**d)
0    1    {'bar': 3, 'foo': 2}

Вот любопытный способ построить словарь с ключами-строками.

In [105]:
def f(**d):
    return d
In [106]:
f(x=0,y=1,z=2)
Out[106]:
{'x': 0, 'y': 1, 'z': 2}

Двойную звёздочку можно использовать не только при вызове функции, но и при построении словаря.

In [107]:
d={0:'a',1:'b'}
{**d,2:'c'}
Out[107]:
{0: 'a', 1: 'b', 2: 'c'}

Вот простой способ объединить два словаря.

In [108]:
d1={0:'a',1:'b'}
d2={2:'c',3:'d'}
{**d1,**d2}
Out[108]:
{0: 'a', 1: 'b', 2: 'c', 3: 'd'}

Если один и тот же ключ встречается несколько раз, следующее значение затирает предыдущее.

In [109]:
d2={1:'B',2:'C'}
{**d1,3:'D',**d2,3:'d'}
Out[109]:
{0: 'a', 1: 'B', 2: 'C', 3: 'd'}

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

In [110]:
def f(x,y,*l,**d):
    print(x,'  ',y,'  ',l,'  ',d)
In [111]:
f(0,1,2,3,foo=4,bar=5)
0    1    (2, 3)    {'bar': 5, 'foo': 4}

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

In [112]:
def f0(x):
    return x+2
In [113]:
def f1(x):
    return 2*x
In [114]:
l=[f0,f1]
l
Out[114]:
[<function __main__.f0>, <function __main__.f1>]
In [115]:
x=2.0
n=1
l[n](x)
Out[115]:
4.0

Если Вы пишете функцию не для того, чтобы один раз её вызвать и навсегда забыть, то нужна документация, объясняющая, что эта функция делает. Для этого сразу после строчки def пишется строка. Она называется док-строкой, и сохраняется при трансляции исходного текста на питоне в байт-код (в отличие от комментариев, которые при этом отбрасываются). Обычно эта строка заключается в тройные кавычки и занимает несколько строчек. Док-строка доступна как атрибут __doc__ функции, и используется функцией help. Вот пример культурно написанной функции, вычисляющей $n$-е число Фибоначчи.

Для проверки типов аргументов, переданных функции, удобно использовать оператор assert. Если условие в нём истинно, всё в порядке, и он ничего не делает; если же оно ложно, выдаётся сообщение об ошибке.

In [116]:
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 [117]:
fib.__doc__
Out[117]:
'вычисляет n-е число Фибоначчи'
In [118]:
help(fib)
Help on function fib in module __main__:

fib(n)
    вычисляет n-е число Фибоначчи

In [119]:
[fib(n) for n in range(1,10)]
Out[119]:
[1, 1, 2, 3, 5, 8, 13, 21, 34]
In [120]:
fib(-1)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-120-b876e14fb318> in <module>()
----> 1 fib(-1)

<ipython-input-116-800ccd3a2d90> in fib(n)
      1 def fib(n):
      2     "вычисляет n-е число Фибоначчи"
----> 3     assert type(n) is int and n>0
      4     if n<=2:
      5         return 1

AssertionError: 
In [121]:
fib(2.0)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-121-363564e722ae> in <module>()
----> 1 fib(2.0)

<ipython-input-116-800ccd3a2d90> in fib(n)
      1 def fib(n):
      2     "вычисляет n-е число Фибоначчи"
----> 3     assert type(n) is int and n>0
      4     if n<=2:
      5         return 1

AssertionError: