Функции с параметрами

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

В программировании особе внимание уделяется концепции о локальных и глобальных переменных, а также связанное с ними представление об областях видимости. Соответственно, локальные переменные видны только в локальной области видимости, которой может выступать отдельно взятая функция. Глобальные переменные видны во всей программе. «Видны» – значит, известны, доступны. К ним можно обратиться по имени и получить связанное с ними значение.

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

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

def rectangle():
    a = float(input("Ширина: "))
    b = float(input("Высота: "))
    print("Площадь: %.2f" % (a*b))

def triangle():
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    print("Площадь: %.2f" % (0.5 * a * h))

figure = input("1-прямоугольник, 2-треугольник: ")
if figure == '1':
    rectangle()
elif figure == '2':
    triangle()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Сколько здесь переменных? Какие из них являются глобальными, а какие – локальными?

Здесь пять переменных. Глобальной является только figure. Переменные a и b из функции rectangle(), а также a и h из triangle() – локальные. При этом локальные переменные с одним и тем же идентификатором a, но объявленные в разных функциях, – разные переменные.

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

В приведенной программе к глобальной области видимости относятся заголовки объявлений функций, объявление и присваивание переменной figure, конструкция условного оператора.

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

print(a)
1

Результат выполнения:

1-прямоугольник, 2-треугольник: 2
Основание: 4
Высота: 5
Площадь: 10.00
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    print(a)
NameError: name 'a' is not defined
1
2
3
4
5
6
7
8

Однако мы можем обращаться из функций к глобальным переменным:

def rectangle():
    a = float(input("Ширина %s: " % figure))
    b = float(input("Высота %s: " % figure))
    print("Площадь: %.2f" % (a*b))

def triangle():
    a = float(input("Основание %s: " % figure))
    h = float(input("Высота %s: " % figure))
    print("Площадь: %.2f" % (0.5 * a * h))

figure = input("1-прямоугольник, 2-треугольник: ")
if figure == '1':
    rectangle()
elif figure == '2':
    triangle()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Результат выполнения:

1-прямоугольник, 2-треугольник: 1
Ширина 1: 6.35
Высота 1: 2.75
Площадь: 17.46
1
2
3
4

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

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

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

result = 0

def rectangle():
    a = float(input("Ширина: "))
    b = float(input("Высота: "))
    result = a*b

def triangle():
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    result = 0.5 * a * h

figure = input("1-прямоугольник, 2-треугольник: ")
if figure == '1':
    rectangle()
elif figure == '2':
    triangle()

