Нормальные имена переменных

Умение давать хорошие имена переменным гораздо более важно, чем обычно кажется учащимся. Кроме того, это гораздо менее тривиально, чем кажется! Этому правда нужно учиться! Посмотрите, как выглядит реальный код: раз, два, три.

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

Допускается использование односложных имен переменных для счётчиков (i, j, k) в циклах for, а также если это имя фигурирует в условии задачи (N, M, K, L).

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

Если имя переменной или функции состоит из нескольких слов, то они должны разделяться символом подчеркивания. Например:
calc_sqrt – божественно
calculate_square_root – допустимо, но длинновато
vychislenie_kornya – недопустимо (используются русские имена)
f – недопустимо (непонятно назначение функции)

Обычно в качестве имён функций и переменных используются одно или несколько слов (или их сокращений) маленькими буквами с разделением символом подчёркивания. Например, factorial, calc_factorial(n), calc_fact(n), iter, find_next_value(), num_apples, apple_num и т.д. Для перевода слов можно использовать deepl.

Обычно имена переменных для чисел — это какое-то существительное или пара существительных: total, tot_sum, cur_elem, iter, prev_elem, next_elem, item_sq, num_apples, amount, tot_amt, seq_len, str_len, average, fib1, fib2, result, floor_num, hall_num, area, numerator, denominator, denom, quot, ratio.

Если переменная указывает на список, кортеж, словарь, множество или другую коллекцию, то используют множественное число: pupils, edges и т.д.

Хорошие имена для функций часто одного из трёх типов:

Имена констант записываются полностью заглавными буквами. Если имя константы состоит из нескольких слов, для их разделения используются подчеркивания. Например, EPSILON, MAX_SIZE.

Функции в языке 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.

Возвращаем из функции и принимаем снаружи наборы чисел

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

return (a, b)

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

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

Как что выглядит и как называется

Обратите внимание, не в каждой функции должен быть вывод на экран, ввод с клавиаутры или возвращаемое значение. Читайте условие, обычно в каждой функции есть что-то одно из этого!

О тестировании

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

A: Среднее значение

Напишите функцию average(a), которая получает на вход список чисел и возвращает среднее значение элементов данного списка. Список содержит только числа и не пуст.

Сдайте на проверку только тело функции.

def average(A):
    решение
    # Ничего после функции не добавлять! Ни input'ов, ни print'ов!

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

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

average([1, 7, 9])
1 7 9
5.666666666666667

Код вызова функции для тестирования
print(average(list(map(float, input().split()))))
IDE

B: Медиана трех чисел

Напишите функцию median(a, b, c), которая получает на вход три числа и возвращает их медиану. В решении нельзя использовать циклы.

Сдайте на проверку только тело функции.

median(1, 9, 7)
1 9 7
7
Код вызова функции для тестирования
print(median(int(input()), int(input()), int(input())))
IDE

Ретурнобулофобия

Ретурнобулофобия — боязнь начинающих программистов использовать логическое выражение в инструкции return при возвращении значения из булевой функции. Симптомы: использование следующей конструкции в программе

def f():
    ...
    if expression:
       return True
    else
       return False

Лечение: использование следующей конструкции:

def f():
    ...
    return expression

Другие примеры проявления этого недуга:

if expression:
   return False
else
   return True
#
if expression:
   var = True
else
   var = False

C: Принадлежит ли точка квадрату - 1

Даны два действительных числа \(x\) и \(y\). Проверьте, принадлежит ли точка с координатами \((x,y)\) заштрихованному квадрату (включая его границу). На рисунке сетка проведена с шагом 1.

Решение оформите в виде функции is_point_in_square(x, y), возвращающую True, если точка принадлежит квадрату и False, если не принадлежит.

Функция is_point_in_square не должна содержать инструкцию if и должна иметь такой вид:

def is_point_in_square(x, y):
    return ...

Сдайте на проверку только тело функции.

