Добавил:
Developer Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / 7. Функции - Jupyter Notebook

.pdf
Скачиваний:
32
Добавлен:
15.12.2022
Размер:
800 Кб
Скачать

15.10.2022, 11:11

7. Функции - Jupyter Notebook

Функции

Пример определения функции

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

In [1]:

1 from math import sqrt

2 sqrt(25)+sqrt(9)

Out[1]:

8.0

Что означает этот код? В первой строчке мы импортировали функцию sqrt из пакета math , то есть загрузили в память (или «выписали на бумажку») инструкцию о том, как считать квадратные корни (её написали раньше умные люди, разработчики Python). Эта инструкция — такая маленькая программка (когда-то давно функций в современном понимании не было, но были подпрограммы).

Во второй строчке необходимо вычислить сумму двух выражений, каждое из которых, в свою очередь, содержит вызов функции sqrt . В тот момент, когда мы написали sqrt(25) , Python посмотрел на бумажку и пользуясь этой маленькой программкой посчитал корень из того числа, которое мы этой функции передали (то есть из 25). После этого функция «вернула» значение 5.0 . Это означает, что в строчке sqrt(25)+sqrt(9) фрагмент sqrt(25) превратился в результат выполнения функции, то есть в 5.0 . Затем то же самое произошло с sqrt(9) , этот фрагмент превратился в число 3.0 . Затем мы сложили два числа и получили число 8.0 .

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

Обычно в математике факториал обозначается восклицательным знаком (например, ).

Давайте для начала напишем программу для вычисления факториала какого-нибудь числа. Она будет выглядеть так:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

1/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [1]:

 

 

1

n =

5

 

2

 

 

 

3

f =

1

 

4

for

i in range(2, n+1):

 

5f = f * i

6 # эквивалентный синтаксис: f *= i

7 print(f)

120

Здесь инициализируется переменная f , в ней помещается значение 1, затем переменная f последовательно умножается на все числа от 2 до n включительно (нам пришлось прибавить единицу при вызове range() , потому что правый конец никогда не включается в интервал, см. про циклы и range() ), каждый раз записывая результат умножения снова в f . Таким образом в f оказалось искомое произведение.

