Основы Python. Часть 2#

Перечислимые типы#

Базовые перечислимые типы#

Тип

Название

Объявление

Порядок

list

Список

[1, 2, 1], ["a", "b", "a"], []

Да

tuple

Кортеж

(1, 2, 1), ("a", "b", "a"), ("a",)

Да

set

Набор

{1, 3, 2}, {"a","c","b"}, set()

Нет

dict

Словарь

{"a":1, "c":2, "b":1}, {}

Сохраняет порядок вставки

Перечислимые типы являются изменяемыми. При присвоении переменные этих типов не копируются.

Типы tuple, str и bytes имеют промежуточный статус. Они является неизменяемыми, но переменные этих типов можно перечислять.

Особенности перечислимых типов#

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

data = [5,7,9,12,"test",True,["ab","cd","ef"]]

Списки и кортежи сохраняют порядок, а наборы и словари - нет:

data_list = [5,4,3,6,2,1]
data_set = {5,4,3,6,2,1}
print(data_list)
print(data_set)
[5, 4, 3, 6, 2, 1]
{1, 2, 3, 4, 5, 6}

Переменные перечислимых типов (как и все составных) при присвоении не копируются:

data = [5,7,9,12]
double_data = data
double_data[1] = 42
print(data)
[5, 42, 9, 12]

Переменные перечислимых типов можно перечислять в цикле for.

Цикл перебора элементов for#

Итерационные цикл for используется для обхода элементов последовательностей.

for 〖переменные〗in 〖список или генератор〗:
    〖операторы〗

Примеры:

x = "привет"
for char in x:
    print(char)
п
р
и
в
е
т
x = [3,4,5]
for num in x:
    print(num)
3
4
5

Некоторые полезные генераторы#

Элемент с индексом

for i, elem in enumerate([1,2,3]):
    print(i,elem)
0 1
1 2
2 3

Целые числа по порядку

for i in range(5, 10, 1):
    print(i)
5
6
7
8
9

Синхронный обход списков

for elem1, elem2, elem3 in zip([1,2,3], ["a","b","c"], "qwt"):
    print(elem1, elem2, elem3)
1 a q
2 b w
3 c t

Полное декартово произведение

import itertools
for elem1, elem2 in itertools.product([1,2,3], ["a","b","c"]):
    print(elem1, elem2)
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
3 c

Список: list#

  • Используется в качестве замены массивам и векторам

  • Может иметь произвольную длину

  • Его можно удлинять и укорачивать

Индексирование в Python выполняется с 0.

data = ['a', 'b', 'c', 'd']
data[0] # 'a'
data[3] # 'd'
# Отрицательные индексы нумеруют список с конца и с -1
data[-1] # 'd'
data[-4] # 'a'
# Длина
x = len(data) # x == 4
# Поиск по значению
x = data.index('c') # x == 2

Срезы списков#

Срезы (slices) — это способ получать из списка его часть не прибегая к циклам.

Синтаксис: <индекс первого элемента>:<индекс последнего элемента +1>:<шаг>

data = ['a', 'b', 'c', 'd']
print(data[1:3])
print(data[2:])
print(data[:])
print(data[-2:])
print(data[1:4:2])
['b', 'c']
['c', 'd']
['a', 'b', 'c', 'd']
['c', 'd']
['b', 'd']

Срезы создают копию списка:

data = ['a', 'b', 'c', 'd']

# есть копирование temp и data это разные списки
temp = data[:]
temp[0] = 'z'
print(data)

# нет копирования temp и data это один список с двумя именами
temp = data
temp[0] = 'z'
print(data)
['a', 'b', 'c', 'd']
['z', 'b', 'c', 'd']

Модификация списков#

data = ['a', 'b', 'c', 'd']
data[1] = 42
del data[1]
data.insert(0,42)
data.append(42)
print(data)

data = data + [1,2,3]
print(data)
v = data.pop()
print(data, v)
v = data.pop(0)
print(data, v)
[42, 'a', 'c', 'd', 42]
[42, 'a', 'c', 'd', 42, 1, 2, 3]
[42, 'a', 'c', 'd', 42, 1, 2] 3
['a', 'c', 'd', 42, 1, 2] 42

Перечисление элементов списков#

data = ['a', 'b', 'c', 'd']
for v in data:
    print(v)
for i,v in enumerate(data):
    print(i,v)
a
b
c
d
0 a
1 b
2 c
3 d

Если элемент списка неизменяемого типа, то при присвоение переменной созданной в цикле for (в примере, v) нового значение не меняет содержание массива.

i = 0
while i < len(data):
    data[i] = 42
    i += 1
    
for i in range(0,len(data)):
    data[i] = 42

Сортировка списков#

adata = [5,4,2,1,4]
# Сортировка с копированием
bdata = sorted(adata)
print(bdata)
# Сортировка на месте (in-place)
adata.sort()
print(adata)
# Обратная сортировка
bdata = sorted(adata, reverse=True)
print(bdata)
adata.sort(reverse=True)
print(adata)
[1, 2, 4, 4, 5]
[1, 2, 4, 4, 5]
[5, 4, 4, 2, 1]
[5, 4, 4, 2, 1]