print("Площадь: %.2f" % result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

1-прямоугольник, 2-треугольник: 2
Основание: 6
Высота: 4.5
Площадь: 0.00
1
2
3
4

… что-то пошло не так.

Дело в том, что в Python присвоение значения переменной совмещено с ее объявлением. (Во многих других языках это не так.) Поэтому, когда имя result впервые упоминается в локальной области видимости, и при этом происходит присваивание ей значения, то создается локальная переменная result. Это другая переменная, никак не связанная с глобальной result.

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

Когда мы вызывали внутри функции переменную figure, то ничего ей не присваивали. Наоборот, мы запрашивали ее значение. Интерпретатор Питона искал ее значение сначала в локальной области видимости и не находил. После этого шел в глобальную и находил.

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

На самом деле можно принудительно обратиться к глобальной переменной. Для этого существует команда global:

result = 0

def rectangle():
    a = float(input("Ширина: "))
    b = float(input("Высота: "))
    global result
    result = a*b

def triangle():
    a = float(input("Основание: "))
    h = float(input("Высота: "))
    global result
    result = 0.5 * a * h

figure = input("1-прямоугольник, 2-треугольник: ")
if figure == '1':
    rectangle()
elif figure == '2':
    triangle()

print("Площадь: %.2f" % result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

В таком варианте программа будет работать правильно.

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

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

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

Упражнения

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

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

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

Возврат значений из функции. Оператор return

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

Если интерпретатор Питона, выполняя тело функции, встречает return, то он «забирает» значение, указанное после этой команды, и «уходит» из функции:

def cylinder():
    r = float(input("Enter radius: "))
    h = float(input("Enter hight: "))
    # площадь боковой поверхности цилиндра:
    side = 2 * 3.14 * r * h
    # площадь одного основания цилиндра:
    circle = 3.14 * r**2
    # полная площадь цилиндра:
    full = side + 2 * circle
    return full

square = cylinder()
print(square)
1
2
3
4
5
6
7
8
9
10
11
12
13

Пример выполнения:

3
7
188.4
1
2
3

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

В основной ветке программы это значение присваивается глобальной переменной square. То есть выражение square = cylinder() выполняется так:

Вызывается функция cylinder().

Из нее возвращается значение.

Это значение присваивается переменной square.

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

...
print(cylinder())
1
2

Здесь число, полученное из cylinder(), непосредственно передается функции print(). Если мы в программе просто напишем cylinder(), не присвоив полученные данные переменной или не передав их куда-либо дальше, то эти данные будут потеряны. Но синтаксической ошибки не будет.

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

def cylinder():
    try:
        r = float(input("Enter radius: "))
        h = float(input("Enter hight: "))
    except ValueError:
        return
    side = 2 * 3.14 * r * h
    circle = 3.14 * r**2
    full = side + 2 * circle
    return full

print(cylinder())
1
2
3
4
5
6
7
8
9
10
11
12

Если попытаться вместо цифр ввести буквы, то сработает return, вложенный в except. Он завершит выполнение функции, так что все нижеследующие вычисления, в том числе return full, будут опущены. Пример выполнения:

Enter radius: 5
Enter hight: hight
None
1
2
3

Но постойте! Что это за слово None, которое нам вернул «пустой» return? Это ничего, такой объект – «ничто». Он принадлежит классу NoneType. До этого мы знали четыре типа данных, они же четыре класса: int, float, str, bool. Пришло время пятого.

Когда после return ничего не указывается, то по умолчанию считается, что там стоит объект None. Но никто вам не мешает явно написать return None.

Более того. Ранее мы рассматривали функции, которые вроде бы не возвращали никакого значения, потому что в них не было оператора return. На самом деле возвращали, просто мы не обращали на него внимание, не присваивали никакой переменной и не выводили на экран. В Python всякая функция что-либо возвращает. Если в ней нет оператора return, то она возвращает None. То же самое, как если в ней имеется «пустой» return.

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

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

def cylinder():
    r = float(input("Enter radius: "))
    h = float(input("Enter hight: "))
    side = 2 * 3.14 * r * h
    circle = 3.14 * r**2
    full = side + 2 * circle
    return side, full

sCyl, fCyl = cylinder()
print("Площадь боковой поверхности %.2f" % sCyl)
print("Полная площадь %.2f" % fCyl)
1
2
3
4
5
6
7
8
9
10
11

Пример выполнения:

Enter radius: 4
Enter hight: 5
Площадь боковой поверхности 125.60
Полная площадь 226.08
1
2
3
4

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

a, b, c = 10, 15, 19
print("a =", a)             # a = 10
print("b =", b)             # b = 15
print("c =", c)             # c = 19
1
2
3
4

Фокус здесь в том, что перечисление значений через запятую (например, 10, 15, 19) создает объект типа tuple. На русский переводится как «кортеж». Это разновидность структур данных, которые будут изучены позже.

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

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

Распаковка не является обязательной. Будет работать и так:

print(cylinder())
1

Пример выполнения:

Enter radius: 4
Enter hight: 3
(75.36, 175.84)
1
2
3

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

Упражнения

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

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

Параметры и аргументы функции

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

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

Рассмотрим схему и поясняющий ее пример:

func_01

Когда функция вызывается, то ей передаются аргументы. В примере указаны глобальные переменные num1 и num2. Однако на самом деле передаются не эти переменные, а их значения. В данном случае числа 100 и 12. Другими словами, мы могли бы писать mathem(100, 12). Разницы не было бы.

Когда интерпретатор переходит к функции, чтобы начать ее исполнение, он присваивает переменным-параметрам переданные в функцию значения-аргументы. В примере переменной a будет присвоено 100, b будет присвоено 12.

Изменение значений a и b в теле функции никак не скажется на значениях переменных num1 и num2. Они останутся прежними. В Python такое поведение характерно для неизменяемых типов данных, к которым относятся, например, числа и строки. Говорят, что в функцию данные передаются по значению. Так, когда a присваивалось число 100, то это было уже другое число, не то, на которое ссылается переменная num1. Число 100 было скопировано и помещено в отдельную ячейку памяти для переменной a.

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

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

Произвольное количество аргументов

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

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

def cylinder(h, r = 1):
    side = 2 * 3.14 * r * h
    circle = 3.14 * r**2
    full = side + 2 * circle
    return full

figure1 = cylinder(4, 3)
figure2 = cylinder(5)
print(figure1)
print(figure2)
1
2
3
4
5
6
7
8
9
10

Результат выполнения:

131.88
37.68
1
2

При втором вызове cylinder() мы указываем только один аргумент. Он будет присвоен переменной-параметру h. Переменная r будет равна 1.

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

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

… figure3 = cylinder(10, 2) figure4 = cylinder(r=2, h=10) print(figure3) print(figure4) В данном случае оба вызова – это вызовы с одними и теми же аргументами-значениями. Просто в первом случае сопоставление параметрам-переменным идет в порядке следования. Во-втором случае – по ключам, которыми выступают имена параметров.

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

def oneOrMany(*a):
    print(a)

oneOrMany(1)
oneOrMany('1', 1, 2, 'abc')
oneOrMany()
1
2
3
4
5
6

Результат выполнения:

(1,)
('1', 1, 2, 'abc')
()
1
2
3

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

Упражнения

  1. Напишите программу, которая:

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

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

    • Функция get_input() не имеет параметров, запрашивает ввод с клавиатуры и возвращает в основную программу полученную строку.
    • Функция test_input() имеет один параметр. В теле она проверяет, можно ли переданное ей значение преобразовать к целому числу. Если можно, возвращает логическое True. Если нельзя – False.
    • Функция str_to_int() имеет один параметр. В теле преобразовывает переданное значение к целочисленному типу. Возвращает полученное число.
    • Функция print_int() имеет один параметр. Она выводит переданное значение на экран и ничего не возвращает.

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

  2. Напишите преобразователь римских цифр (I, II, ..., L) в арабские (1, 2, ..., 50):

    • в консоли просят ввести римское число;
    • функция is_rim_number(num) принимает на вход число, проверяет является ли введенное значение римской цифрой и возвращает True - если является или False - если нет;
    • функция rim_to_int(num) принимает на вход число, преобразует римскую цифру в целое число и возвращает полученное число.
    • вывести ответ в консоль.
  3. Напишите программу преобразования арабских целых чисел от 1 до 900 в римские.

    rim_convert_int

  4. Напишите программу преобразования римских цифр от 1 до 900 в арабские.

  5. Напишите программу калькулятор для римских цифр, с операциями сложения и вычитания. Программа должна уметь работать с цифрами от I до L:

    • программа в консоли просит ввести пример вида: "XII + IV";
    • функция is_rome_example(example) принимает на вход пример в текстовом виде, проверяет корректность введенного примера, возвращает True, если пример корректный;
    • функция get_elementes(example) принимает на вход пример в текстовом виде, преобразует его в список ['XII', '+', 'IV'] и возвращает его;
    • функция is_rome_number(number) принимает на вход число, проверяет является ли введенное значение римской цифрой и возвращает True - если является или False - если нет;
    • функция rome_to_int(number) принимает на вход римское число. Преобразует римское число в целое арабское число, и возвращает значение;
    • функция get_example_as_list(list) принимает на вход пример в виде списка ['XII', '+', 'IV'], и возвращает пример в виде списка [12, '+', 4]. Функция должна использовать функции is_rim_number(num) и rim_to_int(num).
    • функция get_answer(list) принимает на вход пример в виде списка [12, '+', 4], решает его и возвращает ответ в виде арабского числа 16;
    • функция int_to_rome(number) принимает на вход целое число. Преобразует число в римское число, и возвращает значение;
    • ответ выводиться в консоль.

    Проверьте правильность работы программы с правильными примерами:

    • II + XIV = XVI
    • XVII - XIV = III
    • LI - xx + v = XXXVI
    • i+X-v+xx = XXVI
    • ix - x + xL - xv = XXIV

    Проверьте корректность отработки неправильных примеров:

    • XX + = Error
    • -i = Error
    • VV + XX = Error
    • IIII - + X = Error
    • I + X - = Error