Ctrl+Enter
Так как текст местами пишется с нуля, то в нём хватает опечаток, неточностей и ошибок. Если вы заметили опечатку или ошибку, выделите её, нажмите Ctrl+Enter и опишите проблему. Желательно также написать ещё и её решение.
Точно также можно поступить, если какой-то кусок текста совсем непонятен. А если вы можете предложить замену некоторой части на гораздо более понятный текст — то будет просто замечательно!
О данном учебном материале
Значительная часть текстов изначально сделаны Денисом Кириенко и частично соответствуют текстам на информатиксе. Все отличия от оригинальных текстов Дениса внесены мной (Сергеем Шашковым, sh57@ya.ru) на основе документации, исходных кодов и обсуждений на stackoverflow.

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

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

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

Установка всего необходимого

Я предлагаю использовать сборку интерпретатора и пакетов под названием анаконда (брать отсюда: https://www.continuum.io/downloads, желательно версию для x64 (если у вас x64), брать "Python 3.5", а не "Python 2.7") Можно использовать и стандартный питон с python.org, но некоторые пакеты в винде доставлять потом больно. При установке нужно поставить флаг "Ставить для всех пользователей", а также флаг про "добавить в Path". Без флагов будет мучительно.

В качестве IDE я предлагаю использовать PyCharm Community Edition (брать отсюда: https://www.jetbrains.com/pycharm/download/)
Поначалу PyCharm будет ужасно тяжёлым, тормозным и противным, но через некоторое время станет вполне ничего. Он предлагает довольно много плюшек, которыми удобно пользоваться Основные горячие клавиши: Ctrl+Shift+F10 — запустить текущую программу в отдельном процессе; Ctrl+Shift+E — выполнить выделенный код в консоли; Tab и Shift+Tab — сдвинуть блок на одну позицию вправо или влево.

Задачи по-началу я буду давать под сдачу в тестовую систему на информатикс.мццме. Регистрация на informatics.mccme.ru. В качестве школы и класса можно указать пустое значение (выбрать из списка). После регистрации пришлите мне ваш логин, я добавлю вас в группу и смогу глядеть и комментировать сдаваемый код;

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

Первые эксперименты;
  Типы данных;
  Преобразование типов;

Интерактивный интерпретатор

Python  — это современный язык программирования, работающий на всех распространных операционных системах.

В настоящее время существует две версии языка Python: более старая, но пока ещё более распространненая версия 2 и современная версия 3. Они не вполне совместимы друг с другом: программа, написанная для одной версии языка может оказаться невыполнимой для другой версии. Но в основном обе версии очень похожи.

Мы будем использовать версию 3 данного языка, некоторые из используемых примеров не будут работать с версией 2. Последняя версия языка, доступная в октября 2016 года — 3.5.2, именно её необходимо установить.

Запустить интерпретатор python можно из командной строки (под nix-ами нужно говорить "python3"):

$ python

Будьте внимательны, под nix-ами команда python запустит интерпретатор версии 2, с которым мы работать не будем. В системе Windows можно использовать пункт меню “Python (command line)”

Вы увидите примерно следующее приглашение командной строки:

Python 3.5.2 |Anaconda custom (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

Смело вводите команды и наслаждайтесь результатом. А что можно вводить? Несколько примеров:

>>> 2 + 2
4
>>> 2 ** 100
1267650600228229401496703205376
>>> 'Hello' + 'World'
'HelloWorld'
>>> 'ABC' * 100
'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC'

Первая команда вычисляет сумму двух чисел, вторая команда вычисляет 2 в степени 100, третья команда выполняет операцию конкатенации для строк, а четвертая команда печатает строку 'ABC', повторенную \(100)\ раз.

Хотите закончить работу с питоном? Введите команду exit() (именно так, со скобочками, так как это — функция), или нажмите Ctrl+D.

Типы данных

Итак, мы видим, что Питон умеет работать как минимум с двумя видами данных — числами и строками. Числа записываются последовательностью цифр, также перед числом может стоять знак минус, а строки записываются в одинарных кавычках. 2 и '2' — это разные объекты, первый объект — число, а второй —строка. Операция + для целых чисел и для строк работает по-разному: для чисел это сложение, а для строк —конкатенация.

Кроме целых чисел есть и другой класс чисел: действительные (вещественные числа), представляемые в виде десятичных дробей. Они записываются с использованием десятичной точки, например, 2.0. В каком-то смысле, 2 и 2.0 имеют равные значение, но это—разные объекты. Например, можно вычислить значения выражения 'ABC' * 10 (повторить строку 10 раз), но нельзя вычислить 'ABC' * 10.0.

Определить тип объекта можно при помощи функции type:

>>> type(2)
<class 'int'>
>>> type('2')
<class 'str'>
>>> type(2.0)
<class 'float'>

Обратите внимание —type является функцией, аргументы функции указываются в скобках после ее имени.

Вот список основных операций для чисел:
A + B — сумма;
A - B — разность;
A * B — произведение;
A / B — частное;
A ** B — возведение в степень. Полезно помнить, что квадратный корень из числа x — это x ** 0.5, а корень степени n это x ** (1 / n).

Есть также унарный вариант операции -, то есть операция с одним аргументом. Она возвращает число, противоположное данному. Например: -A.

В выражении может встречаться много операций подряд. Как в этом случае определяется порядок действий? Например, чему будет равно 1 + 2 * 3 ** 1 + 1? В данном случае ответ будет 8, так как сначала выполняется возведение в степень, затем – умножение, затем — сложение.

Более общие правила определения приоритетов операций такие:

  1. Выполняются возведения в степень справа налево, то есть 3 ** 3 ** 3 это \(3^{(3^3)}\).
  2. Выполняются унарные минусы (отрицания).
  3. Выполняются умножения и деления слева направо. Операции умножения и деления имеют одинаковый приоритет.
  4. Выполняются сложения и вычитания слева направо. Операции сложения и вычитания имеют одинаковый приоритет.

Основные операции над строками:
A + B — конкатенация;
A * n — повторение n раз, значение n должно быть целого типа.

Преобразование типов

Иногда бывает полезно целое число записать, как строку. И, наоборот, если строка состоит из цифр, то полезно эту строку представить в виде числа, чтобы дальше можно было выполнять арифметические операции с ней. Для этого используются функции, одноименные с именем типа, то есть int, float, str. Например, int('123') вернет целое число 123, а str(123) вернет строку '123'.

Пример:

>>> str(2 + 2) * int('2' + '2')
'4444444444444444444444'

Результатом будет строка из числа 4, повторенная 22 раза.

00. Первые упражнения в консоли

Нужно написать программу из одной строки вида print(2 + 2), где в скобках будет значение, которое необходимо вычислить в этой задаче.

01: Вычислите \(2^{2016}\) .

02: Вычислите \(20!\).

03: Вычислите длину гипотенузы в прямоугольном треугольнике со сторонами \(2016\) и \(6102\).

04: Напишите программу, которая выводит \(100\) раз подряд букву 'A' (латинскую, заглавную).

05: Запишите слово 'Python' \(100\) раз подряд.

06: Число \(2016\) записали \(50\) раз подряд. Полученное \(200\)-значное число возвели в квадрат. Сколько получилось?

07: Число \(2016^{10}\) записали четыре раза подряд. Из получившегося числа извлекли корень степени \(10\) (то есть возвели число в степень \(1/10\)). Сколько получилось?

Простейшие программы;
  Тонкости про переменные и ссылки на объекты;
  Ввод данных: функция input();
  Вывод данных: функция print();
  Целочисленная арифметика;
  Под капотом int

Пишем простейшие программы

В предыдущем задании мы использовали Питон для простых разовых вычислений, используя интерактивный режим. Например, было задание вычислить длину гипотенузы прямоугольного треугольника по ее катетам. Запустите PyCharm, создайте тестовый проект, а в нём — новый файл и напишите следующий текст:

a = 2016
b = 6102
c = (a ** 2 + b ** 2) ** 0.5
print (c)

Здесь мы используем переменные — объекты, в которых можно сохранять различные (числовые, строковые и прочие) значения. В первой строке переменной a присваивается значение 2016, затем переменной b присваивается значение 6102, затем переменной c присваивается значение арифметического выражения, равному длине гипотенузы.

После этого значение переменной c выводится на экран.

Сохраните этот текст в файле с именем hypot.py. Затем выполните эту программу (Shift+Ctrl+F10).

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

Немного тонкостей с ссылками и объектам

Дело в том, что в отличие от языков Paskal, C и C++ (C#, RPGLE) (и иным компилируемым), а также частично Java, всё в питоне — это объект. И a в предыдущем примере на самом деле не переменная, а ссылка на объект-целое число 2016. В картинках всё выглядит так (в компилируемых языках):
int a = 1;
a = 2;
int b = a;

В питоне:

a = 1
a = 2
b = a

Для ускорения объекты с небольшими целыми числами уже заранее созданы при старте интерпретатора. В связи таким подходом теоретически возможны курьёзы: если испортить объект, которых должен содержать, скажем, число 2, то можно получить самые неожиданные результаты при вычислении 2 + 2.

Ввод данных: функция input()

Пример выше неудобен тем, что исходные данные для программы заданы в тексте программы, и для того, чтобы использовать программу для другого треугольника необходимо исправлять текст программы. Это неудобно, лучше, чтобы текст программы не менялся, а программа запрашивала бы у пользователя данные, необходимые для решения задачи, то есть запрашивала бы значения двух исходных переменных a и b. Для этого будем использовать функцию input(), которая считывает строку с клавиатуры и возвращает значение считанной строки, которое сразу же присвоим переменым a и b:

a = input()
b = input()

Правда, функция input возвращает текстовую строку, а нам нужно сделать так, чтобы переменные имели целочисленные значения. Поэтому сразу же после считывания выполним преобразование типов при помощи фунцкии int, и запишем новые значения в переменные a и b.

a = int(a)
b = int(b)

Можно объединить считывание строк и преобразование типов, если вызывать функцию int для того значения, которое вернет функция input:

a = int(input())
b = int(input())

Далее в программе вычислим значение переменной c и выведем результат на экран.

Теперь мы можем не меняя исходного кода программы многократно использовать ее для решения различных задач. Для того нужно запустить программу и после запуска программы ввести с клавиатуры два числа, нажимая после кажого числа клавишу Enter. Затем программа сама выведет результат.

Вывод данных: функция print()

Функция print может выводить не только значения переменных, но и значения любых выражений. Например, допустима запись print(2 + 2 ** 2). Также при помощи функции print можно выводить значение не одного, а нескольких выражений, для этого нужно перечислить их через запятую:

a = 1
b = 2
print(a, '+', b, '=', a + b)
В данном случае будет напечатан текст 1 + 2 = 3: сначала выводится зание переменной a, затем строка из знака “+”, затем значение переменной b, затем строка из знака “=”, наконец, значение суммы a + b.

Обратите внимание, выводимые значение разделяются одним пробелом. Но такое поведение можно изменить: можно разделять выводимые значения двумя пробелами, любым другим символом, любой другой строкой, выводить их в отдельных строках или не разделять никак. Для этого нужно функции print передать специальный именованный параметр, называемый sep, равный строке, используемый в качестве разделителя (sep — аббревиатура от слова separator, т.е. разделитель). По умолчанию параметр sep равен строке из одного пробела и между значениями выводится пробел. Чтобы использовать в качестве разделителя, например, символ двоеточия нужно передать параметр sep, равный строке ':':

print(a, b, c, sep = ':')

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

print(a, '+', b, '=', a + b, sep = '')

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

print(a, b, sep = '\n')

Символ обратного слэша в текстовых строках является указанием на обозначение специального символа, в зависимости от того, какой символ записан после него. Наиболее часто употребляется символ новой строки '\n'. А для того, чтобы вставить в строку сам символ обратного слэша, нужно повторить его два раза: '\\'.

Вторым полезным именованным параметром функции print является параметр end, который указывает на то, что выводится после вывода всех значений, перечисленных в функции print. По умолчанию параметр end равен '\n', то есть следующий вывод будет происходить с новой строки. Этот параметр также можно исправить, например, для того, чтобы убрать все дополнительные выводимые символы можно вызывать функцию print так:

print(a, b, c, sep = '', end = '')

Целочисленная арифметика

Для целых чисел определены ранее рассматривавшиеся операции +, -, * и **. Операция деления / для целых чисел возвращает значение типа float. Также функция возведения в степень возвращает значение типа float, если показатель степени — отрицательное число.

Но есть и специальная операция целочисленного деления, выполняющегося с отбрасыванием дробной части, которая обозначается //. Она возвращает целое число: целую часть частного. Например:

>>> 17 // 3
5
>>> -17 // 3
-6

Другая близкая ей операция: это операция взятия остатка от деления, обозначаемая %:

>>> 17 % 3
2
>>> -17 % 3
1

Под капотом int

Если числа типа float в питоне — это просто стандартный long double, то целые числа — не настолько тривиальны. Их размер ограничен только имеющейся оперативной памятью, поэтому работать с большими числами в питоне можно без дополнительной возни с длинной арифметикой.

Реализованы же они идеологически достаточно просто: модуль числа хранится в 230-ричной системе счисления, то есть просто в массиве беззнаковых 32-битных чисел. Запас в 2 сделан для того, чтобы сумма таких чисел не могла привести к переполнению при обычных операциях. Также хранится количество "цифр". При этом если число отрицательно, то количество "цифр" меняет знак. Сложение таких чисел выполняется "в столбик", умножение тоже в столбик. Если же оба числа более, чем 70-значны, то выполняется умножение Карацубы (На самом деле есть ещё хинт с возведением в квадрат, там используется алгоритм, которых примерно в два раза быстрее обычного умножения, см. 14.16 Algorithm Multiple-precision squaring). В общем ниже кусок оригинального кода (больше подробностей в longobject.c и longintrepr.h).

// Кусок из longintrepr.h

typedef PY_UINT32_T digit;
#define PyLong_SHIFT    30
/* Long integer representation.
   The absolute value of a number is equal to
        SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
   Negative numbers are represented with ob_size < 0;
   zero is represented by ob_size == 0.
   In a normalized number, ob_digit[abs(ob_size)-1] (the most significant
   digit) is never zero.  Also, in all cases, for all valid i,
        0 <= ob_digit[i] <= MASK.
   The allocation function takes care of allocating extra memory
   so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available.
*/

struct _longobject {
        PyObject_VAR_HEAD  // Здесь стандартный заголовок объектов
        digit ob_digit[1];  // Обычно хватит одной цифры. Если потребуется больше, то память выделят
};


// Теперь кусок из longobject.c   

/* Add the absolute values of two long integers. */

static PyLongObject *
x_add(PyLongObject *a, PyLongObject *b)
{
    Py_ssize_t size_a = ABS(Py_SIZE(a)), size_b = ABS(Py_SIZE(b));  // Помните, что отрицальтеный знак размера=отрицательное число?
    PyLongObject *z;
    Py_ssize_t i;
    digit carry = 0;

    /* Ensure a is the larger of the two: */
    if (size_a < size_b) {  // Меняем их местами. Помните, что в питоне это одна строка? :)
        { PyLongObject *temp = a; a = b; b = temp; }
        { Py_ssize_t size_temp = size_a;
            size_a = size_b;
            size_b = size_temp; }
    }
    z = _PyLong_New(size_a+1);  // Сложение - хорошая операция. Почти понятен размер суммы
    if (z == NULL)
        return NULL;
    for (i = 0; i < size_b; ++i) {
        carry += a->ob_digit[i] + b->ob_digit[i];  // Обычное сложение в столбик
        z->ob_digit[i] = carry & PyLong_MASK;
        carry >>= PyLong_SHIFT;
    }
    for (; i < size_a; ++i) {  // Осталось прибавить то, что в большем числе
        carry += a->ob_digit[i];
        z->ob_digit[i] = carry & PyLong_MASK;
        carry >>= PyLong_SHIFT;
    }
    z->ob_digit[i] = carry;
    return long_normalize(z);  // Здесь мы считаем число ведущих нулей и уменьшаем размер
}

01. Первые задачи в тестовой системе; Целочисленные вычисления

01: Гипотенуза

Дано два числа a и b. Выведите гипотенузу треугольника с заданными катетами.

Пример

Ввод Вывод
3
4
5.0
Сдать

02: Следующее и предыдущее

Напишите программу, которая считывает целое число и выводит текст, аналогичный приведенному в примере (пробелы важны!):

Пример

Ввод Вывод
179
The next number for the number 179 is 180.
The previous number for the number 179 is 178.
Сдать

03: Сумма цифр

Дано трехзначное число. Найдите сумму его цифр.

Пример

Ввод Вывод
179
17
Сдать

04: Стоимость покупки

Пирожок в столовой стоит \(a\) рублей и \(b\) копеек. Определите, сколько рублей и копеек нужно заплатить за \(n\) пирожков. Программа получает на вход три числа: \(a\), \(b\), \(n\), и должна вывести два числа: стоимость покупки в рублях и копейках.

Пример

Ввод Вывод
10
15
2
20 30
2
50
4
10 0
Сдать

05: Максимум

Напишите программу, которая считывает два целых числа \(a\) и \(b\) и выводит наибольшее значение из них. Числа — целые неотрицательные.

При решении задачи можно пользоваться только целочисленными арифметическими операциями +, -, *, //, %, =. Нельзя пользоваться нелинейными конструкциями: ветвлениями, циклами, функциями (а также трюками с кортежами, списками и т.п).

Пример

Ввод Вывод
8
5
8
5
8
8
5
5
5
Сдать
Условная инструкция;
  Тип данных bool;
  Логические операторы;
  Каскадные условные инструкции;
  Тернарный оператор

Синтаксис условной инструкции

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

Допустим мы хотим по данному числу x определить его абсолютную величину (модуль). Программа должна напечатать значение переменной x, если x>0 или же величину -x в противном случае. Линейная структура программы нарушается: в зависимости от справедливости условия x>0 должна быть выведена одна или другая величина. Соответствующий фрагмент программы на Питоне имеет вид:

x = int(input())
if x > 0:
    print(x)
else:
    print(-x)

В этой программе используется условная инструкция if (если). После слова if указывается проверяемое условие (x > 0), завершающееся двоеточием. После этого идет блок (последовательность) инструкций, который будет выполнен, если условие истинно, в нашем примере это вывод на экран величины x. Затем идет слово else (иначе), также завершающееся двоеточием, и блок инструкций, который будет выполнен, если проверяемое условие неверно, в данном случае будет выведено значение -x.

Итак, условная инструкция в Питоне имеет следующий синтаксис:

if Условие:
    Блок_инструкций_1
else:
    Блок_инструкций_2

Блок инструкций 1 будет выполнен, если Условие истинно. Если Условие ложно, будет выполнен Блок инструкций 2.

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

if x < 0:
    x = -x
print(x)

В этом примере переменной x будет присвоено значение -x, но только в том случае, когда x<0. А вот инструкция print(x) будет выполнена всегда, независимо от проверяемого условия.

Для выделения блока инструкций, относящихся к инструкции if или else в языке Питон используются отступы. Все инструкции, которые относятся к одному блоку, должны иметь равную величину отступа, то есть одинаковое число пробелов в начале строки. Рекомендуется использовать отступ в 4 пробела и не рекомедуется использовать в качестве отступа символ табуляции.

Это одно из существенных отличий синтаксиса Питона от синтаксиса большинства языков, в которых блоки выделяются специальными словами, например, нц... кц в Кумире, begin... end в Паскале или фигурными скобками в Си.

Вложенные условные инструкции

Внутри условных инструкций можно использовать любые инструкции языка Питон, в том числе и условную инструкцию. Получаем вложенное ветвление – после одной развилки в ходе исполнения программы появляется другая развилка. При этом вложенные блоки имеют больший размер отступа (например, 8 пробелов). Покажем это на примере программы, которая по данным ненулевым числам x и y определяет, в какой из четвертей координатной плоскости находится точка (x,y):

x = int(input())
y = int(input())
if x > 0:
    if y > 0:               # x>0, y>0
        print("Первая четверть")
    else:                   # x>0, y<0
        print("Четвертая четверть")
else:
    if y > 0:               # x<0, y>0
        print("Вторая четверть")
    else:                   # x<0, y<0
        print("Третья четверть")

В этом примере мы использовали комментарии – текст, который интерпретатор игнорирует. Комментариями в Питоне является символ # и весь текст после этого символа до конца строки.

Операторы сравнения

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

<
Меньше — условие верно, если первый операнд меньше второго.
>
Больше — условие верно, если первый операнд больше второго.
<=
Меньше или равно.
>=
Больше или равно.
==
Равенство. Условие верно, если два операнда равны.
!=
Неравенство. Условие верно, если два операнда неравны.

Например, условие (x * x < 1000) означает “значение x * x меньше 1000”, а условие (2 * x != y) означает “удвоенное значение переменной x не равно значению переменной y”.

Операторы сравнения в Питоне можно объединять в цепочки (в отличие от большинства других языков программирования, где для этого нужно использовать логические связки), например, a == b == c или 1 <= x <= 10.

Тип данных bool

Операторы сравнения возвращают значения специального логического типа bool. Значения логического типа могут принимать одно из двух значений: True (истина) или False (ложь). Если преобразовать логическое True к типу int, то получится 1, а преобразование False даст 0. При обратном преобразовании число 0 преобразуется в False, а любое ненулевое число в True. При преобразовании str в bool пустая строка преобразовывается в False, а любая непустая строка в True.

Логические операторы

Иногда нужно проверить одновременно не одно, а несколько условий. Например, проверить, является ли данное число четным можно при помощи условия (n % 2 == 0) (остаток от деления n на 2 равен 0), а если необходимо проверить, что два данных целых числа n и m являются четными, необходимо проверить справедливость обоих условий: n % 2 == 0 и m % 2 == 0, для чего их необходимо объединить при помощи оператора and (логическое И): n % 2 == 0 and m % 2 == 0.

В Питоне существуют стандартные логические операторы: логическое И, логическое ИЛИ, логическое отрицание.

Логическое И является бинарным оператором (то есть оператором с двумя операндами: левым и правым) и имеет вид and. Оператор and возвращает True тогда и только тогда, когда оба его операнда имеют значение True.

Логическое ИЛИ является бинарным оператором и возвращает True тогда и только тогда, когда хотя бы один операнд равен True. Оператор “логическое ИЛИ” имеет вид or.

Логическое НЕ (отрицание) является унарным (то есть с одним операндом) оператором и имеет вид not, за которым следует единственный операнд. Логическое НЕ возвращает True, если операнд равен False и наоборот.

Пример. Проверим, что хотя бы одно из чисел a или b оканчивается на 0:

if a % 10 == 0 or b % 10 == 0:

Проверим, что число a — положительное, а b — неотрицательное:

if a > 0 and not (b < 0):

Или можно вместо not (b < 0) записать (b >= 0).

Каскадные условные инструкции

Пример программы, определяющий четверть координатной плоскости, можно переписать используя “каскадную“ последовательность операцией if... elif... else:

x = int(input())
y = int(input())
if x > 0 and y > 0:
    print("Первая четверть")
elif x > 0 and y < 0:
    print("Четвертая четверть")
elif y > 0:
    print("Вторая четверть")
else:
    print("Третья четверть")

В такой конструкции условия if, ..., elif проверяются по очереди, выполняется блок, соответствующий первому из истинных условий. Если все проверяемые условия ложны, то выполняется блок else, если он присутствует.

Тернарный оператор условия

В языке питон есть тернарный оператор условия, подобный [expression]?[on_true]:[on_false] в языке си. Его синтаксис такой:

[on_true] if [expression] else [on_false]

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

res = 'foo' if x == 1 else 'boo' if x == 2 else 'zoo' if x == 3 else 'goo'

Ситуация, когда тернарный оператор прямо уместен, встречается не так часто.

02. Условная инструкция

06: Високосный год

Дано натуральное число. Требуется определить, является ли год с данным номером високосным. Если год является високосным, то выведите YES, иначе выведите NO. Напомним, что в соответствии с григорианским календарем, год является високосным, если его номер кратен 4, но не кратен 100, а также если он кратен 400.

Эту задачу нужно решать при помощи логических операций.

Ввод Вывод
2010
NO
Сдать

07: Упорядочить три числа

Дано три числа. Упорядочите их в порядке неубывания. Программа должна считывать три числа a, b, c, затем программа должна менять их значения так, чтобы стали выполнены условия a <= b <= c, затем программа выводит тройку a, b, c.

Дополнительные ограничения: нельзя использовать дополнительные переменные (то есть единственной допустимой операцией присваивания является обмен значений двух переменных типа (a, b) = (b, a).

Ввод Вывод
1
2
1
1 1 2
Сдать
Цикл for;
  Функция range;
  Функция enumerate

Цикл for

Цикл for, также называемый циклом с параметром, в языке Питон богат возможностями. В цикле for указывается переменная и множество значений, по которому будет пробегать переменная. Множество значений может быть задано списком, кортежем, строкой или диапазоном.

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

i = 1
for color in 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'violet':
    print(i,'-th color of rainbow is ', color, sep = '')
    i += 1

В этом примере переменная color последовательно принимает значения 'red', 'orange' и т.д. В теле цикла выводится сообщение, которое содержит название цвета, то есть значение переменной color, а также номер итерации цикла  число, которое сначала равно 1, а потом увеличивается на один (инструкцией i += 1 с каждым проходом цикла.

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

for i in 1, 2, 3, 'one', 'two', 'three':
    print(i)

При первых трех итерациях цикла переменная i будет принимать значение типа int, при последующих трех — типа str.

Вернёмся к первому примеру. В случаях, когда вместе с элементами нужны также и их индексы, идеоматический подход в питоне выглядит так:

for i, color in enumerate('red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'violet'):
    print(i+1,'-th color of rainbow is ', color, sep = '')
Функция enumerate возвращает пары из порядкового номера элемента начиная с нуля и собственно самого элемента.

Функция range

Как правило, циклы for используются либо для повторения какой-либо последовательности действий заданное число раз, либо для изменения значения переменной в цикле от некоторого начального значения до некоторого конечного.

Для повторения цикла некоторое заданное число раз n можно использовать цикл for вместе с функцией range:

for i in range(n):
    Тело цикла

В качестве n может использоваться числовая константа, переменная или произвольное арифметическое выражение (например, 2 ** 10). Если значение n равно нулю или отрицательное, то тело цикла не выполнится ни разу.

Если задать цикл таким образом:

for i in range(a, b):
    Тело цикла

то индексная переменная i будеть принимать значения от a до b - 1, то есть первый параметр функции range, вызываемой с двумя параметрами, задает начальное значение индексной переменной, а второй параметр — значение, которая индексная переменная принимать не будет. Если же ab, то цикл не будет выполнен ни разу. Например, для того, чтобы просуммировать значения чисел от 1 до n можно воспользоваться следующей программой:

sum = 0
for i in range(1, n + 1):
    sum += i

В этом примере переменная i принимает значения 1, 2, ..., n, и значение переменной sum последовательно увеличивается на указанные значения.

Наконец, чтобы организовать цикл, в котором индексная переменная будет уменьшаться, необходимо использовать функцию range с тремя параметрами. Первый параметр задает начальное значение индексной переменной, второй параметр — значение, до которого будет изменяться индексная переменная (не включая его!), а третий параметр — величину изменения индексной переменной. Например, сделать цикл по всем нечетным числам от 1 до 99 можно при помощи функции range(1, 100, 2), а сделать цикл по всем числам от 100 до 1 можно при помощи range(100, 0, -1).

Более формально, цикл for i in range(a, b, d) при d > 0 задает значения индексной переменной i = a, i = a + d, i = a + 2 * d и так для всех значений, для которых i < b. Если же d < 0, то переменная цикла принимает все значения i > b.

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

03. Цикл For

08: Ряд - 3

Дано натуральное число n. Напечатайте все n-значные нечетные натуральные числа в порядке убывания.

Ввод Вывод
1
9 7 5 3 1
Сдать

09: Факториал

По данному целому неотрицательному n вычислите значение n!.

Ввод Вывод
5
120
Сдать

10: Четные числа

По данным двум натуральным числам A и B (A≤B) выведите все чётные числа на отрезке от A до B. В этой задаче нельзя использовать инструкцию if.

Ввод Вывод
1
10
2 4 6 8 10
Сдать
Действительные числа;
  Библиотека math;
  Под капотом float

Действительные числа

Сейчас речь пойдет о действительных числах, имеющих тип float.

Обратите внимание, что если вы хотите считать с клавиатуры действительное число, то результат, возвращаемый функцией input() необходимо преобразовывать к типу float:

x = float(input())

Действительные (вещественные) числа представляются в виде чисел с десятичной точкой (а не запятой, как принято при записи десятичных дробей в русский текстах). Для записи очень больших или очень маленьких по модулю чисел используется так называемая запись “с плавающей точкой” (также называемая “научная” запись). В этом случае число представляется в виде некоторой десятичной дроби, называемой мантиссой, умноженной на целочисленную степень десяти (порядок). Например, расстояние от Земли до Солнца равно 1.496·1011, а масса молекулы воды 2.99·10-23.

Числа с плавающей точкой в программах на языке Питон, а также при вводе и выводе записавыются в виде мантиссы, затем пишется буква e, затем пишется порядок. Пробелы внутри этой записи не ставятся. Например, указанные выше константы можно записать в виде 1.496e11 и 2.99e-23. Перед самим числом также может стоять знак минус.

Напомним, что результатом операции деления / всегда является действительное число, в то время как результатом операции // является целое число.

Преобразование действительных чисел к целому производится с округлением в сторону нуля, то есть int(1.7) == 1, int(-1.7) == -1.

Библиотека math

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

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

import math

Функция от одного аргумента вызывается, например, так: math.sin(x) (то есть явно указывается, что из модуля math используется функция sin). Вместо числа x может быть любое число, переменная или выражение. Функция возращает значение, которое можно вывести на экран, присвоить другой переменной или использовать в выражении:

y = math.sin(x)
print(math.sin(math.pi/2))

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

from math import *

y = sin(x)
print(sin(pi/2))

Ниже приведен список основных функций модуля math. Более подробное описание этих функций можно найти на сайте с документацией на Питон.

Некоторые из перечисленных функций (int, round, abs) являются стандартными и не требуют подключения модуля math для использования.

Функция Описание
Округление
int(x) Округляет число в сторону нуля. Это стандартная функция, для ее использования не нужно подключать модуль math.
round(x) Округляет число до ближайшего целого. Если дробная часть числа равна 0.5, то число округляется до ближайшего четного числа.
round(x, n) Округляет число x до n знаков после точки. Это стандартная функция, для ее использования не нужно подключать модуль math.
floor(x) Округляет число вниз (“пол”), при этом floor(1.5) == 1, floor(-1.5) == -2
ceil(x) Округляет число вверх (“потолок”), при этом ceil(1.5) == 2, ceil(-1.5) == -1
trunc(x) Округление в сторону нуля (так же, как функция int).
abs(x) Модуль (абсолютная величина). Это - стандартная функция.
fabs(x) Модуль (абсолютная величина). Эта функция всегда возвращает значение типа float.
Корни, степени, логарифмы
sqrt(x) Квадратный корень. Использование: sqrt(x)
pow(a, b) Возведение в степень, возвращает ab. Использование: pow(a,b)
exp(x) Экспонента, возвращает ex. Использование: exp(x)
log(x) Натуральный логарифм. При вызове в виде log(x, b) возвращает логарифм по основанию b.
log10(x) Десятичный логарифм
e Основание натуральных логарифмов \(e\approx2{,}71828...\).
Тригонометрия
sin(x) Синус угла, задаваемого в радианах
cos(x) Косинус угла, задаваемого в радианах
tan(x) Тангенс угла, задаваемого в радианах
asin(x) Арксинус, возвращает значение в радианах
acos(x) Арккосинус, возвращает значение в радианах
atan(x) Арктангенс, возвращает значение в радианах
atan2(y, x) Полярный угол (в радианах) точки с координатами (x, y).
hypot(a, b) Длина гипотенузы прямоугольного треугольника с катетами a и b.
degrees(x) Преобразует угол, заданный в радианах, в градусы.
radians(x) Преобразует угол, заданный в градусах, в радианы.
pi Константа π

Числа с плавающей точкой

В языке Python реализована длинная арифметика. Это означает, что целые числа в вычисления могут быть сколь угодны большими, длина чисел ограничена только объёмом доступной памяти. Тонкости реализации длинной арифметики были описаны выше. Для дробных чисел типа float используется представление в виде чисел с плавающей точкой, соответствующее 64-битному типу long double в языке C. Каждое число типа float хранится в виде \(\pm a\cdot 2^b\). Числа типа float обеспечивают точность в 15-17 десятичных цифр и масштабы в диапазоне примерно от 10−308 до 10308. Число \(a\) называется мантиссой, число \(b\) — экспонентой или порядком. На хранение экспоненты отводится 11 бит, на хранение мантиссы — 52 бита. Ещё один бит отводится на хранение знака числа. В итоге получается 64 бита или 8 байт.

Знак
(11 бит)
Порядок
(52 бита)
Мантисса
63 56 55 48 47 40 39 32 31 24 23 16 15 8 7 0
Если быть точнее, то числа хранятся в виде \(\pm \displaystyle(1+a/2^{52})\cdot 2^{b - 1023}\). Таким образом, для представления числа 1 нужно взять \(a=0\) и \(b=1023=1024-1\). В результате получится 0 01111111111 0000000000000000000000000000000000000000000000000000.
Самое маленькое число, большее 1, очевидно, представляется в виде 0 01111111111 0000000000000000000000000000000000000000000000000001 и равно примерно 1.0000000000000002.

При хранении чисел с таким подходом есть забавные особенности. Во-первых, есть две различных записи нуля — +0 и -0

'0 00000000000 0000000000000000000000000000000000000000000000000000'
'1 00000000000 0000000000000000000000000000000000000000000000000000'
Другая особенность — есть числа \(+\infty\) и \(-\infty\), которые можно получить при помощи кода float('inf') и float('-inf'). Эти числа ведут себя как и полагается бесконечностям. Скажем, положительная бесконечность больше любого конечного числа. При умножении на положительное число остаётся бесконечностью, при умножении на отрицательное — отрицательной.
'0 11111111111 0000000000000000000000000000000000000000000000000000'
'1 11111111111 0000000000000000000000000000000000000000000000000000'

За дальнейшими подробностями можно обратиться к википедии, а для экспериментов над представлениями чисел типа float можно использовать следующий код:

import struct
def p(x): x = ''.join('{:08b}'.format(el) for el in struct.pack('d', x)[::-1]); return x[0]+'1'+x[1:12]+'-'+x[12:]
print(p(-2**1023))
04. Действительные числа

11: Дробная часть

Дано положительное действительное число X. Выведите его дробную часть абсолютно точно. Исходное число содержит не более 6 знаков после десятичной точки.

Ввод Вывод
17.9
0.9
Сдать

12: Первая цифра после точки

Дано положительное действительное число X. Выведите его первую цифру после десятичной точки. При решении этой задачи нельзя пользоваться условной инструкцией и циклом.

Ввод Вывод
1.79
7
Сдать

13: Округление по российским правилам

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

Дано неотрицательное число x, округлите его по этим правилам. Обратите внимание, что функция round не годится для этой задачи!

Ввод Вывод
2.3
2
2.5
3
Сдать

14: Проценты

Процентная ставка по вкладу составляет P процентов годовых, которые прибавляются к сумме вклада. Вклад составляет X рублей Y копеек. Определите размер вклада через год.

Программа получает на вход целые числа P, X, Y и должна вывести два числа: величину вклада через год в рублях и копейках. Дробная часть копеек отбрасывается.

При решении этой задачи нельзя пользоваться условными инструкциями и циклами.

Ввод Вывод
12
179
0
200 48
Сдать

15: Сложные проценты

Процентная ставка по вкладу составляет P процентов годовых, которые прибавляются к сумме вклада через год. Вклад составляет X рублей Y копеек. Определите размер вклада через K лет.

Программа получает на вход целые числа P, X, Y, K и должна вывести два числа: величину вклада через год в рублях и копейках. Дробное число копеек по истечение года отбрасывается. Перерасчет суммы вклада (с отбрасыванием дробных частей копеек) происходит ежегодно.

Ввод Вывод
12
179
0
5
315 43
Сдать

16: Просто π

По данному числу n вычислите сумму \( 4\left(1-\displaystyle\frac13+\displaystyle\frac15-\displaystyle\frac17+...+\displaystyle\frac{(-1)^n}{2n+1}\right)\)

Операцией возведения в степень пользоваться нельзя. Алгоритм должен иметь сложность O(n).

Ввод Вывод
2
3.466666666666667

Этот ряд сходится к числу \(\pi\).
Сдать

Цикл while;

Цикл while

Цикл while (“пока”) позволяет выполнить одну и ту же последовательность действий, пока проверяемое условие истинно. Условие записывается до тела цикла и проверяется до выполнения тела цикла. Как правило, цикл while используется, когда невозможно определить точное значение количества проходов исполнения цикла.

Синтаксис цикла while в простейшем случае выглядит так:

while условие:
    блок инструкций

При выполнении цикла while сначала проверяется условие. Если оно ложно, то выполнение цикла прекращается и управление передается на следующую инструкцию после тела цикла while. Если условие истинно, то выполняется инструкция, после чего условие проверяется снова и снова выполняется инструкция. Так продолжается до тех пор, пока условие будет истинно. Как только условие станет ложно, работа цикла завершится и управление передастся следующей инструкции после цикла.

Например, следующий фрагмент программы напечатает на экран квадраты всех целых чисел от 1 до 10. Видно, что цикл while может заменять цикл for ... in range(...):

i = 1
while i <= 10:
    print(i)
    i += 1

В этом примере переменная i внутри цикла изменяется от 1 до 10. Такая переменная, значение которой меняется с каждым новым проходом цикла, называется счетчиком. Заметим, что после выполнения этого фрагмента значение переменной i будет равно 11, поскольку именно при i==11 условие i<=10 впервые перестанет выполняться.

Вот еще один пример использования цикла while для определения количества цифр натурального числа n:

n = int(input())
length = 0
while n > 0:
    n //= 10
    length += 1

В этом цикле мы отбрасываем по одной цифре числа, начиная с конца, что эквивалентно целочисленному делению на 10 (n //= 10), при этом считаем в переменной length, сколько раз это было сделано.

В языке Питон есть и другой способ решения этой задачи: length = len(str(i)).

Инструкции управления циклом

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

i = 1
while i <= 10:
    print(i)
    i += 1
else:
    print('Цикл окончен, i =', i)

Казалось бы, никакого смысла в этом нет, ведь эту же инструкцию можно просто написать после окончания цикла. Смысл появляется только вместе с инструкцией break, использование которой внутри цикла приводит к немедленному прекращению цикла, и при этом не исполняется ветка else. Разумеется, инструкцию break осмыленно вызывать только из инструкции if, то есть она должна выполняться только при выполнении какого-то особенного условия.

Другая инструкция управления циклом — continue (продолжение цикла). Если эта инструкция встречается где-то посередине цикла, то пропускаются все оставшиеся инструкции до конца цикла, и исполнение цикла продолжается со следующей итерации.

Инструкции break, continue и ветка else: можно использовать и внутри цикла for. Тем не менее, увлечение инструкциями break и continue не поощряется, если можно обойтись без их использования. Вот типичный пример плохого использования инструкции break.

while True:
    length += 1
    n //= 10
    if n == 0:
        break
05. Цикл while

17: Банковские проценты

Вклад в банке составляет \(x\) рублей. Ежегодно он увеличивается на \(p\) процентов, после чего дробная часть копеек отбрасывается. Определите, через сколько лет вклад составит не менее \(y\) рублей.

Программа получает на вход три натуральных числа: \(x\), \(p\), \(y\) и должна вывести одно целое число.

Ввод Вывод
100
10
200
8
Сдать
Функции, рекурсия;
  Локальные, глобальные и нелокальные переменные;
  Распаковка;
  Именованные и необязательные параметры;
  Лямбда-функции;
  Замыкания

Функции в языке Python

В формуле для числа сочетаний из n элементов по k необходимо вычисление факториалов трех величин: n, k и n-k. Для этого можно сделать три цикла, что приводит к увеличению размера программы за счет трехкратного повторения похожего кода. Вместо этого лучше сделать одну функцию, вычисляющую факториал любого данного числа n и трижды использовать эту функцию в своей программе. Соответствующая функция может выглядеть так:

def factorial(n):
    f = 1
    for i in range(2, n + 1):
        f *= i
    return f

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

Далее идет тело функции, оформленное в виде блока, то есть с отступом. Внутри функции вычисляется значение факториала числа n и оно сохраняется в переменной f. Функция завершается инструкцией return f, которая завершает работу функции и возвращает значение переменной f. Инструкция return может встречаться в произвольном месте функции, ее исполнение завершает работу функции и возвращает указанное значение в место вызова. Если функция не возвращает значения, то инструкция return используется без возвращаемого значения, также в функциях, не возвращающих значения, инструкция return может отсутствовать.

Теперь мы можем использовать нашу функцию несколько раз. В этом примере мы трижды вызываем функцию factorial для вычисления трех факториалов: factorial(n), factorial(k), factorial(n-k).

n = int(input())
k = int(input())
print(factorial(n) // (factorial(k) * factorial(n - k)))

Мы также можем, например, объявить функцию binomial, которая принимает два целочисленных параметра n и k и вычисляет число сочетаний из n по k:

def binomial(n, k)
    return factorial(n) // (factorial(k) * factorial(n - k))

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

print(binomial(n, k))

Вернемся к задаче нахождения наибольшего из двух или трех чисел. Функцию нахождения максимума из двух чисел можно написать так:

def max(a, b):
    if a > b:
        return a
    else:
        return b

Теперь мы можем реализовать функцию max3, находящую максимум трех чисел:

def max3(a, b, c):
    return max(max(a, b), c)

Функция max3 дважды вызывает функцию max для двух чисел: сначала, чтобы найти максимум из a и b, потом чтобы найти максимум из этой величины и c.

Возвращение нескольких значений

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

def f(a, b):
  return (a + b, b ** 3)

Тогда результат вызова функции тоже нужно присваивать кортежу:

(n, m) = f(a, b)

Локальные и глобальные переменные

Внутри функции можно использовать переменные, объявленные вне этой функции

def f():
    print(a)
a = 1
f()

Здесь переменной a присваивается значение 1, и функция f печатает это значение, несмотря на то, что выше функции f эта переменная не инициализируется. Но в момент вызова функции f переменной a уже присвоено значение, поэтому функция f может вывести его на экран.

Такие переменные (объявленные вне функции, но доступные внутри функции) называются глобальными.

Но если инициализировать какую-то переменную внутри функции, использовать эту переменную вне функции не удастся. Например:

def f():
    a = 1
f()
print(a)

Получим NameError: name 'a' is not defined. Такие переменные, объявленные внутри функции, называются локальными. Эти переменные становятся недоступными после выхода из функции.

Интересным получится результат, если попробовать изменить значение глобальной переменной внутри функции:

def f():
    a = 1
    print(a)
a = 0
f()
print(a)

Будут выведены числа 1 и 0. То есть несмотря на то, что значение переменной a изменилось внутри функции, то вне функции оно осталось прежним! Это сделано в целях “защиты” глобальных переменных от случайного изменения из функции (например, если функция будет вызвана из цикла по переменной i, а в этой функции будет использована переменная i также для организации цикла, то эти переменные должны быть различными). То есть если внутри функции модифицируется значение некоторой переменной, то переменная с таким именем становится локальной переменной, и ее модификация не приведет к изменению глобальной переменной с таким же именем.

Более формально: интерпретатор Питон считает переменную локальной, если внутри нее есть хотя бы одна инструкция, модифицирующая значение переменной (это может быть оператор =, += и т.д., или использование этой переменной в качестве параметра цикла for, то эта переменная считается локальной и не может быть использована до инициализации. При этом даже если инструкция, модицифицирующая переменную никогда не будет выполнена: интерпретатор это проверить не может, и переменная все равно считается локальной. Пример:

def f():
    print(a)
    if False:
        a = 0
a = 1
f()

Возникает ошибка: UnboundLocalError: local variable 'a' referenced before assignment. А именно, в функции f идентификатор a становится локальной переменной, т.к. в функции есть команда, модифицирующая переменную a, пусть даже никогда и не выполняющийся (но интерпретатор не может это отследить). Поэтому вывод переменной a приводит к обращению к неинициализированной локальной переменной.

Чтобы функция могла изменить значение глобальной переменной, необходимо объявить эту переменную внутри функции, как глобальную, при помощи ключевого слова global:

def f():
    global a
    print(a)
    a = 1
    print(a)
a = 0
f()
print(a)

В этом примере на экран будет выведено 1 1 1, так как переменная a объявлена, как глобальная, и ее изменение внутри функции приводит к тому, что и вне функции переменная будет доступна. Более того, описанная таким образом переменная может быть даже создана в глобальной области видимости:

def f():
    global a; a = 1;
f()
print(a)
>>> 1

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

nonlocal-переменные

Кроме глобальных переменных бывают nonlocal-переменные. Они очень похожи на глобальные. При ссылке переменную, которая не определена в текущей функции, будет использована "ближайшая" из локальных переменных в объемлющих функциях, либо в глобальной области видимости. Если же переменную пометить как nonlocal, то поиск переменной не будет производиться за пределами инструкций def ни в глобальной области видимости объемлющего модуля, ни во встроенной области видимости, даже если переменные с такими именами там существуют. Переменные, помеченные как nonlocal, можно менять внутри функции, при этом будет изменяться её значение в соответствующей объемлющей функции. В отличие от global декларация nonlocal не позволяет создать переменную во внешней области видимости.

def tester():
    state = 1
    print(state)
    def nested():
        state = 2
        print(state)
    nested()

    print(state)
tester()
>>> 1
>>> 2
>>> 1
def tester():
    state = 1
    print(state)
    def nested():
        nonlocal state
        state = 2
        print(state)
    nested()

    print(state)
tester()
>>> 1
>>> 2
>>> 2

Рекурсия

                      Эпиграф:
                        def short_story():
                            print("У попа была собака, он ее любил.")
                            print("Она съела кусок мяса, он ее убил,")
                            print("В землю закопал и надпись написал:")
                            short_story()

Как мы видели выше, функция может вызывать другую функцию. Но функция также может вызывать и саму себя! Рассмотрим это на примере функции вычисления факториала. Хорошо известно, что \(0!=1\), \(1!=1\). А как вычислить величину \(n!\) для большого \(n\)? Если бы мы могли вычислить величину \((n-1)!\), то тогда мы легко вычислим \(n!\), поскольку \(n!=n(n-1)\)!. Но как вычислить \((n-1)!\)? Если бы мы вычислили \((n-2)!\), то мы сможем вычисли и \((n-1)!=(n-1)(n-2)!\). А как вычислить \((n-2)!\)? Если бы... В конце концов, мы дойдем до величины \(0!\), которая равна \(1\). Таким образом, для вычисления факториала мы можем использовать значение факториала для меньшего числа. Это можно сделать и в программе на Питоне:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

Подобный прием (вызов функцией самой себя) называется рекурсией, а сама функция называется рекурсивной.

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

  1. Неправильное оформление выхода из рекурсии. Например, если мы в программе вычисления факториала забудем поставить проверку if n == 0, то factorial(0) вызовет factorial(-1), тот вызовет factorial(-2) и т.д.
  2. Рекурсивный вызов с неправильными параметрами. Например, если функция factorial(n) будет вызывать factorial(n), то также получиться бесконечная цепочка.

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

Распаковка

Для того, чтобы сделать использование функций ещё удобнее, в Python есть оператор * — распаковка. Его можно применять только применительно к параметрам вызываемой функции. При этом происходит следующее: из объекта, к которому применяется распаковка, извлекаются отдельные элементы и передаются в качестве отдельных параметров: Рассмотрим пример:
def func1():
    return 2, 5, 7

def func2(x, y, z):
    print(x + y + z)

func2(*func1())
Здесь функция func1() возвращает кортеж из трёх чисел, а функция func2() принимает на вход три элемента. Результат работы первой функции можно передать сразу во вторую, если распаковать результат её работы.

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

def func1(x, y):
    print(x + y)

def func2(x, y):
    print((x ** 2 + y ** 2) ** 0.5)

pars = (3, 7)
func1(*pars)
func2(*pars)

Очень удобно использовать распаковку — внутри функции print:

def fibonacci(n):
    fib1, fib2 = 0, 1
    for i in range(n):
        fib1, fib2 = fib2, fib1 + fib2
        yield fib1
print(*fibonacci(10))
Таким образом, можно быстро выводить содержимое списков, результаты работы генераторов или функций, возвращающих кортежи или списки.

Также при помощи распаковки можно передавать числовые параметры из стандартного ввода в функцию в одну строчку:

def max(a, b, c):
    return max(max(a, b), c)
print(max(*map(int, input().split())))

Необязательные и именованные переменные

Переменные у функции могут быть необязательными. Для того, чтобы сделать переменную необязательной, после её декларации нужно указать её значение по умолчанию
def add5(a, b=5):
    return a + b

>>> add5(3)
8
>>> add5(3, 10)
13

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

def foo(fst, snd):
    return fst, snd
>>> foo(1,2)
(1, 2)
>>> foo(snd=2, fst=1)
(1, 2)

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

def foo(fst, *, snd=None):
    return fst, snd
>>> foo(1)
(1, None)
>>> foo(1,2)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: foo() takes 1 positional argument but 2 were given
>>> foo(1, snd=2)
(1, 2)

Лямбда-функции

В тех случаях, когда у функции нет необязательных параметров, а её тело может быть записано в одну строчку, существует другой способ записи функции:
foo = lambda ([параметры]): ([выражение])
# Синоним
def foo([параметры]):
    return [выражение]
Однако приведённый пример является анти-паттерном. Лямбда функции предназначены для использования там, где функция передаётся в качестве параметра и более нигде не используется.

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

# Числа Фибоначчи
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:f(x,f), range(20))))
>>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

# Факториал
print((lambda n,f:f(n,1,f))(20,(lambda n,m,f:m if n<=1 else f(n-1,m*n,f))))
>>> 2432902008176640000

# В некоторых случаях даже функции не нужны. Первые простые числа
print([x for x in range(2, 101) if all(x % i for i in range(2, int(x**0.5)+1))])
>>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Замыкание

Одно из интересных понятий функционального программирования — это замыкания (closure). Эта идея оказалась настолько заманчивой для многих разработчиков, что была реализована даже в некоторых нефункциональных языках программирования (Perl). Девид Мертц приводит следующее определение замыкания: "Замыкание - это процедура вместе с привязанной к ней совокупностью данных" (в противовес объектам в объектном программировании, как: "данные вместе с привязанным к ним совокупностью процедур" ).
Смысл замыкания состоит в том, что определение функции "замораживает" окружающий её контекст на момент определения. Это может делаться различными способами, например, за счёт параметризации создания функции. При замыкании будут использованы переменные из объемлющих функций. Для того, чтобы переменная попала в замыкание, она должна быть упомянута в коде функции:
def print_msg(msg):
    def fun():
        print(msg)
    return fun  # Помните, что в питоне всё - это объект. Функция тоже, её можно вернуть и воспользоваться потом.

printer = print_msg("Hello")
printer()
>>>Hello
def multiplier( n ):  # multiplier возвращает функцию умножения на n
    def mul( k ):
        return n * k
    return mul

mul3 = multiplier(3)  # mul3 - функция, умножающая на 3
print(mul3(3), mul3(5))
>>> 9 15

Правильно захваченные переменные при этом можно даже успешно модифицировать:

def tester(start):
    state = start  # В каждом вызове сохраняется свое значение state
    def nested(label):
        nonlocal state      # Объект state находится 
        print(label, state) # в объемлющей области видимости
        state += 1 # Изменит значение переменной, объявленной как nonlocal
    return nested

>>> F = tester(0)
>>> F('spam')          # Будет увеличивать значение state при каждом вызове
spam 0
>>> F('ham')
ham 1
>>> F('eggs')
eggs 2
06. Функции, рекурсия

09: Минимальный делитель числа

Дано натуральное число \(n>1\). Выведите его наименьший делитель, отличный от 1.

Решение оформите в виде функции min_divisor(n). Алгоритм должен иметь сложность \(O(\sqrt{n})\).

Указание. Если у числа \(n\) нет делителя не превосходящего \(\sqrt{n}\), то число \(n\) — простое и ответом будет само число \(n\).

Ввод Вывод
4
2
5
5
Сдать

19: Ханойские башни

Головоломка “Ханойские башни” состоит из трех стержней, пронумерованных числами 1, 2, 3. На стержень 1 надета пирамидка из \(n\) дисков различного диаметра в порядке возрастания диаметра. Диски можно перекладывать с одного стержня на другой по одному, при этом диск нельзя класть на диск меньшего диаметра. Необходимо переложить всю пирамидку со стержня 1 на стержень 3 за минимальное число перекладываний.

Напишите программу, которая решает головоломку; для данного числа дисков n печатает последовательность перекладываний в формате a b c, где a — номер перекладываемого диска, b — номер стержня с которого снимается данный диск, c — номер стержня на который надевается данный диск.

Например, строка 1 2 3 означает перемещение диска номер 1 со стержня 2 на стержень 3. В одной строке печатается одна команда. Диски пронумерованы числами от 1 до n в порядке возрастания диаметров.

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

Указание: подумайте, как переложить пирамидку из одного диска? Из двух дисков? Из трех дисков? Из четырех дисков? Пусть мы научились перекладывать пирамидку из \(n\) дисков с произвольного стержня на любой другой, как переложить пирамидку из \(n+1\) диска, если можно пользоваться решением для \(n\) дисков.

Напишите функцию move (n, x, y), которая печатает последовательнось перекладываний дисков для перемещения пирамидки высоты n со стержня номер x на стержень номер y.

Ввод Вывод
2
1 1 2
2 1 3
1 2 3
Сдать
Строки;
  Срезы;
  Методы строк;
  Форматирование;
  Под капотом строк;
  Альтернативы строкам: bytes, bytearray, array

Строки

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

a = 'В этой строке можно использовать символ ", а обычную кавычку нужно экранировать'
b = "Здесь можно писать ', но " нужно экранировать: \" "
с = '''Строка на
есколько строк (можно писать ' и " свободно)'''
d = """То же самое, только кавычки другие\
нужно использовать \, чтобы экранировать конец строки"""
Конечно же, строки можно также получать из других типов приведением к строке, а также получать из внешних источников.

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

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

>>> S = 'Hello'
>>> print(len(S))
5

Проверять на вхождение подстроки можно при помощи оператора in:

>>> 'foo' in 'boo and foo and zoo'
True
>>> 'goo' in 'boo and foo and zoo'
False

Срезы (slices)

Срез (slice) — извлечение из данной строки одного символа или некоторого фрагмента подстроки или подпоследовательности.

Есть три формы срезов. Самая простая форма среза: взятие одного символа строки, а именно, S[i] — это срез, состоящий из одного символа, который имеет номер i, при этом считая, что нумерация начинается с числа 0. То есть если S='Hello', то S[0]=='H', S[1]=='e', S[2]=='l', S[3]=='l', S[4]=='o'.

Номера символов в строке (а также в других структурах данных: списках, кортежах) называются индексом.

Если указать отрицательное значение индекса, то номер будет отсчитываться с конца, начиная с номера -1. То есть S[-1]=='o', S[-2]=='l', S[-3]=='l', S[-4]=='e', S[-5]=='H'.

Или в виде таблицы:

Строка S H e l l o
Индекс S[0] S[1] S[2] S[3] S[4]
Индекс S[-5] S[-4] S[-3] S[-2] S[-1]

Если же номер символа в срезе строки S больше либо равен len(S), или меньше, чем -len(S), то при обращении к этому символу строки произойдет ошибка IndexError: string index out of range.

Срез с двумя параметрами: S[a:b] возвращает подстроку из b-a символов, начиная с символа c индексом a, то есть до символа с индексом b, не включая его. Например, S[1:4]=='ell', то же самое получится если написать S[-4:-1]. Можно использовать как положительные, так и отрицательные индексы в одном срезе, например, S[1:-1] — это строка без первого и последнего символа (срез начинается с символа с индексом 1 и заканчиватеся индексом -1, не включая его).

При использовании такой формы среза ошибки IndexError никогда не возникает. Например, срез S[1:5] вернет строку 'ello', таким же будет результат, если сделать второй индекс очень большим, например, S[1:100] (если в строке не более 100 символов).

Если опустить второй параметр (но поставить двоеточие), то срез берется до конца строки. Например, чтобы удалить из строки первый символ (его индекс равен 0, то есть взять срез, начиная с символа с индексом 1), то можно взять срез S[1:], аналогично если опустиить первый параметр, то срез берется от начала строки. То есть удалить из строки последний символ можно при помощи среза S[:-1]. Срез S[:] совпадает с самой строкой S.

Если задать срез с тремя параметрами S[a:b:d], то третий параметр задает шаг, как в случае с функцией range, то есть будут взяты символы с индексами a, a+d, a+2*d и т.д. При задании значения третьего параметра, равному 2, в срез попадет кажый второй символ, а если взять значение среза, равное -1, то символы будут идти в обратном порядке.

В тех случаях, когда нужно проверить, что строка начинается или заканчивается заданным образом, принято использовать не срезы, а методы startswith и endswith.

>>> a = 'foo boo zoo'
>>> a[:3] == 'foo'
True
>>> a.startswith('foo')
True

Методы строк

Метод — это функция, применяемая к объекту, в данном случае — к строке. Метод вызывается в виде Имя_объекта.Имя_метода(параметры). Например, S.find("e") — это применение к строке S метода find с одним параметром "e".

Метод find и rfind

Метод find находит в данной строке (к которой применяется метод) данную подстроку (которая передается в качестве параметра). Функция возвращает индекс первого вхождения искомой подстроки. Если же подстрока не найдена, то метод возвращает значение -1. Например:

>>> S = 'Hello'
>>> print(S.find('e'))
1
>>> print(S.find('ll'))
2
>>> print(S.find('L'))
-1

Аналогично, метод rfind возвращает индекс последнего вхождения данной строки (“поиск справа”).

>>> S = 'Hello'
>>> print(S.find('l'))
2
>>> print(S.rfind('l'))
3

Если вызвать метод find с тремя параметрами S.find(T, a, b), то поиск будет осуществляться в срезе S[a:b]. Если указать только два параметра S.find(T, a), то поиск будет осуществляться в срезе S[a:], то есть начиная с символа с индексом a и до конца строки. Метод S.find(T, a, b) возращает индекс в строке S, а не индекс относительно начала среза.

Метод replace

Метод replace заменяет все вхождения одной строки на другую. Формат: S.replace(old, new) — заменить в строке S все вхождения подстроки old на подстроку new. Пример:

>>> 'Hello'.replace('l', 'L')
'HeLLo'

Если методу replace задать еще один параметр: S.replace(old, new, count), то заменены будут не все вхождения, а только не больше, чем первые count из них.

>>> 'Abrakadabra'.replace('a', 'A', 2)
'AbrAkAdabra'

Метод count

Подсчитывает количество вхождений одной строки в другую строку. Простейшая форма вызова S.count(T)  возвращает число вхождений строки T внутри строки S. При этом подсчитываются только непересекающиеся вхождения, например:

>>> 'Abracadabra'.count('a')
4
>>> ('a' * 100000).count('aa')
50000

При указании трех параметров S.count(T, a, b), будет выполнен подсчет числа вхождений строки T в срез S[a:b].

Другие методы

Обширный список других методов можно найти непосредственно в документации, либо выполнив help(str) в консоли.

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

str.capitalize()
str.casefold()
str.center(width[, fillchar])
str.count(sub[, start[, end]])
str.encode(encoding="utf-8", errors="strict")
str.endswith(suffix[, start[, end]])
str.expandtabs(tabsize=8)
str.find(sub[, start[, end]])
str.format(*args, **kwargs)
str.format_map(mapping)
str.index(sub[, start[, end]])
str.isalnum()
str.isalpha()
str.isdecimal()
str.isdigit()
str.isidentifier()
str.islower()
str.isnumeric()
str.isprintable()
str.isspace()
str.istitle()
str.isupper()
str.join(iterable)
str.ljust(width[, fillchar])
str.lower()
str.lstrip([chars])
str.partition(sep)
str.replace(old, new[, count])
str.rfind(sub[, start[, end]])
str.rindex(sub[, start[, end]])
str.rjust(width[, fillchar])
str.rpartition(sep)
str.rsplit(sep=None, maxsplit=-1)
str.rstrip([chars])
str.split(sep=None, maxsplit=-1)
str.splitlines([keepends])
str.startswith(prefix[, start[, end]])
str.strip([chars])
str.swapcase()
str.title()
str.translate(table)
str.upper()¶
str.zfill(width)
Кроме того, в модуле string можно найти ряд полезных констант:
string.ascii_letters   = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
string.ascii_lowercase = abcdefghijklmnopqrstuvwxyz
string.ascii_uppercase = ABCDEFGHIJKLMNOPQRSTUVWXYZ
string.digits          = 0123456789
string.hexdigits       = 0123456789abcdefABCDEF
string.octdigits       = 01234567
string.punctuation     = !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
string.printable       = 0123456789abcdefghijklmnopqrstuvwxyzABCD...
string.whitespace      = " \t..."
Они включают себя только ASCII символы, поэтому разные тонкие вещи с юникодом решать не помогут.

Форматирование

Иногда (а точнее, довольно часто) возникают ситуации, когда нужно сделать строку, подставив в неё некоторые данные, полученные в процессе выполнения программы (пользовательский ввод, данные из файлов и т. д.). В этом случае для подстановки этих значений можно использовать строковый метод format. При этом в шаблоне в места подстановок нужно поставить якори вида {}, а в параметры format передать ровно необходимое количество значений. Однако возможностей у format гораздо больше: кроме непосредственной подстановки в строку возможно применение форматирования в выводимым данным. Полный список возможностей можно получить непосредственно в документации, а здесь будет список разумных примеров использования этого метода:

>>> '{0}, {1}, {2}'.format('a', 'b', 'c')
'a, b, c'
>>> '{}, {}, {}'.format('a', 'b', 'c')  # 3.1+ only
'a, b, c'
>>> '{2}, {1}, {0}'.format('a', 'b', 'c')
'c, b, a'
>>> '{2}, {1}, {0}'.format(*'abc')      # unpacking argument sequence
'c, b, a'
>>> '{0}{1}{0}'.format('abra', 'cad')   # arguments' indices can be repeated
'abracadabra'


>>> 'Coordinates: {latitude}, {longitude}'.format(latitude='37.24N', longitude='-115.81W')
'Coordinates: 37.24N, -115.81W'
>>> coord = {'latitude': '37.24N', 'longitude': '-115.81W'}
>>> 'Coordinates: {latitude}, {longitude}'.format(**coord)
'Coordinates: 37.24N, -115.81W'


>>> '{:<30}'.format('left aligned')
'left aligned                  '
>>> '{:>30}'.format('right aligned')
'                 right aligned'
>>> '{:^30}'.format('centered')
'           centered           '
>>> '{:*^30}'.format('centered')  # use '*' as a fill char
'***********centered***********'


>>> '{:+f}; {:+f}'.format(3.14, -3.14)  # show it always
'+3.140000; -3.140000'
>>> '{: f}; {: f}'.format(3.14, -3.14)  # show a space for positive numbers
' 3.140000; -3.140000'
>>> '{:0.2f}; {:0.3f}'.format(3.14, -3.14)  # fixed number of digits
'3.14; -3.140' 

>>> '{:,}'.format(1234567890)
'1,234,567,890'

Под капотом строк

Этому посвящена любопытная статья. Если коротко, то строка хранится как массив двухбайтовых символов. Однако если в строке используется хотя бы один символ, который не живёт в двухбайтовом юникоде, то вся строка переводится в четырёхбайтовый режим. Отдельно односимвольные строки "кешируются", и в результате всегда ссылаются на один и тот же объект. В частности, срезы, состоящие из одного символа, используют этот механизм. Для поиска подстроки используется алгоритм Бойера — Мура (плюс несколько хаков).

Альтернативы строкам: bytes, bytearray, array

Строки обладают двумя специфическими особенностями, определяющими область их применения. Во-первых, строки — это последовательности юникодных символов. В частности бинарный файл обычно нельзя прочитать в строку. Во-вторых, у строки нельзя менять отдельные символы, а также приклеивать другие строки в хвост. То есть приклеивать, конечно, можно, но код вида
res = ''
for i in range(100):
    next_str = str(i)
    res += next_str + ' '
создаст в процессе работе 200 различных объектов-строк, работая в итоге за квадратичное время.

Для решения первой проблемы есть тип bytes — как раз бинарные строки. Бинарные строки очень похоже на обычные, только перед открывающей кавычкой нужно ставить букву b, например b'hello'. Внутри при этом могут быть только ASCII символы, либо явно hex-коды: b'\x00\x01\x02. С бинарными строками можно делать почти всё то же, что и с обычными. Превращения между бинарными и обычными строками происходит следующим образом:

>>> test_str = 'Привет, мир! Hello, world!'
>>> bin_cp1251 = test_str.encode('1251')
>>> bin_utf8 = test_str.encode('utf-8')
>>> bin_cp1251
b'\xcf\xf0\xe8\xe2\xe5\xf2, \xec\xe8\xf0! Hello, world!'
>>> bin_utf8
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, \xd0\xbc\xd0\xb8\xd1\x80! Hello, world!'
>>> bin_cp1251.decode('1251')
'Привет, мир! Hello, world!'
>>> bin_utf8.decode('utf-8')
'Привет, мир! Hello, world!'
>>> bin_cp1251.decode('utf-8')
Traceback (most recent call last):
  File "", line 1, in 
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte

Следующим идёт тип bytearray — изменяемый массив битовых символов. И его строковый брат array('u'):

>>> from array import array
>>> ts = array('u')
>>> ts.fromunicode('Hello. world')
>>> ts[5] = ','
>>> ts.append('!')
>>> ts
array('u', 'Hello, world!')
>>> tb = b'Hello. world'
>>> tb = bytearray(b'Hello. world')
>>> tb[5] = ord(b',')
>>> tb += b'!'
>>> tb
bytearray(b'Hello, world!')
07. Строки

04: Переставить два слова

Дана строка, состоящая ровно из двух слов, разделенных пробелом. Переставьте эти слова местами. Результат запишите в строку и выведите получившуюся строку.

При решении этой задачи нельзя пользоваться циклами и инструкцией if.

Ввод Вывод
Hello world
world Hello
Сдать

08: Обращение фрагмента

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

Методом replace пользоваться нельзя.

Ввод Вывод
In the hole in the ground there lived a hobbit
In th a devil ereht dnuorg eht ni eloh ehobbit
Сдать
Списки;
  split и join;
  генераторы списков;
  срезы;
  основные операции со списками;
  сортировка списка;
  вложенные списки;
  под капотом списков;

Списки

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

Primes = [2, 3, 5, 7, 11, 13]
Rainbow = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']

В списке Primes — 6 элементов, а именно, Primes[0] == 2, Primes[1] == 3, Primes[2] == 5, Primes[3] == 7, Primes[4] == 11, Primes[5] == 13. Список Rainbow состоит из 7 элементов, каждый из которых является строкой.

Также как и символы строки, элементы списка можно индексировать отрицательными числами с конца, например, Primes[-1] == 13, Primes[-6] == 2.

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

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

A = []
for i in range(int(input()):
    A.append(int(input())

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

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

A = [1, 2, 3]
B = [4, 5]
C = A + B
D = B * 3

В результате список C будет равен [1, 2, 3, 4, 5], а список D будет равен [4, 5, 4, 5, 4, 5]. Это позволяет по-другому организовать процесс считывания списков: сначала считать размер списка и создать список из нужного числа элементов, затем организовать цикл по переменной i начиная с числа 0 и внутри цикла считывается i-й элемент списка:

A = [0] * int(input())
for i in range(len(A)):
    A[i] = int(input())

Вывести элементы списка A можно одной инструкцией print(A), при этом будут выведены квадратные скобки вокруг элементов списка и запятые между элементами списка. Такой вывод неудобен, чаще требуется просто вывести все элементы списка в одну строку или по одному элементу в строке. Приведем два примера, также отличающиеся организацией цикла:

for i in range(len(A)):
    print(A[i])

Здесь в цикле меняется индекс элемента i, затем выводится элемент списка с индексом i.

for elem in A:
    print(elem, end = ' ')

В этом примере элементы списка выводятся в одну строку, разделенные пробелом, при этом в цикле меняется не индекс элемента списка, а само значение переменной (например, в цикле for elem in ['red', 'green', 'blue'] переменная elem будет последовательно принимать значения 'red', 'green', 'blue'.

Копирование списков

Присваивание B = A не создает новый список, а всего лишь делает B ссылкой на уже существующий список. Для создания копии списка можно использовать срезы или метод copy:

B = A[:]
B = A.copy()
Однако если требуется полноценная копия списка, внутри которого могут быть другие списки (или иные изменяемые (mutable) объекты), то нужно использовать функцию deepcopy из модуля copy:
from copy import deepcopy
B = deepcopy(A)

Методы split и join

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

A = input().split()

Если при запуске этой программы ввести строку 1 2 3, то список A будет равен ['1', '2', '3']. Обратите внимание, что список будет состоять из строк, а не из чисел. Если хочется получить список именно из чисел, то можно затем элементы списка по одному преобразовать в числа:

for i in range(len(A)):
    A[i] = int(A[i])

Используя функции языка map и list то же самое можно сделать в одну строку:

A = list(map(int, input().split()))

Работает этот код следующим образом: input().split() возвращает список. Функция map последовательно применяет функцию int (переданную в параметре) ко всем элементам этого списка. Только применяет она её лениво — сам вызов произойдёт только в тот момент, когда очередной элемент из map'а потянут. А функция list в свою очередь «вытягивает» всё из итерируемого объекта и собирает список.

У метода split есть необязательный параметр, который определяет, какая строка будет использоваться в качестве разделителя между элементами списка. Например, метод split('.') вернет список, полученный разрезанием исходной строки по символам '.'.

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

A = ['red', 'green', 'blue']
print(' '.join(A))
print(''.join(A))
print('***'.join(A))

выведет строки 'red green blue', redgreenblue и red***green***blue.

Если же список состоит из чисел, то придется использовать еще и функцию map. То есть вывести элементы списка чисел, разделяя их пробелами, можно так:

print(' '.join(map(str, A)))

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

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

A = [0] * n

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

[ выражение for переменная in итерируемый_объект]

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

Вот несколько примеров использования генераторов.

Создать список, состоящий из n нулей можно и при помощи генератора:

A = [ 0 for i in range(n)]

Создать список, заполненный квадратами целых чисел можно так:

A = [ i ** 2 for i in range(n)]

Если нужно заполнить список квадратами чисел от 1 до n, то можно изменить параметры функции range на range(1, n + 1):

A = [ i ** 2 for i in range(1, n + 1)]

Вот так можно получить список, заполненный случайными числами от 1 до 9 (используя функцию randint из модуля random):

A = [ randint(1, 9) for i in range(n)]

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

A = [ input() for i in range(int(input()))]

Срезы

Со списками, так же как и со строками, можно делать срезы. А именно:

A[i:j]  срез из j-i элементов A[i], A[i+1], ..., A[j-1].

A[i:j:-1]  срез из i-j элементов A[i], A[i-1], ..., A[j+1] (то есть меняется порядок элементов).

A[i:j:k]  срез с шагом k: A[i], A[i+k], A[i+2*k],... . Если значение k<0, то элементы идут в противоположном порядке.

Каждое из чисел i или j может отсутствовать, что означает “начало строки” или “конец строки”

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

A = [1, 2, 3, 4, 5]
A[2:4] = [7, 8, 9]

Получится список, у которого вместо двух элементов среза A[2:4] вставлен новый список уже из трех элементов. Теперь список стал равен [1, 2, 3, 7, 8, 9, 5].

A = [1, 2, 3, 4, 5, 6,  7]
A[::-2] = [10, 20, 30, 40]

Получится список [40, 2, 30, 4, 20, 6, 10]. Здесь A[::-2] — это список из элементов A[-1], A[-3], A[-5], A[-7], которым присваиваются значения 10, 20, 30, 40 соответственно.

Если не непрерывному срезу (то есть срезу с шагом k, отличному от 1), присвоить новое значение, то количество элементов в старом и новом срезе обязательно должно совпадать, в противном случае произойдет ошибка ValueError.

Обратите внимание, A[i] — это элемент списка, а не срез!

Операции со списками

Со списками можно легко делать много разных операций.

x in A Проверить, содержится ли элемент в списке. Возвращает True или False
x not in A То же самое, что not(x in A)
min(A) Наименьший элемент списка
max(A) Наибольший элемент списка
A.index(x) Индекс первого вхождения элемента x в список, при его отсутствии генерирует исключение ValueError
A.count(x) Количество вхождений элемента x в список
A.append(x) Добавить в конец списка A элемент x.
A.insert(i, x) Вставить в список A элемент x на позицию с индексом i. Элементы списка A, которые до вставки имели индексы i и больше сдвигаются вправо.
A.extend(B) Добавить в конец списка A содержимое списка B.
A.pop() Удалить из списка последний элемент, возвращается значение удаленного элемента
A.pop(i) Удалить из списка элемент с индексом i, возвращается значение удаленного элемента. Все элементы, стоящие правее удаленного, сдвигаются влево.

Функция sorted и метод sort

Функция sorted получает в качестве аргумента список, и возвращает ссылку на новый список, содержащий те же элементы в порядке возрастания. Пример:

A = [1, 6, 2, 5, 3, 4]
B = sorted(A)

Метод sort применяется к списку и переставляет элементы списка в порядке возрастания. Пример:

A.sort()

Например, вот решение задачи про сортировку чисел, перечисленных через пробел, в порядке неубывания в одну строку:

print(" ".join(list(map(str,sorted(list(map(int,input().split())))))))

У функции sorted и метода sort есть параметр reverse. Если этот параметр установить в True, то сортировка будет выполняться в порядке невозрастания. Пример:

B = sorted(A, reverse=True)
A.sort(reverse=True)

Хитрые сортировки в Python

Иногда возникает необходимость сортировать не только числовые последовательности, но и более сложные объекты, а также сохранять при сортировке знание о позиции элемента в исходной последовательности. Всё это в языки Python делается довольно просто и элегантно.

Начнём с того, что Python умеет сравнивать списки:

>>> [1, 2, 3, 4] > [2, 3, 4]
False
>>> [1, 2, 3, 4] > [1, 2, 3, 4]
False
>>> [1, 3, 2, 4] > [1, 2, 3, 4]
True
>>> [1, 2, 3, 4, 5] > [1, 2, 3, 4]
True
>>> [10] > [1, 2, 3, 4]
True
>>> [2, -100] > [1, 2, 3, 4]
True
Сравнивает он их поэлементно (или лексикографически), то есть сначала сравнивает первые элементы. Владелец большего первого элемента и сам больше. Если же первые элементы совпадают, то мы сравниваем вторые элементы. Так мы движемся до тех пор, пока i-й элемент одного списка не станет больше i-го элемента второго списка, тогда первый победит. Если какой-то список закончился раньше, то он меньше. А иначе списки совпадают.

Итак, если списки можно сравнивать, то их можно и сортировать.

>>> A = [[1, 2, 3, 4], [2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4, 5], [10], [2, -100], [1, 3, 2, 4], [1, 2, 3, 4]]
>>> sorted(A)
[[1, 2, 3, 4],
 [1, 2, 3, 4],
 [1, 2, 3, 4],
 [1, 2, 3, 4, 5],
 [1, 3, 2, 4],
 [2, -100],
 [2, 3, 4],
 [10]]

Конечно же, для того, чтобы такой «сложный» список можно было отсортировать, необходимо, чтобы его элементы можно было сравнить.

>>> sorted([[1, 2], [1, 'a']])
Traceback (most recent call last):
  File "", line 301, in runcode
  File "", line 1, in 
TypeError: unorderable types: str() < int()

Теперь представим себе, что нам нужно отсторировать список, но при этом нужно сохнанить знание о том, где стоял элемент до сортировки. Для этого можно применить такой трюк: заменим число x на кортеж (x, i), где i — номер элемента в списке. И после этого отсортируем.

my_list = [39, 12, 21, 77, 21, 51, 48, 21, 42, 76]
for i in range(len(my_list)):
    my_list[i] = (my_list[i], i)
my_list.sort()
print(my_list)

[(12, 1), (21, 2), (21, 4), (21, 7), (39, 0), (42, 8), (48, 6), (51, 5), (76, 9), (77, 3)]

Благодаря такой операции мы знаем, что числа 21 стояли на 2, 4 и 7-й позиции в исходном списке, а наибольшее число 77 было на позиции 3. Всё это можно сделать короче с помощью генератора списков:

my_list = [39, 12, 21, 77, 21, 51, 48, 21, 42, 76]
hacked_list = sorted([(my_list[i], i) for i in range(len(my_list))])
или даже ещё лучше при помощи функции enumerate
hacked_list = sorted([(val, i) for i, val in enumerate(my_list)])

Параметр key у метода sort и функции sorted

Допустим, у нас есть список названий деталей и их стоимостей. Нам нужно отсортировать его сначала по названию деталей, а одинаковые детали по убыванию цены. Самая коротка реализация даст не совсем тот результат:
shop = [['каретка', 1200], ['шатун', 1000], ['седло', 300], ['педаль', 100], ['седло', 1500], ['рама', 12000], 
        ['обод', 2000], ['шатун', 200], ['седло', 2700]]
shop.sort()
for i in range(len(shop)):
    print('{:<10} цена: {:>5}р.'.format(shop[i][0], shop[i][1]))
каретка    цена:  1200р.
обод       цена:  2000р.
педаль     цена:   100р.
рама       цена: 12000р.
седло      цена:   300р.
седло      цена:  1500р.
седло      цена:  2700р.
шатун      цена:   200р.
шатун      цена:  1000р.
Как и следовало ожидать, одинаковые детали отсортированы по возрастанию цены (не обращайте пока внимания на format, или почитайте документацию).

Это можно исправить так:

def prepare_item(item):
    return (item[0], -item[1])

shop = [['каретка', 1200], ['шатун', 1000], ['седло', 300], ['педаль', 100], ['седло', 1500], ['рама', 12000], 
        ['обод', 2000], ['шатун', 200], ['седло', 2700]]
shop.sort(key=prepare_item)
for i in range(len(shop)):
    print('{:<10} цена: {:>5}р.'.format(shop[i][0], shop[i][1]))
каретка    цена:  1200р.
обод       цена:  2000р.
педаль     цена:   100р.
рама       цена: 12000р.
седло      цена:  2700р.
седло      цена:  1500р.
седло      цена:   300р.
шатун      цена:  1000р.
шатун      цена:   200р.
Что здесь произошло? Перед тем, как сравнивать два элемента списка к ним применялась функция prepare_item, которая меняла знак у стоимости. В результате при одинаковов первом значении сортировка по второму происходила в обратном порядке.

Ещё можно писать так (но делать это нужно аккуратно и с пониманием):

shop.sort(key=lambda x: (x[0], -x[1]))

Используя подходящую функцию prepare_item мы можем делать и более сложные сортировки:
Отсортировать только по второму элементу

def prepare_item(item):
    return item[1]
Отсортировать по модулю третьего элемента:
def prepare_item(item):
    return abs(item[2])
Отсортировать сначала по третьему элементу списка, затем по модулю первого:
def prepare_item(item):
    return (item[2], abs(item[0]))
Вернёмся к примеру, где нужно было сохранить знание о начальном положении элементов. Теперь мы можем решить эту задачу так:
my_list = [39, 12, 21, 77, 21, 51, 48, 21, 42, 76]
hacked_list = sorted(enumerate(my_list), key=lambda x:x[1])
# Или так
from operator import itemgetter
hacked_list = sorted(enumerate(my_list), key=itemgetter(1))

Ещё один способ хитрых сортировок

Допустим данные нужно отсортировать сначала по столбцу А по возрастанию, затем по столбцу Б по убыванию, и наконец по столбцу В снова по возрастанию. Если данные в столбце Б числовые, то при помощи подходящей функции в key можно поменять знак у элементов Б, что приведёт к необходимому результату. А если все данные текстовые? Тут есть такая возможность. Дело в том, что сортировка sort в Python устойчивая, то есть она не меняет порядок «одинаковых» элементов. Поэтому можно просто отсортировать три раза по разным ключам:

data.sort(key = lambda x: x['В'])
data.sort(key = lambda x: x['Б'], reverse=True)
data.sort(key = lambda x: x['А'])

Обработка и вывод вложенных списков

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

A = [ [1, 2, 3], [4, 5, 6] ]

Здесь первая строка списка A[0] является списком из чисел [1, 2, 3]. То есть

A[0][0] == 1,   A[0][1] == 2,   A[0][2] == 3

A[1][0] == 4,   A[1][1] == 5,   A[1][2] == 6

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

for i in range(len(A)):
    for j in range(len(A[i]):
        print(A[i][j], end = ' ')
    print()

То же самое, но циклы не по индексу, а по значениям списка:

for row in A:
    for elem in row:
        print(elem, end = ' ')
    print()

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

for row in A:
    print(' '.join(list(map(str, row))))

Используем два вложенных цикла для подсчета суммы всех чисел в списке:

S = 0
for i in range(len(A)):
    for j in range(len(A[i])):
        S += A[i][j]

Или то же самое с циклом не по индексу, а по значениям строк:

S = 0
for row in A:
    for elem in row:
        S += elem

Создание многомерного списка

Пусть даны два числа: количество строк n и количество столбцов m. Необходимо создать список размером n×m, заполненный нулями.

Очевидное решение оказывается неверным:

A = [[0] * m ] * n

В этом легко убедиться, если присвоить элементу A[0][0] значение 1, а потом вывести значение другого элемента A[1][0] — оно тоже будет равно 1! Дело в том, что [0] * m возвращает ccылку на список из m нулей. Но последующее повторение этого элемента создает список из n элементов, которые являются ссылкой на один и тот же список (точно так же, как выполнение операции B = A для списков не создает новый список), поэтому все строки результирующего списка на самом деле являются одной и той же строкой.

Таким образом, двумерный список нельзя создавать при помощи операции повторения одной строки. Что же делать?

Первый способ: сначала создадим список из n элементов (для начала просто из n нулей). Затем сделаем каждый элемент списка ссылкой на другой одномерный список из m элементов:

A = [0] * n
for i in range(n):
    A[i] = [0] * m

Другой (но похожий) способ: создать пустой список, потом n раз добавить в него новый элемент, являющийся списком-строкой:

A = []
for i in range(n):
    A.append([0] * m)

Но еще проще воспользоваться генератором: создать список из n элементов, каждый из которых будет списком, состоящих из m нулей:

A = [[0] * m for i in range(n)]

В этом случае каждый элемент создается независимо от остальных (заново конструируется список [0] * m для заполнения очередного элемента списка), а не копируются ссылки на один и тот же список.

Ввод списка

Пусть программа получает на вход двумерный массив, в виде n строк, каждая из которых содержит m чисел, разделенных пробелами. Как их считать? Например, так:

A = []
for i in range(n):
    A.append(list(map(int, input().split())))

Или, без использования сложных вложенных вызовов функций:

A = []
for i in range(n):
    row = input().split()
    for i in range(len(row)):
        row[i] = int(row[i])
    A.append(row)

Можно сделать то же самое и при помощи генератора:

A = [list(map(int, input().split())) for i in range(n)]

Сложный пример обработки массива

Пусть дан квадратный массив из n строк и n столбцов. Необходимо элементам, находящимся на главной диагонали, проходящей из левого верхнего угла в правый нижний (то есть тем элементам A[i][j], для которых i == j) присвоить значение 1, элементам, находящимся выше главной диагонали – значение 0, элементам, находящимся ниже главной диагонали – значение 2. То есть получить такой массив (пример для n==4):

     1 0 0 0
     2 1 0 0
     2 2 1 0
     2 2 2 1

Рассмотрим несколько способов решения этой задачи. Элементы, которые лежат выше главной диагонали – это элементы A[i][j], для которых i < j, а для элементов ниже главной диагонали i > j. Таким образом, мы можем сравнивать значения i и j и по ним определять значение A[i][j]. Получаем следующий алгоритм:

for i in range(n):
    for j in range(n):
        if i < j:
            A[i][j] = 0
        elif i > j:
            A[i][j] = 2
        else:
            A[i][j] = 1

Данный алгоритм плох, поскольку выполняет одну или две инструкции if для обработки каждого элемента. Если мы усложним алгоритм, то мы сможем обойтись вообще без условных инструкций.

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

for i in range(n):
    A[i][i] = 1

Затем заполним значением 0 все элементы выше главной диагонали, для чего нам понадобится в каждой из строк с номером i присвоить значение элементам A[i][j] для j=i+1, ..., n-1. Здесь нам понадобятся вложенные циклы:

for i in range(n):
    for j in range(i + 1, n):
        A[i][j] = 0

Аналогично присваиваем значение 2 элементам A[i][j] для j=0, ..., i-1:

for i in range(n):
    for j in range(0, i):
        A[i][j] = 2

Можно также внешние циклы объединить в один и получить еще одно, более компактное решение:

for i in range(n):
    for j in range(0, i):
        A[i][j] = 2
    A[i][i] = 1
    for j in range(i + 1, n):
        A[i][j] = 0

А вот такое решение использует операцию повторения списков для построения очередной строки списка. i-я строка списка состоит из i чисел 2, затем идет одно число 1, затем идет n-i-1 число 0:

for i in range(n):
    A[i] = [2] * i + [1] + [0] * (n - i - 1)

А можно заменить цикл на генератор:

A = [ [2] * i + [1] + [0] * (n - i - 1) for i in range(n)]

Под капотом списка

Устройство списка достаточно просто — это массив ссылок на объекты-элементы списка. При этом при добавлении элементов выделенная под ссылки память расширяется в такой последовательности: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, … и дальше по формуле l -> 1.125(l+1) + 6

Для совсем любопытных есть достаточно подробная статья, и, конечно же, исходные коды: listobject.h и listobject.c.

08. Списки

09: Наименьший нечетный

Выведите значение наименьшего нечетного элемента списка, а если в списке нет нечетных элементов - выведите число 0.

Ввод Вывод
0 1 2 3 4
1
2 4 6 8 10
0
Сдать

22: Медиана

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

При решении этой задачи нельзя модифицировать данный список (в том числе и сортировать его), использовать вспомогательные списки.

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

Программа должна вывести единственное число — значение медианного элемента в списке.

Ввод Вывод
7
6 1 9 2 3 4 8
4
Сдать

11: Личные дела

Однажды, неловкая секретарша перепутала личные дела учащихся. Теперь их снова необходимо упорядочить сначала по классам, а внутри класса по фамилиям.

В первой строке входных данных записано число \(N\) (\(1 \le N \le 1000\)) – количество личных дел. Далее для каждого из N учащихся следующие данные (каждое в своей строке): фамилия и имя, класс, дата рождения. Фамилия и имя – строки не более чем из 20 символов, класс – строка состоящая из числа (от 1 до 11) и латинской буквы (от "A" до "Z" ), дата рождения – дата в формате "ДД.ММ.ГГ" . Гарантируется, что внутри одного класса нет однофамильцев.

В выходной файл требуется вывести N строк, в каждой из которых записаны данные по одному учащемуся. Строки должны быть упорядочены сначала по классам, а затем по фамилиям.

Ввод Вывод
3
Sidorov
Sidor
9A
20.07.93
Petrov
Petr
9B
23.10.92
Ivanov
Ivan
9A
10.04.93
9A Ivanov Ivan 10.04.93
9A Sidorov Sidor 20.07.93
9B Petrov Petr 23.10.92
Сдать

04: Диагонали параллельные главной

Дано число n. Создайте массив размером n×n и заполните его по следующему правилу. На главной диагонали должны быть записаны числа 0. На двух диагоналях, прилегающих к главной, числа 1. На следующих двух диагоналях числа 2, и т.д.

Ввод Вывод
5
0 1 2 3 4
1 0 1 2 3
2 1 0 1 2
3 2 1 0 1
4 3 2 1 0
Сдать
Множества;
  Словари (ассоциативные массивы);
  Под капотом словарей в Python <= 3.5
  Под капотом словарей в Python >= 3.6

Множества

Множество в языке Питон — это структура данных, эквивалентная множствам в математике. Множество может состоять из различных элементов, порядок элементов в множестве неопределен. В множество можно добавлять и удалять элементы, можно перебирать элементы множества, можно выполнять операции над множествами (объединение, пересечение, разность). Можно проверять принадлежность элементу множества.

В отличии от массивов, где элементы хранятся в виде последовательного списка, в множествах порядок хранения элементов неопределен (более того, элементы множества храняться не подряд, как в списке, а при помощи хитрых алгоритмов). Это позволяет выполнять операции типа “проверить принадлежность элемента множеству” быстрее, чем просто перебирая все элементы множества.

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

Задание множеств

Множество задается перечислением всех его элементов в фигурных скобках. Например:

A = {1, 2, 3}

Исключением явлеется пустое множество, которое можно создать при помощи функции set(). Если функции set передать в качестве параметра список, строку или кортеж, то она вернет множество, составленное из элементов списка, строки, кортежа. Например:

A = set('qwerty')
print(A)

выведет {'e', 'q', 'r', 't', 'w', 'y'}.

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

A = {1, 2, 3}
B = {3, 2, 3, 1}
print(A == B)

выведет True, так как A и B — равные множества.

Каждый элемент может входить в множество только один раз. set('Hello') вернет множество из четырех элементов: {'H', 'e', 'l', 'o'}.

Работа с элементами множеств

Узнать число элементов в множестве можно при помощи функции len.

Перебрать все элементы множества (в неопределенном порядке!) можно при помощи цикла for:

C = {1, 2, 3, 4, 5}
for elem in C:
    print(elem)

Проверить, принадлежит ли элемент множеству можно при помощи операции in, возвращающей значение типа bool:

i in A

Аналогично есть противоположная операция not in.

Для добавления элемента в множество есть метод add:

A.add(x)

Для удаления элемента x из множества есть два метода: discard и remove. Их поведение различается только в случае, когда удаляемый элемент отсутствует в множестве. В этом случае метод discard не делает ничего, а метод remove генерирует исключение KeyError.

Наконец, метод pop удаляет из множества один случайный элемент и возвращает его значение. Если же множество пусто, то генерируется исключение KeyError.

Из множества можно сделать список при помощи функции list.

Перебор элементов множества

При помощи цикла for можно перебрать все элементы множества:

Primes = {2, 3, 5, 7, 11}
for num im Primes:
    print(num)

Операции с множествами

С множествами в питоне можно выполнять обычные для математики операции над множествами.

A | B
A.union(B)
Возвращает множество, являющееся объединением множеств A и B.
A |= B
A.update(B)
Добавляет в множество A все элементы из множества B.
A & B
A.intersection(B)
Возвращает множество, являющееся пересечением множеств A и B.
A &= B
A.intersection_update(B)
Оставляет в множестве A только те элементы, которые есть в множестве B.
A - B
A.difference(B)
Возвращает разность множеств A и B (элементы, входящие в A, но не входящие в B).
A -= B
A.difference_update(B)
Удаляет из множества A все элементы, входящие в B.
A ^ B
A.symmetric_difference(B)
Возвращает симметрическую разность множеств A и B (элементы, входящие в A или в B, но не в оба из них одновременно).
A ^= B
A.symmetric_difference_update(B)
Записывает в A симметрическую разность множеств A и B.
A <= B
A.issubset(B)
Возвращает true, если A является подмножеством B.
A >= B
A.issuperset(B)
Возвращает true, если B является подмножеством A.
A < B
Эквивалентно A <= B and A != B
A > B
Эквивалентно A >= B and A != B

Генераторы множеств

Подобно генераторам списков существуют и генераторы множеств. Синтаксис у них точно такой же, с точностью до скобок:
s = {i for i in range(100)}
s = {i for i in range(100) if i % 2 == i % 3 == i % 5 == i % 7 == 0}
s = {item[0] for item in some_Nx2_list}

Словари (ассоциативные массивы)

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

Структура данных, позволяющая идентифицировать ее элементы не по числовому индексу, а по произвольному, называется словарем или ассоциативным массивом. Соответствующая структура данных в языке Питон называется dict.

Рассмотрим простой пример использования словаря. Заведем словарь Capitals, где индексом является название страны, а значением — название столицы этой страны. Это позволит легко определять по строке с названием страны ее столицу.

# Создадим пустой словать Capitals
Capitals = dict()

# Заполним его несколькими значениями
Capitals['Russia'] = 'Moscow'
Capitals['Ukraine'] = 'Kiev'
Capitals['USA'] = 'Washington'

# Считаем название страны
print('В какой стране вы живете?')
country = input()

# Проверим, есть ли такая страна в словаре Capitals
if country in Capitals:
    # Если есть - выведем ее столицу
    print('Столица вашей страны', Capitals[country])
else:
    # Запросим название столицы и добавив его в словарь
    print('Как называется столица вашей страны?')
    city = input()
    Capitals[country] = city

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

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

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

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

В языке Питон как ключом может быть произвольный неизменяемый тип данных: целые и действительные числа, строки, кортежи. Ключом в словаре не может быть множество, но может быть элемент типа frozenset: специальный тип данных, являющийся аналогом типа set, который нельзя изменять после создания. Значением элемента словаря может быть любой тип данных, в том числе и изменяемый.

Когда нужно использовать словари

Словари нужно использовать в следующих случаях:

  • Подсчет числа каких-то объектов. В этом случае нужно завести словарь, в котором ключами являются объекты, а значениями — их количество.
  • Хранение каких-либо данных, связанных с объектом. Ключи — объекты, значения — связанные с ними данные. Например, если нужно по названию месяца определить его порядковый номер, то это можно сделать при помощи словаря Num['January'] = 1; Num['February'] = 2; ....
  • Установка соответствия между объектами (например, “родитель—потомок”). Ключ — объект, значение — соответствующий ему объект.
  • Если нужен обычный массив, но при этом масимальное значение индекса элемента очень велико, но при этом будут использоваться не все возможные индексы (так называемый “разреженный массив”), то можно использовать ассоциативный массив для экономии памяти.

Создание словаря

Пустой словарь можно создать при помощи функции dict() или пустой пары фигурных скобок {} (вот почему фигурные скобки нельзя использовать для создания пустого множества). Для создания словаря с некоторым набором начальных значений можно использовать следующие конструкции:

Capitals = {'Russia': 'Moscow', 'Ukraine': 'Kiev', 'USA': 'Washington'}
Capitals = dict(Russia = 'Moscow', Ukraine = 'Kiev', USA = 'Washington')
Capitals = dict([("Russia", "Moscow"), ("Ukraine", "Kiev"), ("USA", "Washington")])
Capitals = dict(zip(["Russia", "Ukraine", "USA"], ["Moscow", "Kiev", "Washington"]))

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

Работа с элементами словаря

Основная операция: получение значения элемента по ключу, записывается так же, как и для списков: A[key]. Если элемента с заданным ключом не существует в словаре, то возникает исключение KeyError.

Другой способ определения значения по ключу — метод get: A.get(key). Если элемента с ключом get нет в словаре, то возвращается значение None. В форме записи с двумя аргументами A.get(key, val) метод возвращает значение val, если элемент с ключом key отсутствует в словаре.

Проверить принадлежность элемента словарю можно операциями in и not in, как и для множеств.

Для добавления нового элемента в словарь нужно просто присвоить ему какое-то значение: A[key] = value.

Для удаления элемента из словаря можно использовать операцию del A[key] (операция возбуждает исключение KeyError, если такого ключа в словаре нет. Вот два безопасных способа удаления элемента из словаря. Способ с предварительной проверкой наличия элемента:

if key in A:
    del A[key]

Способ с перехватыванием и обработкой исключения:

try:
    del A[key]
except KeyError:
    pass

Еще один способ удалить элемент из словаря: использование метода pop: A.pop(key). Этот метод возвращает значение удаляемого элемента, если элемент с данным ключом отсутствует в словаре, то возбуждается исключение. Если методу pop передать второй параметр, то если элемент в словаре отсутствует, то метод pop возвратит значение этого параметра. Это позволяет проще всего организовать безопасное удаление элемента из словаря: A.pop(key, None).

Перебор элементов словаря

Можно легко организовать перебор ключей всех элементов в словаре:

for key in A:
    print(key, A[key])

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

Соответственно, быстро проверить, если ли значение val среди всех значений элементов словаря A можно так: val in A.values(), а организовать цикл так, чтобы в переменной key был ключ элемента, а в переменной val было его значение можно так:

for key, val in A.items():
    print(key, val)

Генераторы словарей

Подобно генераторам списков и множеств существуют и генераторы словарей. Синтаксис у них такой:
d = {x: x*(x-1)//2 for x in range(100)}
d = {x: x**3 for x in some_list if x % 2 == 0}
d = {x[0]: x[1] for x in some_Nx2_list}

Счётчики, pythonic-way

Предположим, у нас есть простая задача: посчитать, сколько раз каждый символ встречается в тексте. Она может быть решена таким образом:
counter = {}
for letter in text:
    if letter in counter:
        counter[letter] += 1
    else:
        counter[letter] = 1
Но более удобный и короткий способ использует метод словаря get(key[, default]), который возвращает значение default, если ключа в словаре нет:
counter = {}
for letter in text:
    counter[letter] = counter.get(letter, 0) + 1
Ну, и конечно же, простые счётчики в питоне уже есть:
from collections import Counter
counter = Counter(text)

Под капотом словаря

Там красивая тема, но много букв. Если коротко, то хеш-таблица троек (хеш, ключ, значение). При коллизиях ищем следующую свободную ячейку.

Для совсем любопытных есть достаточно подробная статья. А вот предложение Raymond Hettinger, которое будет применено в Python 3.6.

09. Множества и словари

06: Количество слов в тексте

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

Определите, сколько различных слов содержится в этом тексте.

Ввод Вывод
She sells sea shells on the sea shore;
The shells that she sells are sea shells I'm sure.
So if she sells sea shells on the sea shore,
I'm sure that the shells are sea shore shells.
19

Примечание. И даже эту задачу на Питоне можно решить в одну строчку. Сдать

16: Частотный анализ

Дан текст. Выведите все слова, встречающиеся в тексте, по одному на каждую строку. Слова должны быть отсортированы по убыванию их количества появления в тексте, а при одинаковой частоте появления — в лексикографическом порядке.

Ввод Вывод
hi
hi
what is your name
my name is bond
james bond
my name is damme
van damme
claude van damme
jean claude van damme
damme
is
name
van
bond
claude
hi
my
james
jean
what
your
Сдать
Работа с файлами;
  менеджеры контекста;
  Практические применения

Открытие файла

Для каждого файла, с которым необходимо производить операции ввода-вывода, нужно связать специальный объект - поток. Открытие файла осуществляется функцией open, которой нужно передать два параметра. Первый параметр (можно также использовать именованный параметр file) имеет значение типа str, в котором записано имя открываемого файла. Второй параметр (можно также использовать именованный параметр mode) —это значение типа str, которое равно "r", если файл открывается для чтения данных (read), "w", если на запись (write), при этом содержимое файла очищается, и "a" — для добавления данных в конец файла (append). Если второй параметр не задан, то считается, что файл открывается в режиме чтения.

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

input = open('input.txt', 'r')
output = open('output.txt', 'w')

Чтение данных из файла

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

Метод readline() считывает одну строку из файла (до символа конца строки '\n', возвращается считанная строка вместе с символом '\n'. Если считывание не было успешно (достигнут конец файла), то возвращается пустая строка. Для удаления символа '\n' из конца файла удобно использовать метод строки rstrip(). Например: s = s.rstrip().

Метод readlines() считывает все строки из файла и возвращает список из всех считанных строк (одна строка — один элемент списка). При этом символы '\n' остаются в концах строк.

Метод read() считывает все содержимое из файла и возвращает строку, которая может содержать символы '\n'. Если методу read передать целочисленный параметр, то будет считано не более заданного количества символов. Например, считывать файл побайтово можно при помощи метода read(1).

Вывод данных в файл

Данные выводятся в файл при помощи метода write, которому в качестве параметра передается одна строка. Этот метод не выводит символ конца строки '\n' (как это делает функция print при стандартном выводе), поэтому для перехода на новую строку в файле необходимо явно вывести символ '\n'.

Также можно выводить данные в файл при помощи функции print, если передать ей еще один именованный параметр file, равный ссылке на открытый файл. Например:

output = open('output.txt', 'w')
print(a, b, c, file=output)

Закрытие файла

После окончания работы с файлом необходимо закрыть его при помощи метода close().

Пример

Следующая программа считывает все содержимое файла input.txt, записывает его в переменную s, а затем выводит ее в файл output.txt.

input = open('input.txt', 'r')
output = open('output.txt', 'w')
s = input.read()
output.write(s)
input.close()
output.close()

Так можно читать файл построчно:

input = open('input.txt', 'r')
output = open('output.txt', 'w')
line = input.readline()
while line:
    output.write(line)
    line = input.readline()
input.close()
output.close()

Существует и другой способ обработать файл построчно, более удобный:

input = open('input.txt', 'r')
output = open('output.txt', 'w')
for line in input:
    output.write(line)
input.close()
output.close()

А вот аналогичная программа, но читающая данные уже посимвольно:

input = open('input.txt', 'r')
output = open('output.txt', 'w')
c = input.read(1)
while c:
    output.write(c)
    c = input.read(1)
input.close()
output.close()

Работа с файлами: the right way

В тех случаях, когда работа с файлом происходит локально, гораздо лучше использовать так называемые менеджеры контекста (context manager):
with open('output.txt', 'w', encoding='utf-8') as f:
    d = int(input())
    f.write('1 / {} = {}\n'.format(d, 1 / d))
Такая конструкция гарантирует, что файл будет закрыт, даже если исполнение программы будет прервано из-за ошибки (например, из-за деления на 0 в примере выше).

Синтаксис и логика этой конструкции следующие:

"with" expression ["as" target] ("," expression ["as" target])* ":"
    suite
  1. Выполняется выражение в конструкции with ... as.
  2. Загружается специальный метод __exit__ для дальнейшего использования.
  3. Выполняется метод __enter__. Если конструкция with включает в себя слово as, то возвращаемое методом __enter__ значение записывается в переменную.
  4. Выполняется suite.
  5. Вызывается метод __exit__, причём неважно, выполнилось ли suite или произошло исключение. В этот метод передаются параметры исключения, если оно произошло, или во всех аргументах значение None, если исключения не было.

Hints

① Если обработка файла простая, то можно не растягивать её на много строчек:

data = open('input.txt').read().split()[-1]

② Если в файле есть русские буквы (и вообще любой символ с кодом, большим 127), то необходимо при открытии указывать кодировку:

data = open('input.txt', 'r', encoding='utf-8')
10. Реальная практика: спасаем фотографии

Практические применения

С помощью короткой программы на питоне можно решить довольно широкий спектр задач которые до появления компьютеров просто не существовали. Это работа с файлами и папками операционной системы, работа с изображениями, mp3, pdf, электронной почтой, получение информации из сети интернет и много другое. Почти для всех задач, которые возникают, в сети можно найти подходящий рецепт на языке питон. На данный момент самым большим источником таких рецептов является сайт http://stackoverflow.com. Увы, он на английском, но у него есть и русская версия (в 1000 раз более бедная).

Если вы нашли рецепт, то следует убедиться, что рецепт на Python3, а не на Python2. Их легко отличить по print без скобок (такая конструкция в Python3 не работает). Зачастую print — это единственное, что нужно поправить. Но это, увы, не всегда так.

Восстановление фотографий

В новой серии задач мы отрабатываем следующий сценарий: из-за резкого скачка электричества ваш компьютер вырубился во время записи файла на диск. В результате повреждена файловая система, поэтому уже невозможно найти свои файлы в "обычных" местах. На диске остались фотографии, которые вам дороги. С помощью специальной программы, которая сканирует весь диск и ищет последовательности байтов, похожие на фотографии, вы добыли всё, что можно. Так как файловая система умерла, то имён файлов и прочих атрибутов нет, есть только гора файлов с бессмысленными именами. Так как вы много работали с фотографиями, то там много повторов фотографий. А вот и архив с этими фотографиями.

План по наведению порядка:

  • Получить список файлов;
  • Посчитать md5-хэш каждого файла. У одинаковых фотографий будет одинаковый хеш, а у разных — разный (для параноиков можно считать sha-512);
  • Отобрать по одному файлу для каждого хеша, а остальные — удалить;
  • В каждом файле в EXIF найти информацию о дате и времени съёмки;
  • Переименовать каждый файл в соответствии с датой и временем съёмки.
Для того, чтобы каждый раз не клеить полный путь к имени файла, удобно перейти прямо в директорию к фотографиям при помощи
os.chdir('C:\\some\\path\\resc_photos')
Архив с фотографиями

06. md5-хеши файлов

Для каждого файла из архива посчитайте его md5-хеш. Выведите все пары (ИМЯ_ФАЙЛА, ХЕШ). Порядок файлов неважен.

Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
import hashlib
...
####################
0spuuzog.jpg 2b04cbfba96acc2b20fb41a65c00116d
1e1ztfom.jpg a46e61e2cf9725d569ad755dbdd189c0
82t56870.jpg 2b04cbfba96acc2b20fb41a65c00116d

07. Отбор уникальных файлов

Отберите все уникальные фотографии и выведите их имена.

Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
import hashlib
...
####################
0spuuzog.jpg
1e1ztfom.jpg

08. Удаление лишнего

Получите список всех файлов, которые не лежат в списке из задачи 07. Удалите все эти повторяющиеся файлы.

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит список удаляемых файлов. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
import hashlib
...
####################
82t56870.jpg

09. Дата съёмки

Известно, что фотографии сняты в августе 2015 года. В начале каждой фотографии находится EXIF — метаданные, например, дата-время съёмки, модель камеры и т.д. Теоретически их можно расшифровать, но сейчас в этом нет необходимости. Нужную дату можно найти и так. Откройте файл при помощи notepad++, и вы её увидите. После этого будет не сложно добыть дату для каждой фотографии автоматически. Здесь следует учитывать, что данные в файле не текстовые, а бинарные, поэтому открывать файл стоит так:

open(filename, 'rb')
Здесь r отвечает за чтение, а b — за то, что чтение бинарных данных. Бинарные строки очень похожи на обычные, только в коде перед ними стоит буква b. С ними можно делать почти все операции, что и с обычными текстовыми. Те символы бинарной строки, код которых не лежит в диапазоне acsii (от 32 до 127), выводятся как их код в 16-ричной системе счисления. Для получения из обычной текстовой строки бинарной используется метод encode:
>>> print('Привет, world!'.encode('utf-8'))
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, world!'
Для обратного преобразования — метод decode:
>>> print(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, world!'.decode('utf-8'))
Привет, world!

Выведите для каждого из оставшихся файлов дату и время съёмки в том виде, в котором они добываются из файла. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
...
####################
0spuuzog.jpg b'2015:08:23 14:26:12'
1e1ztfom.jpg b'2015:08:19 16:05:07'

10. Переименование

Теперь ничего не стоит переименовать файлы в имена вида "yyyy-mm-dd_hh-mm-ss.jpg". Ну, почти ничего, кроме того, что некоторые фотографии сняты в одну и ту же секунду. Если вдруг так окажется, то добавьте в конец имени файла перед ".jpg" один, два или три символа "_" (например, "2015-08-23_14-26-12_.jpg").

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит имена новых файлов. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
...
####################
2015-08-23_14-26-12.jpg
2015-08-19_16-05-07.jpg

11. Чтение EXIF

Решение в предыдущих двух задачах не сработает, если у нас будет архив фотографий за 10 лет. Мы не сможем уверенно искать дату съёмке по строке "2015.08", ведь это может оказаться датой изменения, а не съёмки. Поэтому более правильное решение — расшифровать EXIF. Стандарт открыт, поэтому можно написать свою программу по его расшифровке. Однако это достаточно трудоёмко. Один из плюсов питона в том, что большинство подобных задач уже решены, и всё, что требуется, это найти подходящий пакет. В данном случае один из подходящих нам пакетов — это exifread.

Установка пакета exifread
Установка пакетов для Python в операционной системе Windows, увы, зачастую вызывает затруднения. Чтобы его установить, нужно выполнить в командной строке pip install exifread (win+R, затем cmd). Если у вас старая версия Python, не установлен pip, или при установке вы не поставили галочку "Добавить Python в PATH", то это не сработает. Тогда нужно скачать архив с пакетом, распаковать его, зайти внутрь папки exif-py-2.1.2, на пустом месте сделать клик правой кнопкой мыши вместе с нажатой клавишей Shift, выбрать "Откыть окно команд", и выполнить python setup.py install. Если и это не поможет, то нужно найти, где лежит исполняемый файл python.exe, и указать полный путь: "C:\Python34\python.exe" "Y:\exif-py-2.1.2\setup.py" install.

Если и это не помогло, то вы — неудачник установите свежий Python 3.5, при установке не забудьте поставить галочку "Добавить в Path", и попробуйте всё сначала.

Напишите программу, которая получает дату и время съёмки из EXIF для каждой фотографии при помощи модуля exifread. В тестовую систему необходимо сдать код программы, которая делает это, а также выводит полученные временные отметки. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
import exifread
...
####################
2015:08:23 14:26:12
2015:08:19 16:05:07

12. Выбор объектива

При выборе объектива для фотоаппарата возникает вопрос: какие диапазон фокусных расстояний (углов поля зрения, см. картинку в википедии) вам необходим. Что выбирать, длиннофокусный объектив, широкоугольный объектив и т.д.? Довольно надёжный способ — посмотреть, какими фокусными расстояними вы пользуетесь.

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

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит количества кадров. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

Пример ответа:
import os
import exifread
...
####################
24: 1
70: 1

13. Разложить по полочкам

После того, как мы «восстановили» имена файлов с фотографиями, появилась необходимость разложить их в отдельные папки по датам.

Разложите фотографии по папка вида yyyy/mm/dd, где yyyy, mm и dd — соответствующие части даты, а также переименуйте фотографии: оставьте в имени только время.

Пример путей:
2015/08/23/14-26-12.jpg
2015/08/19/16-05-07.jpg