Занятие 3

Практическое занятие 3: Составные типы данных и файлы в Python

Цель: Рассмотреть основы языка Python

Задачи:

  1. Списки в Python
  2. Генераторы списков
  3. Разложение числа на множители
  4. Двумерные списки в Python
  5. Оператор in
  6. Методы строк
  7. Функции, возвращающие значения
  8. Функции как аргументы функций map и filter
  9. Работа с файлами
  10. Сортировка списков

Списки в Python

В большинстве практических задач возникает необходимость сохранять не отдельные значения в отдельных переменных, а целые последовательности значений: значения температуры за несколько дней, показания датчиков за некоторый временной интервал, показатели эффективности работы за период времени и т.п.

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

a = [1, 2, 3, 4, 5]

Доступ к отдельному элементу списка осуществляется по его индексу (номеру). Например, код в ячейке ниже создает список и печатает все его элементы — выполните его:

In [ ]:
a = [1, 2, 3, 4, 5]
for i in range(5):
    print('a[', i, ']: ', a[i])

Полученный результат демонстрирует, что индексация начинается с индекса 0 и заканчивается индексом 4 (для массива из 5 элементов). Кроме того, для удобства в Python реализована возможность обращаться к последнему элементу по индексу -1, к предпоследнему -2 и так далее. Для списка из n элементов определены индексы от -n до n-1 включительно:

In [ ]:
print(a[-1], a[-2], a[-3], a[-4], a[-5])

Длину списка, то есть количество элементов в нем, можно узнать при помощи функции len():

In [ ]:
len(a)

Существует несколько способов создания списков. Выше был рассмотрен вариант непосредственного перечисления элементов. Можно создать пустой список (не содержащий элементов) и добавлять элементы в конец списка при помощи метода append(). Например, если программа получает на вход количество элементов в списке n, а потом n элементов списка по одному в отдельной строке, то организовать создание списка можно так:

In [ ]:
a = []
for i in range(int(input())):
    a.append(int(input()))
print(a)

Для списков целиком определены следующие операции: конкатенация списков (добавление одного списка в конец другого) и повторение списков (умножение списка на число):

In [ ]:
a = [1, 2, 3]
b = [4, 5]
c = a + b
d = b * 3
print('c: ', c)
print('d: ', d)

Исходя из этого, для создания списка, заполненного одинаковыми элементами, можно использовать оператор повторения списка:

In [ ]:
a = [2] * 5
print(a)

Кроме того, есть различные методы списков. Например, append(), который мы обсудили выше - это один из методов. Есть метод count(), аналогичный методу count() строк и многие другие методы:

In [ ]:
a = [1, 2, 3, 4, 3, 2, 5]
print(a.count(3))
In [ ]:
a = [10, 11, 12, 13, 14, 15, 16]
print(a[2:4])

Почему это так?

In [ ]:
print(a[::-1])
In [ ]:
print(a[6::-2])

Генераторы списков

Еще один способ создания списков — это использовать генераторы списков, list comprehensions (по-русски их ещё называют списковые включения). Общий вид генератора следующий:

[выражение for переменная in список]

где переменная — идентификатор некоторой переменной, список — список значений, который принимает данная переменная (как правило, полученный при помощи функции range()), выражение — некоторое выражение, которым будут заполнены элементы списка, как правило, зависящее от использованной в генераторе переменной:

In [ ]:
a = [x for x in range(2, 10, 2)]
print(a)
In [ ]:
a = [2 * x for x in range(2, 10, 2)]
print(a)

Пример. Заполните массив квадратами натуральных чисел:

In [ ]:
a = [i ** 2 for i in range(10)]
print(a)

Задача 1: с помощью генератора списка создайте список a, содержащий 7 первых членов геометрической прогрессии. Первый член равен 5, знаменатель равен 3.9

In [ ]:

Разложение числа на множители

Напомним, что алгоритм разложения числа на множители похож на алгоритм проверки числа на простоту. Будем проверять делимость данного числа n на натуральные числа подряд, начиная с числа 2. Если мы находим делитель числа n, то будем делить число n на данный делитель, пока число делится на него и добавлять делитель в список простых делителей.

