Лаб. 01: Выражения и управляющие инструкции.

Теория

Деление

Сравни поведения разных операций, связанных с делением:

Деление /
>>> 1 / 5
0.2
>>> 25 / 4
6.25
>>> 4 / 2
2.0
>>> 5 / 0
ZeroDivisionError
Целочисленное деление //
>>> 1 // 5
0
>>> 25 // 4
6
>>> 4 // 2
2
>>> 5 // 0
ZeroDivisionError
Модуль % (остаток)
>>> 1 % 5
1
>>> 25 % 4
1
>>> 4 % 2
0
>>> 5 % 0
ZeroDivisionError

Оператор % удобно использовать для проверки того, что m — множитель n.

n % m == 0

Например, проверить чётность n можно так:

n % 2 == 0

Функции

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

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

>>> 2 + 3 * 1
5
>>> 2 + 3 * 2
8
>>> 2 + 3 * 3
11

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

def foo(x):
    return 2 + 3 * x

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

>>> foo(1)
5
>>> foo(2)
8
>>> foo(1000)
3002

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

Вызывающие выражения

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

call expression

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

Выполнение вызывающего выражения:

  • Вычислить значения оператора и операндов (слева направо).

  • Вызвать функцию (значение оператора) с аргументами (значения операндов).

Если оператор и/или операнды сами по себе являются вызывающими выражениями, то эта процедура повторяется рекурсивно.

return и print

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

def square(x):
    """
    >>> square(4)
    16
    """
    return x * x

Как только встречается инструкция return, интерпретатор перестаёт выполнять эту функцию — выходит из неё. Если в теле функции отсутствует return, то после выполнения всех инструкций в теле функции интерпретатор вернёт из неё значение None.

Функция же print используется для вывода информации в терминал. Из-за похожего поведения (в интерактивном режиме интерпретатор также выводит результат вызова функции в терминал) может возникнуть недопонимание.

Отметим, что в отличие от return, print не прекращает выполнение функции.

def what_prints():
    print('Привет!')
    return 'Тут выходим из функции.'
    print('U-курс — лучший курс!')

>>> what_prints()
Привет!
'Тут выходим из функции.'
Обычно print выводит строки без кавычек, а return — с кавычками.

Управляющие инструкции

Булевы операторы

Python поддерживает три булевых оператора: and, or и not.

>>> a = 4
>>> a < 2 and a > 0
False
>>> a < 2 or a > 0
True
>>> not (a > 0)
False

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

  • Выражение с И — <левое> and <правое>:

    1. Вычислить значение подвыражения <левое>.

    2. Если полученный результат v является ложным, то всё значение выражения считается равным v.

    3. В противном случае результатом всего выражения будет значение подвыражения <правое>.

  • Выражение с ИЛИ — <левое> or <правое>:

    1. Вычислить значение подвыражения <левое>.

    2. Если полученный результат v является истинным, то всё значение выражения считается равным v.

    3. В противном случае результатом всего выражения будет значение подвыражения <правое>.

  • Выражение с НЕ — not <выражение>:

    1. Вычислить значение выражения <выражение>; если полученное значение истинно, вернуть False, в противном случае — True.

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

>>> True and not False or not True and False

Человеку значение такого выражения определить непросто. Если использовать скобки, то результат будет более предсказуем. Python будет вычислять выражение как-то так:

>>> (True and (not False)) or ((not True) and False)

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

  • not обладает высшим приоритетом;

  • далее следует and;

  • or обладает низшим приоритетом.

Булевы операторы работают не только со значениями True и False. Значения 0, None, '' (пустая строка), [] (пустой список) интерпретатор рассматривает как ложные. Все остальные значения считаются истинными.

Оптимизации

Как думаешь, что напечатает интерпретатор в ответ на это?

>>> 1 / 0

Попробуй ввести это выражение в интерактивном режиме. Ты получишь ZeroDivisionError — ошибку деления на ноль. А как насчёт такого?

True or 1 / 0

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

Оператор Проверяет, что…​ Обрабатывает операнды слева направо, пока не встретит…​ Пример

and

все значения истинны

первое неистинное значение

False and 1 / 0False

or

хотя бы одно значение истинно

первое истинное значение

True or 1 / 0True

Оптимизация случается, когда встречается первый операнд, позволяющий сделать суждение о всём выражении. Например, and закончит проверять операнды как только встретит первое значение False, поскольку одно из значений уже точно не True.

И and, и or — оба возвращают последнее обработанное выражение. Кстати, значение этого выражения не всегда приводится к True или False.

Инструкция if

Условные инструкции в Python состоят из набора заголовков и наборов инструкций: обязательное предложение if, необязательное предложение elif и необязательное предложение else:

if <выражение>:
    <набор инструкций>
elif <выражение>:
    <набор инструкций>