Кортеж: tuple#

Кортеж это неизменяемый список. Часто встречается, как возвращаемое значение функций и генераторов.

tple = (1, 2, 3)
print(tple[0])
1

Набор: set#

Набор не сохраняет порядок. Все элементы набора различны.

vset = {4, 3, 2, 3, 1}
print(vset)
vset.add(0)
print(vset)
vset.add(4)
print(vset)
vset.remove(4)
print(vset)
{1, 2, 3, 4}
{0, 1, 2, 3, 4}
{0, 1, 2, 3, 4}
{0, 1, 2, 3}

Типичное применение — хранение данных имеющих структуру множества и удаление дубликатов из списка:

data = [1, 1, 2, 2, 2]
data = list(set(data))
print(data)
[1, 2]

Словарь: dict#

Набор пар ключ-значение. Ключи всегда различны.

Значения могут быть любого типа, ключи — с некоторыми ограничениями.

Модификация словарей#

data = {"a":1, "b":2, "c":3}
data["b"] = 3
print(data)
del data["b"]
print(data)
data["f"] = 99
print(data)
print(len(data))
data.update({"a":99, "q":64})
print(data)
{'a': 1, 'b': 3, 'c': 3}
{'a': 1, 'c': 3}
{'a': 1, 'c': 3, 'f': 99}
3
{'a': 99, 'c': 3, 'f': 99, 'q': 64}

Перечисление элементов словарей#

data = {"a":1, "b":2, "c":3}

for k in data:
    print(k)
    print(data[k]) 

for k in sorted(data.keys()):
    print(k,data[k])
a
1
b
2
c
3
a 1
b 2
c 3

Операторы над составными типами#

Оператор

Действие

Пример

in, not in

Содержание, возвращает bool

x in data

y[x]

Индексирование переменных перечислимых типов

data[x]

y.x

Обращение к полю или методу объекта или модуля

data.foo(2)

Приведение перечислимых типов#

Исходный тип

Целевой тип

Приведение для переменных data и keys

list

tuple

tuple(data)

list

set

set(data)

list

dict

dict(zip(keys,data))

tuple

list

list(data)

tuple

set

set(data)

tuple

dict

dict(zip(keys,data))

set

list

list(data)

set

tuple

tuple(data)

set

dict

dict(zip(keys,data))

dict

list

list(data.keys()), list(data.values())

dict

set

set(data.keys()), set(data.values())

dict

tuple

tuple(data.keys()), tuple(data.values())

Объявление функций#

Как и в большинстве языков структурного программирования, блоки операторов можно оформлять в виде функций для дальнейшего использования.

def 〖имя функции〗(〖аргументы〗):
    〖операторы〗
    return 〖возвращаемое значение〗

Функция может возвращать одно или несколько значений (в виде кортежа) или None.

Объявление функции следует рассматривать, как присвоение значения соответствующей переменной.

Модификация аргументов#

Базовые типы данных при вызове функций копируются, а составные нет.

def foo(x):
    x = x + 1
    return x
    
w = foo(5)
print(w)
x = 1
x = foo(x)
print(x)
6
2
def foo(x):
    x[0] = "42"

w = [1, 2, 3]
foo(w)
print(w)
['42', 2, 3]

Область видимости#

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

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

y = 1

def foo(x):
    y = x + 2
    return x,y

a,b = foo(1)
print(a,b)

def foo(x):
    global y # Явно используем y из глобальной области
    x = x + y 
    y = y + 1
    return x,y

a,b = foo(1)
print(a,b)
1 3
2 2

Аргументы по умолчанию#

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

def foo(x = 42):
    return x*2
    
print(foo(1))
print(foo())
2
84

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

def foo(x, y, z = 42): #Верно
    return x*y*2

Порядок аргументов#

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

Аргументы со значениями по умолчанию можно передавать в произвольном порядке.

def foo(a, x = 1, y = 2, z = 3):
    return (x*y*z)/a

print(foo(1, z = 7, y = 7))
49.0

Функция как аргумент#

def foo(x):
    return x**2

def bar(x):
    return x**3 / 3

def calc(f, x, a):
    return f(x) + a
    
print(calc(foo,1,2))
print(calc(bar,3,2))
3
11.0

Что еще нужно знать о функциях#

  • Функция — объект специального составного типа function

  • Объявление одной и той-же функции несколько раз не выдаст ошибки

  • Функции можно складывать в списки, словари, проверять на тождество

  • Функции можно объявлять внутри функций, кроме слова global есть слово nonlocal, которое делает тоже самое, но для локальных переменных

  • Функции с помощью специальных приемов могут обрабатывать произвольное число аргументов (подробнее)

  • Функции можно вызывать рекурсивно

  • Есть особые функции: лямбда (безымянные) и генераторы (запоминающие свое состояние)

Исключения#

Возникновение исключений#

Когда происходит ошибка — возбуждается соответствующее исключение. Обработка исключения — основой способ обработки ошибок в Python.

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

Обработка исключений#