Перебор также необходимо продолжать до $\sqrt{n}$. Если после окончания этого алгоритма число n не станет равно 1, то оставшееся значение также является простым, так как не делится ни на одно число, не превосходящее корня из оставшегося значения, поэтому его нужно добавить к списку простых делителей:

In [ ]:
def factorize(n):
    a = []
    d = 2
    while d * d <= n:
        if n % d == 0:
            a.append(d)
            n //= d
        else:
            d += 1
    if n > 1:
        a.append(n)
    return a

print(factorize(int(input('Введите целое число: '))))

Задача 2: Для введенного с клавиатуры числа определите число его простых делителей

In [ ]:

Двумерные списки

Двумерных списков в Python нет. Но, если нам нужна двойная индексация, то мы можем использовать вложенные списки. Таким образом, двумерный список — это обычный список, элементами которого являются списки.

Создадим список и рассмотрим обращение к элементам:

In [ ]:
a = [[1, 2, 3], [4, 5, 6]]
print(a)
print(a[1])
print(a[1][0])
print(a[-1][-1])

Доступ к строчкам как к спискам:

In [ ]:
for i in range(len(a)):
    print(a[i])

Доступ к элементам каждого списка по очереди:

In [ ]:
for i in range(len(a)):
    for k in range(len(a[i])):
        print(a[i][k], end='\t')
    print()

В ситуации, когда мы не собираемся изменять в списках ссылки на элементы, можно итерироваться по спискам так:

In [ ]:
for line in a:
    for element in line:
        print(element, end='\t')
    print()

Для создания списка можем использовать генераторы:

In [ ]:
a = [[1, 2, 3] for i in range(4)]
for i in range(len(a)):
    print(a[i])
In [ ]:
a = [[i, i+1, i+2] for i in range(4)]
for i in range(len(a)):
    print(a[i])
In [ ]:
for x in a:
    print(x)
In [ ]:
print(a)

Вот пример создания списка списков при помощи генератора списка, вложенного в генератор списка:

In [ ]:
a = [[j for j in range(5)] for i in range(5)]
for x in a:
    print(x)

Задача 3: создайте двумерный список. Заполните его следующим образом: значение элемента - номер его строки

In [ ]:

Пример. Напечатаем таблицу умножения $10*10$:

In [ ]:
n, m = 10, 10
a = [[i * j for j in range(1, m + 1)] for i in range(1, n + 1)]
for x in a:
    print(x)

Оператор in

Лексема in может быть использована не только как часть синтаксической конструкции цикла for, но и как оператор для проверки принадлежности элемента итерируемому объекту (list или str):

In [ ]:
print(4 in [1, 2, 3, 4, 5])
print(6 in [1, 2, 3, 4, 5])
In [ ]:
print(4 in range(1, 6))
print(6 in range(1, 6))
In [ ]:
print('b' in 'abc')
print('B' in 'abc')

Строки. Операции со строками. Методы строк.

Строки в Python кодируются методом UTF-8, а значит могут содержать символы любых алфавитов мира. При этом объекты типа str являются неизменяемыми.

Строка считывается со стандартного ввода функцией input(). Напомним, что для двух строк определена операция сложения (конкатенации), также определена операция умножения строки на число. Строка состоит из последовательности символов. Узнать количество символов (длину строки) можно при помощи функции len():

In [ ]:
s = 'abcd'
s1 = input()
print(len(s), len(s1))

Все сказанное про итерацию по спискам справедливо и для строки:

In [ ]:
s = 'abcdef'
for x in s:
    print(x)
In [ ]:
print(s[1], s[0], s[-1])
In [ ]:
print(s[-6])

Ошибка IndexError возникает, когда индекс "вылетает" за допустимые пределы либо в положительную, либо в отрицательную сторону:

In [ ]:
print(s[-7])

Для того чтобы вывести через пробел элементы объекта, по которому мы можем итерироваться, достаточно написать:

$*$ - это разворачивание элементов итерируемого объекта в параметры функции. Соответственно, функция print() на примере ниже получает несколько аргументов, соответственно, выводит их через пробел

In [ ]:
print(*a)
In [ ]:
print(*s)

Пример. Дана строка, содержащая только символы 'a', 'b' и 'c'. Удалить из нее все элементы 'b':

In [ ]:
s = input()
s1 = ''
for x in s:
    if x != 'b':
        s1 = s1 + x
print(s1)

Пример. Дана символьная строка, содержащая больше 5 символов. Требуется удалить символы с третьего по пятый:

In [ ]:
s = input()
s1 = ''
for i in range(2):
    s1 = s1 + s[i] 
for i in range(5, len(s)):
    s1 = s1 + s[i]
print(s1)

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

In [ ]:
a = [int(i) for i in input().split()]
print(a)

Задача 4 Считать список. Удалить из него все элементы, равные единице. Вывести элементы полученного списка через пробел:

In [ ]:

Хотелось бы иметь возможность делать такие преобразования проще. Для этого в Python есть срезы. Срез (slice) — извлечение из данной строки одного символа или некоторого фрагмента подстроки или подпоследовательности. Синтаксис среза напоминает синтаксис range(): s[start:stop:step] - срез элементов строки/списка с индексами [start; stop) с шагом step.

Посмотрим, как упростится решение задачи про удаление символов с третьего по пятый:

In [ ]:
s = 'abcdefghij'
s1 = s[0:2:1] + s[5:len(s):1]
print(s1)

Шаг указывать не обязательно, более того, не обязательно указывать начало, если начинаем с нулевого элемента и конец, если заканчиваем последним:

In [ ]:
print(s[:2] + s[5:])
In [ ]:
print(s[5:0:-2])
In [ ]:
print(s[4:0:-2])
In [ ]:
print(s[4:-1:-2])

Как интерпретировать этот результат? -1 - это индекс последнего элемента строки. При отрицательном шаге у нас получается пустая последовательность, т.к. 4-й элемент стоит левее последнего. Для получения того, что мы ожидаем получить, можно просто не указывать значение параметра stop:

In [ ]:
print(s[4::-2])
In [ ]:
print(s[::-1])

Методы строк

Один из основных методов строк — split(). Это способ разбить строку на список строк по некоторому разделителю. Синтаксис: str.split(sep), sep (от слова separator) — разделитель. По умолчанию sep=' '.

In [ ]:
s = '1+2+3+4'
print(s.split('+'))
In [ ]:
s = '1 2 3 4'
print(s.split())
In [ ]:
s = 'www.mipt.ru'
print(s.split('.'))
print(s.split('.')[1])

Обратный к нему метод — join():

In [ ]:
a = [1, 2, 3, 4, 5]
print(' '.join(a))
In [ ]:
a = [1, 2, 3, 4, 5]
for i in range(len(a)):
    a[i] = str(a[i])
print(a)
print(', '.join(a))

Для замены фрагмента используется метод replace.

In [ ]:
s = 'abrakadabra'
print(s.replace('a', 'A'))

Также этот метод можно использовать для удаления частей строки:

In [ ]:
s = 'abcbcdcba'
s1 = s.replace('b', '')
print(s1)

Обратите внимание! Любая операция, изменяющая строку, как, например, replace не меняет исходную строку, а создаёт новую, в которой осуществлена эта замена.

Пример: Рассмотрим задачу подсчета количества вхождений символа 'b' в строку. Ее можно решить следующим образом:

In [ ]:
s = 'abcbdbegh'
k = 0
for x in s:
    if x == 'b':
        k += 1
print(k)

А можно воспользоваться методом count:

In [ ]:
s = 'abcbdbegh'
print(s.count('b'))

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

Функции, возвращающие значение

Функции — это вспомогательные алгоритмы.

Логическое значение как результат работы функции

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

In [ ]:
def is_positive(x):
    if x > 0:
        return True
    else:
        return False
    
print(is_positive(5))
print(is_positive(-5))
In [ ]:
def is_positive(x):
    if x > 0:
        return True
    return False