else:
    <набор инструкций>

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

  1. Вычислить выражение заголовка.

  2. Если полученное значение истинно, выполнить набор и проигнорировать остальные предложения.

В случае, если все предложения if и elif не сработали и достигнуто предложение else, то выполняется его набор инструкций.

Инструкция while

Инструкция while нужна для циклического исполнения некоего набора инструкций. Предложение while состоит из заголовка и набора инструкций:

while <выражение>:
    <набор>

Выполнение инструкции while:

  1. Вычислить выражение в заголовке.

  2. Если результат истинен, выполнить набор инструкций и перейти к шагу 1.

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

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

Зависшую инструкцию while называют бесконечным циклом. Нажми Ctrl+C для его остановки.

Сообщения об ошибках

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

SyntaxError

Означает наличие синтаксической ошибки (например пропущенное двоеточие после инструкции if).

IndentationError

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

TypeError

Возникает в случае выполнения операции с аргументами неподдерживаемого/абсурдного типа (например сложение функции и целого числа).

ZeroDivisionError

Ошибка деления на ноль.

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

>>> square(3, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: square() takes 1 positional argument but 2 were given
  1. В последней строке указан тип ошибки — TypeError.

  2. Сообщение об ошибке объясняет, что не так — было передано 2 аргумента, тогда как square() может принять только один. Последняя строка самая информативная.

  3. Предпоследняя строка сообщает о месте возникновения ошибки — ошибка TypeError произошла в строке 1.

Практика

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

  • склонировать свою копию репозитория с этой лабораторной работой;

  • открыть файл lab_01.py в текстовом редакторе;

  • открыть терминал и перейти в папку со склонированным репозиторием;

  • выполнять задания, изменяя содержимое файла lab_01.py.

Задания могут быть двух видов:

  • Представь-себя-пайтоном (ПСП) — требуется проанализировать последовательность выражений и/или инструкций и записать результат.

  • Напиши программу — Текстом задания и доктестами описано некоторое поведение, которого надо добиться, написав/дописав некоторый программный код.

После завершения работы над каждым вопросом требуется фиксировать изменения (делать коммит), сопровождая его вменяемым описанием сделанного, например:

$ git add .
$ git commit -m "Решение вопроса 1"

Также не возбраняется в любое время проталкивать изменения на GitHub:

$ git push

Основная часть

Эту часть практических вопросов нужно успеть сделать на занятии.

Вопрос 1: Выражения (ПСП)

Это твой первый ПСП-вопрос. Для его выполнения отыщи в начале файла lab_01.py функцию _q_01. Эта функция ничего не делает, но содержит доктесты с пропусками, которые нужно заполнить ответами интерпретатора Python, то есть тебя.

Проверять правильность ответов на ПСП-вопросы можно и нужно из терминала вот таким образом:

$ python3 lab_01.py

Если что-то не так, будет выведено сообщение об ошибке с указанием функции и номера строки в файле.

Подумай и запиши, что выведет Python при обработке следующих выражений.

>>> 3
______
>>> 2 + 3
______
>>> -16 - -16
______
>>> 3 * 4 + 1
______
>>> 3 * (4 + 1)
______
>>> 2 ** 3
______

Если в выражении будет использована неизвестная переменная, то интерпретатор так и скажет: NameError: name __ is not defined. Это ошибка, и в пропуск надо записать что-то типа такого:

Traceback (most recent call last):
...
NameError: name 'unknown' is not defined
>>> x = 4
>>> 3 + x
______
>>> x + y
______
>>> x, y = 1, 2
>>> 3 + x
______
>>> x + y
______
>>> from operator import mul, add
>>> mul(3, 4)
______
>>> mul(3, add(4, 1))
______
>>> pow(2, 3)
______
>>> pow(pow(2, 3), abs(-2))
______

Вопрос 2: Управляющие инструкции (ПСП)

Коммит предыдущего вопроса сделан, так ведь? Тогда переходи к функции _q_02.

>>> def xk(c, d):
...     if c == 4:
...         return 6
...     elif d >= 4:
...         return 6 + 7 + c
...     else:
...         return 25
>>> xk(10, 10)
______
>>> xk(10, 6)
______
>>> xk(4, 6)
______
>>> xk(0, 0)
______
>>> def how_big(x):
...     if x > 10:
...         print('очень много')
...     elif x > 5:
...         return 'много'
...     elif x > 0:
...         print('мало')
...     else:
...         print("нисколько")
>>> how_big(7)
______
>>> how_big(12)
______
>>> how_big(1)
______
>>> how_big(-1)
______
Вывод интерпретатора может состоять из нескольких строк.
>>> n = 3
>>> while n >= 0:
...     n -= 1
...     print(n)
______

Случайные факты:

  1. Сочетание клавиш Ctrl+с помогает бороться с зависаниями.

  2. В случае необходимости значение переменной positive можно слегка изменить.

>>> positive = 28
>>> while positive:
...    print("positive?")
...    positive -= 3
______
>>> positive = -9
>>> negative = -12
>>> while negative:
...    if positive:
...        print(negative)
...    positive += 3
...    negative += 3
______

Вопрос 3: Вычисление логических выражений (ПСП)

Что тут сказать? Вперёд!

>>> True and 13
______
>>> False or 0
______
>>> not 10
______
>>> not None
______

Бывает, что выражение не может быть вычислено. Например, в нём требуется произвести деление на ноль, а на ноль делить нельзя — это все знают. Что же записать в пропуск?

Что-то такое:

Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> True and 1 / 0 and False
______
>>> True or 1 / 0 or False
______
>>> True and 0
______
>>> False or 1
______
>>> 1 and 3 and 6 and 10 and 15
______
>>> 0 or False or 2 or 1 / 0
______
>>> not 0
______
>>> (1 + 1) and 1
______
>>> 1/0 or True
______
>>> (True or False) and False
______

Вопрос 4: Исправь ошибку

Теперь придется не только понимать код, но и писать его — это вопрос второго типа.

Проверять правильность кода в таких вопросах нужно немного по-другому:

$ python3 -m doctest lab_01.py

Следующий фрагмент кода не работает! Найди косячки и исправь их.

def both_positive(x, y):
    """
    Возвращает True, если x и y — положительные.

    >>> both_positive(-1, 1)
    False
    >>> both_positive(1, 1)
    True
    """
    return x and y > 0

Вопрос 5: Сложение цифр

Напиши функцию, которая принимает неотрицательное целое число и складывает его цифры.

Припомни как работает целочисленное деление // и модуль (остаток от деления) %.
def sum_digits(n):
    """
    Суммирует все цифры n.

    >>> sum_digits(10) # 1 + 0 = 1
    1
    >>> sum_digits(4224) # 4 + 2 + 2 + 4 = 12
    12
    >>> sum_digits(1234567890)
    45
    >>> x = sum_digits(123) # проверяет применение return, а не print
    >>> x
    6
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Дополнительная часть

Эту часть, если есть время, можно поделать и дома.

Вопрос 6: Print vs Return

Переходи к функции _q_06.

print (в отличие от return) не приводит к выходу из функции!
>>> def ab(c, d):
...     if c > 5:
...         print(c)
...     elif c > 7:
...         print(d)
...     print('foo')
>>> ab(10, 20)
______
>>> def bake(cake, make):
...     if cake == 0:
...         cake = cake + 1
...         print(cake)
...     if cake == 1:
...         print(make)
...     else:
...         return cake
...     return make
>>> bake(0, 29)
______
>>> bake(1, "беспонтовый пирожок")
______

Вопрос 7: Множители

Дополни функцию factors, принимающую число n и выводящую все числа, которые делят n нацело. Например, для 20 найдутся такие числа: 20, 10, 5, 4, 2, 1.

def factors(n):
    """
    Выводит все числа, которые делят `n` без остатка.

    >>> factors(20)
    20
    10
    5
    4
    2
    1
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вопрос 8: Нисходящий факториал

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

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

assert позволяет программе выбросить ошибку, которую задает сам создатель программы:

assert <условие>, <текст_ошибки>

Под условием подразумевается любое условное выражение, которое описывает возможную ошибочную ситуацию. Текст ошибки - это сообщение, которое будет выведено после записи AssertionError. Как правило, даноое сообщение поясняет, почему возникла ошибка.

Допустим в программе, которую ты написал или написала, необходимо в качестве аргумента функции передавать неотрицательное число n. Тогда инструкция assert позволит вывести ошибку, если n окажется отрицательным. А также выведет текст ошибки:

>>>> assert n < 0, "Переменная n меньше нуля" # вывод ниже возможен, если n < 0
Traceback (most recent call last):
...
AssertionError: Переменная n меньше нуля
def falling(n, k):
    """
    Вычисляет нисходящий факториал n глубины k.

    >>> falling(6, 3)  # 6 * 5 * 4
    120
    >>> falling(4, 0)
    1
    >>> falling(4, 3)  # 4 * 3 * 2
    24
    >>> falling(4, 1)  # 4
    4
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вопрос 9. Удвоение восьмёрок

Напиши функцию, которая принимает число и определяет, есть ли в цифровом его представлении пара рядом стоящих восьмёрок.

def double_eights(n):
    """
    Возвращает True, если в n содержится комбинация цифр 88.

    >>> double_eights(8)
    False
    >>> double_eights(88)
    True
    >>> double_eights(2882)
    True
    >>> double_eights(880088)
    True
    >>> double_eights(12345)
    False
    >>> double_eights(80808080)
    False
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Не забудь отправить работу на проверку:

$ git add .
$ git commit -m "Полное решение"
$ git push