Можно перехватывать все типы исключения написав except:, но это маскирует ошибки.

Популярные типы исключений#

Исключение

Описание

IndexError

Индекс за пределами массива

KeyError

Ключ отсутствует в словаре

NameError

Обращение к необъявленной переменной (методу, классу)

UnboundLocalError

Обращение к недоступной переменной

OSError

Ошибки системы (права на файл, нет места на диске, …)

TypeError

Операция не поддерживается данным типом данных (вычитание строк, …)

ValueError

Недопустимое значение переменной (float('hello'), …)

NotImplementedError

Метод не реализован

Исключение также можно выбросить прямо в коде. Для этого используется ключевое слово raise.

Примеры использования#

value_str = "123.15"
value_float = 0.0

try:
    value_float = float(value_str)
except ValueError:
    print("Это не число!")
finally:
    print(value_float)
    
print(value_float*10)
123.15
1231.5

Примеры использования в функции#

from traceback import print_exc

def fix(x, fix_type = None):
    if fix_type == "round":
        return round(x)
    elif fix_type == "ceil":
        return math.ceil(x)
    elif fix_type == "trunc":
        return math.trunc(x)
    else:
        raise ValueError("Недопустимое значение переменной fix_type")

#ft = input("Введите тип округления:")
ft = 'unknown'
value = 73/13
try:
    value = fix(value, ft)
except ValueError as v:
    print_exc()
    value = fix(value, "round")
print(value)
6
Traceback (most recent call last):
  File "/tmp/ipykernel_6823/1214461313.py", line 17, in <module>
    value = fix(value, ft)
  File "/tmp/ipykernel_6823/1214461313.py", line 11, in fix
    raise ValueError("Недопустимое значение переменной fix_type")
ValueError: Недопустимое значение переменной fix_type

Раскрутка стека#

После того, как возникло исключение (был вызван raise или возникли обстоятельства провоцирующие исключение) если блок except не был найден на текущем уровне происходит поиск ближайщего обработчика (раскрутка стека вызовов):

from traceback import print_exc

def foo(x,y):  # стек вызовов: baz -> bar -> foo
    return x/y # место возникновения исключения. обработчика нет. ищем на уровень выше: 

def bar(x,y):  # стек вызовов: baz -> bar
    try:
        m = foo(x+2,y-2)+2 # сначала ищем тут
        m = m + 1
        return m
    except ValueError: # обработчик не соответствует типу исключения. ищем на уровень выше
        return 1

def baz(x, y): # стек вызовов: bar
    try:
        v = bar(x,y) # потом ищем тут
    except ZeroDivisionError: # обработчик соответствует типу исключения. обрабатываем.
        print_exc()
        v = None
    return v

baz(5,2);
Traceback (most recent call last):
  File "/tmp/ipykernel_6823/2961457100.py", line 16, in baz
    v = bar(x,y) # потом ищем тут
  File "/tmp/ipykernel_6823/2961457100.py", line 8, in bar
    m = foo(x+2,y-2)+2 # сначала ищем тут
        ~~~^^^^^^^^^
  File "/tmp/ipykernel_6823/2961457100.py", line 4, in foo
    return x/y # место возникновения исключения. обработчика нет. ищем на уровень выше:
           ~^~
ZeroDivisionError: division by zero

Если обработчик не находится, про программа аварийно завершается. Интерактивные консоли (Spyder, IPython и т.п.) устанавливают собственный обработчик, который перехватывает почти все исключения и останавливает выполнение программы сохраняя значение переменных на момент возникнования исключения.

Что еще нужно знать об исключениях#

  • «Я не знаю, что дальше делать, я вызываю raise»

  • Оператор try почти ничего не стоит, но обработка возникшего исключения дело более ресурсоемкое (хотя и не очень), поэтому не следует использовать исключения вместо if

  • Объект исключения можно преобразовать в str, обычно это используется для вывод сообщений об ошибках

  • Исключение можно возбудить повторно, вызвав raise без аргументов в обработчике

  • Исключения можно содержательно анализировать с помощью методов модуля traceback

Модули#

import os
os.getcwd();
from os import getcwd
getcwd();

Порядок поиск при импорте:

  • Файлы в папке программы

  • Файлы в переменной окружения $PYTHONPATH

  • Сторонние пакеты

  • Стандартная библиотека

Разделение программы на несколько файлов#

Файл mylib.py:

def foo(x):
    return x*x

Файл program.py:

Собственный модуль#

Собственный модуль это папка в которой лежат файлы .py и создан дополнительный файл __init__.py. Этот файлы выполняется при загрузке модуля через импорт.

Пример структуры модуля:

Папка mylib:

  • __init__.py

  • fu1.py

  • fu2.py

Файл __init__.py:

Файл program.py:

Разница между импортом и запуском#

Теоретически любой скрипт на Python может быть импортирован через import или запущен на исполнение, как программа (python program.py). Следующая конструкция позволяет определнить импортирован скрипт или запущен:

Функция main() будет вызвана только если скрипт запущен как программа и не будет запускаться, если он импортирован в другой скрипт.