print(is_positive(5))
print(is_positive(-5))
In [ ]:
def is_positive(x):
    return x > 0

print(is_positive(5))
print(is_positive(-5))

Функции как аргументы функций map и filter

Для поэлементной обработки любого итерируемого объекта можно использовать функции map и filter:

In [ ]:
a = '1 2 -3 4 -5'.split()
print(a)
a = list(map(int, a))
print(a)
In [ ]:
a = '1 2 -3 4 -5'.split()
print(a)
a = list(map(float, a))
print(a)
In [ ]:
a = '1 2 -3 4 -5 0'.split()
print(a)
a = list(map(bool, a))
print(a)
In [ ]:
a = '1 2 -3 4 -5 0'.split()
print(a)
a = list(map(int, a))
print(a)
a = list(map(bool, a))
print(a)

Как параметр для map можно передавать не только стандартные функции, но и свои собственные:

In [ ]:
def my_function(x):
    return x * 2

a = '1 2 -3 4 -5'.split()
print(a)
a = list(map(my_function, a))
print(a)

Такие "одноразовые функции" загрязняют пространство имён, поэтому есть возможность создать безымянную функцию при помощи слова lambda. Вычисляясь, лямбда-выражение "изготавливает" новую функцию и передаёт её как объект туда, где она нужна — в функцию map(). Затем она сразу забывается.

In [ ]:
a = '1 2 -3 4 -5'.split()
print(a)
a = list(map(lambda x: x * 2, a))
print(a)
In [ ]:
a = [1, 2, -3, 4, -5]
b = list(map(is_positive, a))
c = list(filter(is_positive, a))
print(b)
print(c)
In [ ]:
a = [1, 2, -3, 4, -5]
b = list(map(lambda x: x > 0, a))
c = list(filter(lambda x: x > 0, a))
print(b)
print(c)

Можем делать "сложную" лямбда-функцию:

In [ ]:
a = '1 2 -3 4 -5'.split()
print(a)
a = list(map(lambda x: abs(int(x)), a))
print(a)

Задача 5: считайте список с клавиатуры. С помощью функции filter удалите из него те элементы, которые не являются квадратами однозначных натуральных чисел.

In [ ]:

Можно использовать map для обработки считанного списка. Считываем с помощью функции input() объект типа str, к нему применяем метод split() и получаем list, ко всем его элементам функция map применяет функцию int, а конструктор списка list сохраняет результат в список:

In [ ]:
a = list(map(int, input().split()))
print(a)
In [ ]:
a = [list(map(int, input().split())) for i in range(5)]
In [ ]:
for x in a:
    print(*x)

Пример: удалить из матрицы строки с отрицательной суммой

In [ ]:
a = [[5, -2, 3], [-1, 2, -3], [-8, 4, 5]]
for x in a:
    print(*x)
In [ ]:
a = list(filter(lambda x: sum(x) >= 0, a))
print(a)
In [ ]:
print(sum(a[0]), sum(a[1]))

Работа с текстовыми файлами

Текстовые файлы содержат в себе символы, закодированные различными кодировками. Долгое время существовали однобайтные кодировки со специфическими национальными кодовыми страницами: DOS CP866, Windows CP-1251, KOI-8R и другие.

С появлением консорциума Unicode были разработаны международные кодировки: двухбайтная UTF-16, четырёхбайтная UTF-32 и кодировка с переменным количеством байт на символ UTF-8, которая пользуется особой популярностью в сети Интернет. Тем не менее, в Интернете встречаются файлы с разными кодировками.

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

Пример: создайте в текстовом редакторе файл input.txt в папке рядом с этим ноутбуком.ipynb, скопируйте туда таблицу с числами ниже:

1 2 3
4 5 6
In [ ]:
with open('input.txt', 'r') as f:
    print(f.read())
In [ ]:
with open('input.txt', 'r', encoding='utf8') as f:  # здесь явно указана кодировка файла
    s = f.read()
    print(s)
In [ ]:
s, type(s)

Так выглядит внутреннее представление строки s. Когда мы вызываем функцию print() символ переноса строки \n переводит курсор на новую строку:

In [ ]:
print('1\n2')
In [ ]:
with open('input.txt', 'r') as f:
    a = f.readlines()
print(a)

Функция rstrip() удаляет пробельные элементы из конца строки, в частности, символ переноса строки:

In [ ]:
with open('input.txt', 'r') as f:
    a = f.readlines()
    a = list(map(lambda x: x.rstrip(), a))
print(a)
In [ ]:
with open('input.txt', 'r') as f:
    a = f.readlines()
    a = list(map(lambda x: x.rstrip().split(), a))
print(a)

Или проще:

In [ ]:
with open('input.txt', 'r') as f:
    a = f.readlines()
    a = list(map(lambda x: x.rstrip().split(), a))
    for i in range(len(a)):
        for j in range(len(a[i])):
            a[i][j] = int(a[i][j])
print(a)

Пример: в созданный файл запишем вместо чисел, которые там были, новые числа, равные квадратам исходных

In [ ]:
b = [[str(a[i][j] ** 2) for j in range(len(a[i]))] for i in range(len(a))]
print(b)
In [ ]:
with open('output.txt', 'w') as f:
    f.writelines([' '.join(x) for x in b])

Посмотрите, что записалось в файл. Не совсем то, что нужно — не хватает символов переноса строки:

In [ ]:
b = [[str(x) for x in b[i]] for i in range(len(b))]
with open('output.txt', 'w') as f:
    f.writelines([' '.join(x) + '\n' for x in b])

Файлы можно открывать не только на запись, но и на дозапись. Обратите внимание, что в предыдущей ячейке предыдущее сожержимое файла стерлось запуском open(), для этого используем флажок a вместо w:

In [ ]:
with open('output.txt', 'a') as f:
    f.write('done!')

Кстати, у обыкновенной функции print есть именованный параметр file для вывода в файл. Естественно, такой файл должен быть открыт на запись с параметром w или a.

In [ ]:
with open('output.txt', 'a') as f:
    print('Print can write to files!', file=f)

После выполения этой ячейки не забудьте посмотреть на содержимое файла output.txt.

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

Для сортировки списков в Python существуют функции sort() и sorted(). Первая изменяет сортируемый объект, вторая создает новый, а исходный остается:

In [ ]:
a = [3, 1, 5, 4, 2]
a.sort()
print(a)
In [ ]:
a = [3, 1, 5, 4, 2]
print(sorted(a))
print(a)

Но можем использовать вторую вместо первой:

In [ ]:
a = [3, 1, 5, 4, 2]
a = sorted(a)
print(a)

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

In [ ]:
a = [3, 1, 5, 4, 2]
a = sorted(a, key = lambda x: -x)
print(a)
In [ ]:
a = [[1, 5], [3, 3], [5, 5], [3, 0]]
print(sorted(a, key = lambda x: sum(x)))
In [ ]:
a = [[1, 5], [3, 3], [5, 5], [3, 0]]
print(sorted(a, key = lambda x: x[0] ** 2 + x[1] ** 2))

Можем сортировать по нескольким параметрам в порядке убывания приоритета:

In [ ]:
a = [[1, 6], [2, 5], [3, 4]]
print(sorted(a, key = lambda x: (sum(x), x[1])))

(sum(x), x[1]) - это кортеж. Кортежи похожи на списки, но они неизменяемы (нельзя присваивать по индексу и добавлять элементы):

In [ ]:
a = [1, 2]
a[1] = 3
print(a)
In [ ]:
a = [1, 2]
a[1] = 3
print(a)
In [ ]:
a = [1, 2]
a.append(3)
print(a)
In [ ]:
a = (1, 2)
a.append(3)
print(a)
In [ ]:
a = [1, 2]
a = [3, 4]
print(a)
In [ ]:
a = (1, 2)
a = (3, 4)
print(a)

Строки сортируются в лексикографическом порядке:

In [ ]:
a = ['100', '11', '12', '120', '119']
print(sorted(a))
In [ ]:
a = ['100', '11', '12', '120', '119']
print(sorted(a, key = lambda x: int(x)))