Если бы нам пришлось вычислять факториалы в разных частях программы, то можно было бы просто скопировать в них этот короткий фрагмент кода. Однако, так почти никогда делать не следует: если вам приходится копировать какие-то строчки кода в своей программе, почти наверняка это значит, что вы делаете что-то не то. (В программировании это называется «принципом DRY» — Don't Repeat Yourself.) Например, представьте себе, что вы скопируете этот код в десять мест программы, а потом придумаете, как сделать вычисление факториала более эффективным. Вам придётся тогда вносить изменение в десять разных мест!

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

In [3]:

1 def factorial(n):

2f = 1

3for i in range(2, n+1):

4

f = f * i

5return f

При выполнении этой ячейки вроде бы ничего не произошло — по крайней мере, Python не выдал никакого вывода. Так и должно быть. На самом деле, в этот момент Python достал большую чёрную записную книжку на странице на букву f (это метафора, на самом деле нет никаких страниц) и записал в неё: «если меня попросят выполнить функцию factorial, нужно сделать вот такие действия». Теперь мы можем вызывать функцию factorial , передавая ей параметр.

In [7]:

1 factorial(6)

Out[7]:

720

И даже использовать её в более сложных выражениях:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

2/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [8]:

1 factorial(5)+factorial(6)

Out[8]:

840

Посмотрим более внимательно на то, что происходит, когда Python вычисляет значение выражения factorial(6) . В первую очередь он открывает свою записную книжку и ищет там функцию factorial . Находит (поскольку раньше мы её туда записали). Дальше он смотрит на первую строчку определения функции (это так называемая сигнатура):

def factorial(n):

Здесь он видит, что функция factorial() имеет аргумент, который называется n . Python помнит, что мы вызвали factorial(6) , то есть значение аргумента должно быть равно 6. Таким образом, дальше он выполняет строчку (которую мы не писали)

n = 6

После чего выполняет остальные строчки из тела функции:

f = 1

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

Наконец он доходит до строчки return f

В этот момент переменная f имеет значение 24. Слово return означает, что Python должен вернуться к строчке, в которой был вызов factorial(6) , и заменить там factorial(6) на 24 (то, что написано после return ). На этом вызов функции завершён.

Возвращаемые значения и побочные эффекты

Давайте а нарисую такую схему:

Аргументы

Функция

Возвращаемое

значение

 

 

Побочные

эффекты

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

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

3/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

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

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

In [9]:

1 def hello(name):

2return "Hello, "+name+"!"

In [10]:

1 s = hello("World")

In [11]:

1 s

Out[11]:

'Hello, World!'

В переменной s сейчас — результат выполнения функции hello() , которой на вход передали аргумент name , равный "World" .

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

In [12]:

1 def say_hello(name):

2print("Hello, "+name+"!")

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

In [14]:

1 s = say_hello("Harry")

Hello, Harry!

Обратите внимание: теперь при выполнении s = say_hello("Harry") строчка выводится на печать. Это и есть побочный эффект выполнения функции say_hello . Что лежит в переменной s ?

In [15]:

1 s

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

4/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [16]:

1 print(s)

None

В ней лежит ничего. Это специальный объект None , который испоьзуется, когда какое-то значение нужно присвоить переменной, но никакого значения нет. В данном случае его нет, потому что функция ничего не вернула. Возвращаемого значения у say_hello() нет. Так бывает.

Более сложные ситуации с функциями

Функции могут вызывать другие функции. Например, вместо того, чтобы копировать строчку "Hello, "+name+"!" из функции hello() в функцию say_hello() , просто вызовем hello() из say_hello() .

In [18]:

1 def new_say_hello(name):

2print(hello(name))

In [19]:

1 new_say_hello("Harry")

Hello, Harry!

Современные программы обычно так и выглядят — это набор из множества функций, каждая из которых вызывает другие функции. В обычной жизни происходит примерно то же самое: мы разбиваем задачу, которую нужно решить, на более простых задач, которые нужно решить последовательно, потом каждую из этих задач разбиваем на ещё более простые и так далее. Например, чтобы решить задачу «доехать до университета», нужно решить задачи «выйти из дома», «дойти до остановки общественного транспорта», «сесть на общественный транспорт», «доехать до нужной остановки» и т.д. Чтобы решить задачу «выйти из дома», нужно решить задачу «встать с кровати», «позавтракать», «одеться», «открыть дверь» и т.д. В программистских терминах мы бы написали функцию go_to_university() , которая бы в какой-то момент вызывала функции exit_from_home() , go_to_stop() , get_transport() и т.д., а функция exit_from_home() вызывала бы wake_up() , breakfast() и т.д.

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

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

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

5/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [17]:

1 def binom(k, n):

2"""

3 calculates binomial coeffs: k from n

4 k, n are integers

5retuns C_n^k

6"""

7return factorial(n)//(factorial(k)*factorial(n-k))

8# можно использовать целочисленное деление, поскольку результат гарантированно целы

Сколькими способами можно выбрать двух дежурных из трёх участников похода? Тремя — потому что выбрать двух дежурных это то же самое, что и выбрать одного человека, который не дежурит.

In [18]:

1 binom(2,3)

Out[18]:

3

В тройных кавычках сразу после сигнатуры функции обычно приводится её описание (так называемая docstring ). Это комментарий для людей, которые будут использовать вашу функцию в будущем. Даже если это будете вы сами, скорее всего, через месяц вы уже забудете, что именно делает эта функция, какие аргументы принимает и какие значения возвращает. Написать всё это сразу бывает очень полезно. Чтобы посмотреть на эту справку, можно набрать название вашей функции, открывающую скобку и нажать Shift+Tab+Tab.

После выполнения строчки return выполнение функции прекращается. Давайте рассмотрим ещё один пример: вычислим модуль некоторого числа. Код для этой функции может иметь такой вид (я назвал её my_abs() , потому что в Python есть встроенная функция abs , которая решает ту же задачу):

In [20]:

1 def my_abs(x):

2if x > 0:

3return x

4else:

5return -x

In [21]:

1 my_abs(-5)

Out[21]:

5

Это самое простое решение: если число положительное, то возвращается оно само, а если отрицательное, то возвращается оно с обратным знаком ( -x ). Подробнее об условном операторе if написано в конспекте лекции №3

Можно было бы написать эту функцию и таким образом:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

6/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [22]:

1 def my_abs(x):

2print("New my_abs")

3 # если функция с таким названием уже была, то Python про неё забудет и запишет вмес 4 # чтобы убедиться в этом я поставил здесь этот print

5if x > 0:

6

return x

7return -x

In [23]:

1 my_abs(-6)

New my_abs

Out[23]:

6

Здесь происходит следующее: если число положительное, то срабатывает return x и после этого выполнение функции прекращается, до строчки return -x дело не доходит. А если число отрицательное, то наборот, срабатывает только строчка return -x (из-за оператора if ).

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

7/17

15.10.2022, 11:11 7. Функции - Jupyter Notebook

In [28]:

1 %load_ext tutormagic

---------------------------------------------------------------------------

ModuleNotFoundError Traceback (most recent call last)

<ipython-input-28-d31836f5d23a> in <module>

----> 1 get_ipython().run_line_magic('load_ext', 'tutormagic')

C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py

in run_line_magic(self, magic_name, line, _stack_depth)

2315

kwargs['local_ns'] = sys._getframe(stack_depth).f_lo

cals

 

2316

with self.builtin_trap:

-> 2317

result = fn(*args, **kwargs)

2318

return result

2319

 

<C:\ProgramData\Anaconda3\lib\site-packages\decorator.py:decorator-gen-65> i n load_ext(self, module_str)

C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\magic.py in <lambda>

(f, *a, **k)

185# but it's overkill for just that one bit of state.

186def magic_deco(arg):

--> 187 call = lambda f, *a, **k: f(*a, **k)

188

189 if callable(arg):

C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\magics\extension.py

in load_ext(self, module_str)

31

if not module_str:

32

raise UsageError('Missing module name.')

---> 33

res = self.shell.extension_manager.load_extension(module_str

)

 

34

 

35

if res == 'already loaded':

C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\extensions.py in loa d_extension(self, module_str)

78

if module_str not in sys.modules:

79

with prepended_to_syspath(self.ipython_extension_dir

):

mod = import_module(module_str)

---> 80

81

if mod.__file__.startswith(self.ipython_extensio

n_dir):

 

82

print(("Loading extensions from {dir} is dep

recated. "

 

C:\ProgramData\Anaconda3\lib\importlib\__init__.py in import_module(name, pa

ckage)

 

125

break

126

level += 1

--> 127

return _bootstrap._gcd_import(name[level:], package, level)

128

 

129

 

C:\ProgramData\Anaconda3\lib\importlib\_bootstrap.py in _gcd_import(name, pa ckage, level)

C:\ProgramData\Anaconda3\lib\importlib\_bootstrap.py in _find_and_load(name,

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

8/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

import_)

C:\ProgramData\Anaconda3\lib\importlib\_bootstrap.py in _find_and_load_unloc ked(name, import_)

ModuleNotFoundError: No module named 'tutormagic'

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

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

In [26]:

1 f = 10

2

3 def factorial(n):

4f = 1

5for i in range(2, n+1):

6f = f * i

7 print("In the function, f =", f) 8 return f

9

10f = 11

11print(f)

12print(factorial(8))

13print("Out of function")

14print(f)

11

In the function, f = 40320 40320

Out of function 11

Тот же код, запущенный в визуализаторе:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

9/17

15.10.2022, 11:11

7. Функции - Jupyter Notebook

In [27]:

1 %%tutor lang='python3'

2 f = 10

3

4 def factorial(n):

5f = 1

6for i in range(2, n+1):

7f = f * i

8 print("In the function, f =", f) 9 return f

10

11f = 10

12print(f)

13print(factorial(8))

14print("Out of function")

15print(f)

UsageError: Cell magic `%%tutor` not found.

Как видно из результата выполнения этого кода, переменная f в основной программе и переменная f внутри функции — это совсем разные переменные (визуализатор pythontutor рисует их в разных фреймах). От того, что мы как-то меняем f внутри функции, значение переменной f вне её не поменялось, и наоборот. Это очень удобно: если бы функция меняла значение «внешней» переменной, то она могла бы сделать это случайно и это привело бы к непредсказуемым последствиям.

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

In [29]:

1 def hello_i18n(name, lang):

2if lang == 'ru':

3print("Привет,",name)

4else:

5print("Hello,",name)

In [31]:

1 hello_i18n("Ivan", 'ru')

Привет, Ivan

In [32]:

1 hello_i18n("Ivan", 'en')

Hello, Ivan

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

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/7. Функции.ipynb

10/17