is_point_in_square(0, 0)
0 0
True
is_point_in_square(3, -7)
3 -7
False
Код вызова функции для тестирования
res = is_point_in_square(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

D: Принадлежит ли точка квадрату — 2

Решите аналогичную задачу для такого квадрата:

Решение должно соответствовать требованиям для решения задачи C.

Функция должна называться is_point_in_rhombus.

is_point_in_rhombus(0, 0)
True
0 0
is_point_in_rhombus(1, 1)
1 1
False
Код вызова функции для тестирования
res = is_point_in_rhombus(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

E: Принадлежит ли точка кругу

Даны пять действительных чисел: \(x\), \(y\), \(x_c\), \(y_c\), \(r\). Проверьте, принадлежит ли точка \((x,y)\) кругу с центром \((x_c,y_c)\) и радиусом \(r\).

Решение оформите в виде функции is_point_in_circle(x, y, xc, yc, r).

Решение должно соответствовать требованиям для решения задачи C.

is_point_in_circle(0.5, 0.5, 0, 0, 1)
0.5 0.5 0 0 1
True
is_point_in_circle(0.5, 0.5, 1, 1, 0.1)
0.5 0.5 1 1 0.1
False
Код вызова функции для тестирования
res = is_point_in_circle(float(input()), float(input()), float(input()), float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

Кладём результат проверки в переменную

Напомним, что логические выражения в духе 1 < x <= 10 or x > 20 можно «класть» в переменные, чтобы использовать потом. Часто это позволяет сделать код куда более «контролируемым» и читаемым. Пример:

x_between_or_greater = 1 < x <= 10 or x > 20
if (x_between_or_greater and y > 0) or (not x_between_or_greater and y < 0):
    print('В реальных примерах условия сложнее')

Кстати, скобки в условиях можно было не ставить, у and выше приоритет.

F: Принадлежит ли точка области

Проверьте, принадлежит ли точка данной закрашенной области. Область неограничена снизу.

Решение оформите в виде функции is_point_in_area(x, y).

Инструкцию if использовать по-прежнему нельзя.

is_point_in_area(-1, 2)
-1 2
True
is_point_in_area(0, 0)
0 0
False
Код вызова функции для тестирования
res = is_point_in_area(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

G: Разверните число

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

Решение оформите в виде функции reverse(n), которая получает на вход значение типа int и возвращает значение типа int.

reverse(179)
179
971
Код вызова функции для тестирования
res = reverse(int(input()))
if type(res) == int:
    print(res)
else:
    print("Функция должна возвращать значение типа int")
IDE

H: Следующий палиндром

Для данного числа \(n\) определите следующее за ним число, запись которого является палиндромом.

Решение оформите в виде функции next_palindrome(n), которая получает на вход значение типа int и возвращает значение типа int.

В решении используйте функцию из предыдущей задачи. Сдайте на проверку две функции: reverse и next_palindrome.

next_palindrome(179)
179
181
Код вызова функции для тестирования
res = next_palindrome(int(input()))
if type(res) == int:
    print(res)
else:
    print("Функция должна возвращать значение типа int")
IDE

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

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

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

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

Сдайте на проверку только тело функции.

min_divisor(4)
4
2
min_divisor(5)
5
5
Код вызова функции для тестирования
for __i in range(2, int(input())):
    d = min_divisor(__i)
    print(__i, d)
IDE

J: Проверка числа на простоту

Дано натуральное число \(n>1\). Проверьте, является ли оно простым.

Решение оформите в виде функции is_prime(n), которая возвращает True для простых чисел и False для составных чисел. Решение должно иметь сложность \(O(\sqrt{n})\).

Сдайте на проверку только тело функции.

is_prime(2)
2
True
is_prime(4)
4
False
Код вызова функции для тестирования
for __i in range(2, int(input())):
    res = is_prime(__i)
    if type(res) != bool:
        print("Функция is_prime(" + str(__i) + ") вернула значение не типа bool")
    elif res:
        print(__i, "YES")
    else:
        print(__i, "NO")
IDE

K: Разложение на множители

Дано натуральное число \(n>1\). Постройте его разложение на простые множители. Решение должно быть оформлено в виде функции factor(n), возвращающей список делителей числа в порядке неубывания. Каждый делитель должен быть включен в список с учетом его кратности.

Функция должна перебирать делители до \(\sqrt{n}\), при уменьшении числа \(n\) граница перебора также должна уменьшатся.

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

factor(132)
132
[2, 2, 3, 11]
factor(2)
2
[2]
Код вызова функции для тестирования
for __i in range(int(input()), int(input()) + 1):
    print(__i, factor(__i))
IDE

L: Произведение цифр

Дано натуральное число \(n\). Посчитайте произведение его цифр, не используя строковые операции (напишите цикл, перебирающий цифры числа). Решение оформите в виде функции product_of_digits.

product_of_digits(179)
63
Код вызова функции для тестирования
print(median(int(input()), int(input()), int(input())))
IDE

Упражнения на реализацию логических функций

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

M: Исключающее ИЛИ (XOR)

Реализуйте функцию xor(x, y), реализующую логическую операцию XOR (исключающее или, обозначается \(x \oplus y\)). Функция возвращает True, если одно (но не оба одновременно!) из значений параметров функции есть True. То есть функция задаётся следующей таблицей истинности (для удобства в ней использованы числа 0 и 1 для обозначения логических величин):

\(\ x\ \)\(\ y\ \)\(x\oplus y\)
000
011
101
110
xor(False, True)
True
Код вызова функции для тестирования
output = ""
for __x in [False, True]:
    for __y in [False, True]:
        res = xor(__x, __y)
        if type(res) != bool:
            print("Функция должна возвращать значение типа bool")
            sys.exit(0)
        output += "x = {0}, y = {1}, res = {2}\n".format(__x, __y, res)
print(output, end="")
IDE

N: Импликация (следование)

Реализуйте функцию imp(x, y), реализующую логическую операцию “импликация” (следование, обозначается \(x\Rightarrow y\)). Функция возвращает True, если из утверждения \(x\) следует утверждение \(y\), а именно, из ложного утверждения следует как ложное, так и истинное утверждение, а из истинного утверждения следует только истинное утверждение. То есть функция задаётся следующей таблицей истинности.

\(\ x\ \)\(\ y\ \)\(x\Rightarrow y\)
001
011
100
111
imp(False, True)
True
Код вызова функции для тестирования
output = ""
for __x in [False, True]:
    for __y in [False, True]:
        res = imp(__x, __y)
        if type(res) != bool:
            print("Функция должна возвращать значение типа bool")
            sys.exit(0)
        output += "x = {0}, y = {1}, res = {2}\n".format(__x, __y, res)
print(output, end="")
IDE

O: Функция голосования

Реализуйте функцию голосования (или большинства) major(x, y, z) (обозначается\(\langle x, y, z\rangle\)). Функция возвращает то значение, которое встречается чаще среди её аргументов. Функцию можно задать следующей таблицей:

\(\ x\ \)\(\ y\ \)\(\ z\ \)\(\langle x, y, z\rangle\)
0000
0010
0100
0111
1000
1011
1101
1111
major(False, True, False)
False
Код вызова функции для тестирования
output = ""
for __x in [False, True]:
    for __y in [False, True]:
        for __z in [False, True]:
            res = major(__x, __y, __z)
            if type(res) != bool:
                print("Функция должна возвращать значение типа bool")
                sys.exit(0)
            output += "x = {0}, y = {1}, z = {2}, res = {3}\n".format(__x, __y, __z, res)
print(output, end="")
IDE

Упражнения на вывод текста из функции

В данных упражнениях функция выводит что-то на экран, при помощи функции print, но не возвращает значения.

P: Гистограмма

Напишите функцию histogram(a), принимающей в качестве параметра список целых неотрицательных чисел. Функция должна напечатать на экран столько строчек, сколько элементов в списке. \(i\)-я строка должна содержать \(a[i]\) символов “#” (в нумерации с нуля). Функция не возвращает значения.

histogram([1, 7, 9])
1 7 9
#
#######
#########
Код вызова функции для тестирования
__a = list(map(int, input().split()))
res = histogram(__a)
if res is not None:
    print("Функция НЕ должна возвращать значение")
IDE

Q: Напечатать треугольник

Напишите функцию triangle(n), получающую в качестве параметра целое положительное число \(n\) и выводящую на экран треугольник из \(n\) строк, составленный из символов “*”. Первая строка содержит одну звёздочку, каждая следующая строка содержит на 2 звёздочки больше, чем предыдущая. При этом все строки, кроме последней, должны содержать в начале один или несколько пробелов так, чтобы строки были выровнены по центру последней строки.

triangle(4)
4
*
***
*****
*******
Код вызова функции для тестирования
__n = int(input())
res = triangle(__n)
if res is not None:
    print("Функция НЕ должна возвращать значение")
IDE

R: Напечатать ёлочку

Напишите функцию fir(a, b), получающую на вход два целых числа \(1\le a\le b\), и выводящую ёлочку из \(b - a + 1\) треугольников высоты \(a\), \(a + 1\), ..., \(b\). Используйте функцию triangle из предыдущей задачи, модифицировав её так, чтобы она получала второй параметр: дополнительное число пробелов, которое нужно добавить в начало каждой строки выводимого треугольника. Самая нижняя строка ёлочки не должна содержать пробелов в начале строки. Сдайте на проверку две функции.

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

fir(2, 4)
2 4
*
***
*
***
*****
*
***
*****
*******
Код вызова функции для тестирования
__a, __b = map(int, input().split())
res = fir(__a, __b)
if res is not None:
    print("Функция НЕ должна возвращать значение")
IDE

Упражнения на считывание данных

В следующих задачах функция должна считать данные с клавиатуры при помощи функции input и вернуть что-то при помощи инструкции return.

S: Вернуть только чётные числа

Напишите функцию read_even(), которая

  1. считывает при помощи функции input список чисел, записанных в одной строке через пробел,
  2. составляет новый список, содержащий только чётные из считанных чисел,
  3. возвращает полученный список.
read_even()
1 2 3 4 5 6
[2, 4, 6]
Код вызова функции для тестирования
res = read_even()
if type(res) != list:
    print("Функция должна возвращать список")
    sys.exit(0)
for elem in res:
    if type(elem) != int:
        print("Все элементы списка должны иметь тип int")
        sys.exit(0)
print(res)
IDE

T: Вернуть числа, больше данного

Напишите функцию read_greater(x), которая

  1. считывает при помощи функции input список чисел, записанных в одной строке через пробел,
  2. составляет новый список, содержащий те числа, которые больше x
  3. возвращает полученный список.
read_greater(2)
1 2 3 4 5 6
[3, 4, 5, 6]
read_greater(3)
6 5 4 3 2 1
[6, 5, 4]
Код вызова функции для тестирования
__x = int(input())
res = read_greater(__x)
if type(res) != list:
    print("Функция должна возвращать список")
    sys.exit(0)
for elem in res:
    if type(elem) != int:
        print("Все элементы списка должны иметь тип int")
        sys.exit(0)
print(res)
IDE

U: Считать последовательность до нуля

Напишите функцию read, которая будет считывать последовательность чисел, записанных по одному в строке, пока не встретит строку, содержащую число 0. Числа, следующие за числом 0, считывать не нужно. Функция должна создать и вернуть список из считанных чисел (не включая 0).

read()
1
2
3
0
[1, 2, 3]
Код вызова функции для тестирования
res = read()
if type(res) != list:
    print("Функция должна возвращать список")
    sys.exit(0)
for elem in res:
    if type(elem) != int:
        print("Все элементы списка должны иметь тип int")
        sys.exit(0)
print(res)
print("Несчитанный остаток входных данных:")
print(sys.stdin.read())
IDE

V: Считать последовательность до двух равных чисел

Напишите функцию read, которая будет считывать последовательность чисел, записанных по одному в строке, пока не встретит два подряд идущих равных числа. Функция должна вернуть это число. Последующие числа считывать не нужно.

read()
1
2
3
3
4
3
Код вызова функции для тестирования
res = read()
if type(res) != int:
    print("Функция должна возвращать значение типа int")
    sys.exit(0)
print(res)
print("Несчитанный остаток входных данных:")
print(sys.stdin.read())
IDE

W: Читайте условия внимательно

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

read(7)
1 2 3
8 9 10
[-6, -5, -4]
Код вызова функции для тестирования
__k = int(input())
res = read(__k)
if type(res) != list:
    print("Функция должна возвращать список")
    sys.exit(0)
for elem in res:
    if type(elem) != int:
        print("Все элементы списка должны иметь тип int")
        sys.exit(0)
print(res)
IDE

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

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

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
    a = 1
    print(a)
a = 0
f()
print(a)

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

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

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

Упражнения на глобальные переменные

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

X: Сумма элементов списка

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

total_sum = 0
sum_list([1, 2, 3])
print(total_sum)
sum_list([1, 7, 9])
print(total_sum)
1 2 3
1 7 9
6
23
Код вызова функции для тестирования
total_sum = 0
for line in sys.stdin:
    __a = list(map(int, line.split()))
    print(__a)
    res = sum_list(__a)
    if res is not None:
        print("Функция НЕ должна возвращать значение")
        sys.exit(0)
    print(total_sum)
IDE

Y: Наибольшая цифра числа

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

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

max_digit_value = 0
max_digit(321)
print(max_digit_value)
max_digit(179)
print(max_digit_value)
321
179
3
9
Код вызова функции для тестирования
max_digit_value = 0
for line in sys.stdin:
    __n = int(line)
    print(__n)
    res = max_digit(__n)
    if res is not None:
        print("Функция НЕ должна возвращать значение")
        sys.exit(0)
    print(max_digit_value)
IDE

Полный список объявленных переменных

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

def remove_magic(it):
    return [name for name in it if not name.startswith('__')]
a = 123
print('Сначала', remove_magic(globals()))
# Сначала ['remove_magic', 'a']
print('foo определена:', 'foo' in globals())
# foo определена: False
def foo(x, y):
    print('Внутри foo, локальные', remove_magic(locals()))
    # Внутри foo, локальные ['x', 'y']
    z = x + y
    print('Внутри foo, локальные', remove_magic(locals()))
    # Внутри foo, локальные ['x', 'y', 'z']
    return z
print('Потом', remove_magic(globals()))
# Потом ['remove_magic', 'a', 'foo']
print('foo определена:', 'foo' in globals())
# foo определена: True
foo(1, 2)

Z: Последняя бессмысленная задача

Напишите функцию read(), которая считывает с клавиатуры целое число и сравнивает его с предыдущим считанным этой функцией числом. Значение предыдущего считанного числа сохраняется в глобальной переменной prev. Если новое значение совпадает с предыдущим считанным значением, то функция должна вернуть True, а если не совпадает (или если это первый вызов функции), то функция должна вернуть False. В любом случае, функция должна обновить значение переменной prev.

print(read())
print(prev)
print(read())
print(prev)
print(read())
print(prev)
print(read())
print(prev)
8
8
7
8
False
8
True
8
False
7
False
8
Код вызова функции для тестирования
__count = int(input())
for i in range(__count):
    res = read()
    if type(res) != bool:
        print("Функция должна возвращать значение типа bool")
        sys.exit(0)
    print(res)
    print(prev)
IDE

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

В тех случаях, когда у функции нет необязательных параметров, а её тело может быть записано в одну строчку, существует другой способ записи функции:
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).
В питоне не слишком часто используют замыкания явно, в то время, как, скажем, в javascript без замыканий обойтись практически невозможно.
Смысл замыкания состоит в том, что определение функции "замораживает" окружающий её контекст на момент определения. Это может делаться различными способами, например, за счёт параметризации создания функции. При замыкании будут использованы переменные из объемлющих функций. Для того, чтобы переменная попала в замыкание, она должна быть упомянута в коде функции:
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

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

def gen_adders():
    i = 5
    def add5(x):
        return x + i
    i = 10
    def add10(x):
        return x + i
    i = 15
    return add5, add10

adder1, adder2 = gen_adders()
print(adder1(0))  # -> 15
print(adder2(0))  # -> 15

В данном случае обе функции используют одну и ту же захваченную из функции gen_adders переменную i, что и приводит к такому «неожиданному» поведению.