6. Функції

6.1. Скрипт [fonc_01]: область дії змінних
Скрипт [fonc_01] демонструє приклади області дії змінних між функціями:
# область дії змінних
def f1():
# Слід уникати використання глобальних змінних
# глобальна змінна i
global i
i += 1
# локальна змінна j
j = 10
print(f"f1[i,j]=[{i},{j}]")
def f2():
# слід уникати використання глобальних змінних
# глобальна змінна i
global i
i += 1
# локальна змінна j
j = 20
print(f"f2[i,j]=[{i},{j}]")
def f3():
# локальна змінна i
i = 1
# локальна змінна j
j = 30
print(f"f3[i,j]=[{i},{j}]")
# головна програма
i = 0
j = 0
# ці дві змінні будуть відомі функції f
# лише в тому випадку, якщо вона явно за допомогою оператора global оголосить, що хоче їх використовувати
# або якщо функція використовує глобальну змінну лише для читання
f1()
f2()
f3()
# j не змінився, але i змінився
print(f"[i,j]=[{i},{j}]")
Результати
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_01.py
f1[i,j]=[1,10]
f2[i,j]=[2,20]
f3[i,j]=[1,30]
[i,j]=[2,0]
Process finished with exit code 0
Примітки:
- скрипт демонструє використання змінної i, оголошеної як глобальна у функціях f1 та f2. У цьому випадку головна програма та функції f1 і f2 використовують одну й ту саму змінну i.
6.2. Скрипт [fonc_02]: область дії змінних
Скрипт [fonc_03] базується на скрипті [fonc_02] і демонструє, як уникнути використання глобальних змінних:
# область дії змінних
def f1(i):
# локальна змінна i
i += 1
# локальна змінна j
j = 10
print(f"f1[i,j]=[{i},{j}]")
# повертаємо змінене значення
return i
def f2(i):
# локальна змінна i
i += 1
# локальна змінна j
j = 20
print(f"f2[i,j]=[{i},{j}]")
# повертаємо змінене значення
return i
def f3():
# локальна змінна i
i = 1
# локальна змінна j
j = 30
print(f"f3[i,j]=[{i},{j}]")
# головна програма
i = 0
j = 0
# ці дві змінні будуть відомі функції f
# лише в тому випадку, якщо вона явно за допомогою оператора global оголосить, що хоче їх використовувати
# або якщо функція використовує глобальну змінну лише для читання
i = f1(i)
i = f2(i)
f3()
# j не змінилася, але i змінилася
print(f"[i,j]=[{i},{j}]")
Коментарі:
- рядки 2, 12: замість того, щоб оголошувати змінну [i] глобальною, її передають як аргумент у функції f1 та f2;
- рядки 9, 19: функції f1 та f2 повертають змінену змінну [i] до головної програми. Головна програма отримує її у рядках 36 та 37;
Результати
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_02.py
f1[i,j]=[1,10]
f2[i,j]=[2,20]
f3[i,j]=[1,30]
[i,j]=[2,0]
Process finished with exit code 0
6.3. Скрипт [fonc_03]: область дії змінних
Скрипт [fonc_03] демонструє особливість змінних, що використовуються як у функції, так і в коді, який її викликає, залежно від того, чи використовується ця змінна у функції виключно для читання, чи ні.
def f1():
# тут глобальна змінна i відома
print(f"[f1] i={i}")
# тут глобальна змінна j відома
print(f"[f1] j={j}")
def f2():
# тут глобальна змінна i невідома
# оскільки функція f2 визначає локальну змінну з таким самим іменем
# тоді саме вона має пріоритет
try:
# спроба вивести на екран локальну змінну i, визначену далі
print(f"[f2] i={i}")
# наступна інструкція робить i локальною змінною функції f2
i = 7
except BaseException as erreur:
print(f"[f2] erreur={erreur}")
def f3():
# тут глобальна змінна i невідома
# оскільки функція f3 визначає локальну змінну з таким самим іменем
# тож саме вона має пріоритет
# наступна інструкція робить i локальною змінною
i = 7
# виведення — тут i є відомою
print(f"[f3] i={i}")
# main -----------
# глобальні змінні функцій
i = 10
j = 20
# виклик f1
f1()
print(f"[main] i={i}, j={j}")
# виклик f2
f2()
print(f"[main] i={i}")
# виклик f3
f3()
print(f"[main] i={i}")
Примітки
- рядок 34: основний код визначає змінну [i];
- рядки 1–5: функція f1 також використовує змінну [i], не присвоюючи їй значення. Це зчитування змінної [i]. У цьому випадку використовується змінна [i] із коду, що її викликає, рядок 34;
- рядки 8–18: функція f2 також використовує змінну [i], але присвоює їй значення в рядку 16. Присвоєння значення змінній [i] у f2 автоматично робить [i] локальною змінною функції [f2]. Таким чином, вона «приховує» змінну [i] у коді, що викликає;
- рядок 14: операція запису локальної змінної [i] завершиться невдачею, оскільки вона не має значення на момент досягнення рядка 14. Вона отримує своє значення в рядку 16. Станеться виняток. З цієї причини рядок 14 вкладено в блок try / catch;
- рядки 21–29: функція f3 виконує те саме, що й функція f2, але раніше визначає свою локальну змінну [i];
Результати
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_03.py
[f1] i=10
[f1] j=20
[main] i=10, j=20
[f2] erreur=local variable 'i' referenced before assignment
[main] i=10
[f3] i=7
[main] i=10
Process finished with exit code 0
6.4. Скрипт [fonc_04]: спосіб передачі параметрів
Скрипт має такий вигляд:
# функція f1
def f1(a):
a = 2
# функція f2
def f2(a, b):
a = 2
b = 3
return a, b
# ------------------------ main
x = 1
f1(x)
print(f"x={x}")
(x, y) = (-1, -1)
(x, y) = f2(x, y)
print(f"x={x}, y={y}")
Результати
Примітки:
- у Python усе є об’єктами. Деякі об’єкти називаються «незмінними» (angl. immutable): їх неможливо змінити. Це стосується чисел, рядків та кортежів. Коли об’єкти Python передаються як аргументи до функцій, передаються їхні посилання, за винятком випадків, коли ці об’єкти є «незмінними» — тоді передається саме значення об’єкта;
- функції f1 (рядок 2) та f2 (рядок 7) мають на меті проілюструвати передачу вихідного параметра. Ми хочемо, щоб фактичний параметр функції був змінений самою функцією;
- рядки 2–3: функція f1 змінює свій формальний параметр a. Потрібно з’ясувати, чи буде змінено також і фактичний параметр;
- рядки 14–15: фактичним параметром є x=1. У рядку 2 результатів показано, що фактичний параметр не змінюється. Отже, фактичний параметр x і формальний параметр a — це два різні об’єкти;
- рядки 8–10: функція f2 змінює свої формальні параметри a та b і повертає їх як результати;
- рядки 17–18: до функції f2 передаються фактичні параметри (x, y), а результат функції f2 записується в (x, y). Рядок 3 результатів показує, що фактичні параметри (x, y) були змінені.
Звідси випливає, що коли «незмінні» об’єкти є вихідними параметрами, вони повинні бути частиною результатів, що повертаються функцією.
6.5. Скрипт [fonc_05]: порядок запису функцій у скрипті
Скрипт [fonc_05] демонструє, що функцію неможливо викликати, якщо вона раніше не зустрічалася в коді:
# ------------------------ main
print(f2(100, 200))
# функція f1
def f1(a):
return a + 10
# функція f2
def f2(a, b):
return f1(a + b)
Примітки
- рядок 2 спричинить помилку, оскільки в ньому використовується функція f2, яка ще не була визначена у скрипті;
Результати
6.6. Скрипт [fonc_06]: порядок запису функцій у скрипті
Скрипт [fonc_06] показує, що те, що справедливо для коду, що викликає, не справедливо для функцій:
# функція f2
def f2(a, b):
return f1(a + b)
# функція f1
def f1(a):
return a + 10
# ------------------------ main
print(f2(100, 200))
Примітки
- рядок 3: функція [f2] використовує функцію [f1], визначену далі у скрипті. Однак це не спричиняє помилки. Отже, можна зробити висновок, що порядок визначення функцій у скрипті Python не має значення;
Результати
6.7. Скрипт [fonc_07]: використання модулів
Скрипт [fonc_07] демонструє, як виділити функції в окремий модуль.

Ми виділяємо функції, які можна використовувати повторно, в окремий модуль. Замість того, щоб переносити їх з одного скрипта в інший:
- ми розміщуємо їх в окремому файлі, який оголошуємо певним чином;
- скрипти, яким потрібні ці функції, «імпортують» модуль, що їх містить;
Скрипт [fonctions_module_01] має такий вигляд:
# функція f2
def f2(a, b):
return f1(a + b)
# функція f1
def f1(a):
return a + 10
Щоб функції скрипта [fonctions_module_01] могли використовуватися іншими скриптами, існують різні способи. Вони відрізняються залежно від того, чи виконується скрипт усередині [PyCharm].
У скрипті [PyCharm] імпортовані модулі шукаються у певних папках, що мають назву [Sources Root]. Існує два способи перетворення папки на [Sources Root]:

- у [4] папка змінила колір;
Після цієї операції папка [fonctions/modules] розпізнається як вихідна папка. Тоді у скрипті можна написати:
Щоб імпортувати / використовувати функцію f2, визначену в модулі [fonctions_module_01.py].
![]() |
Інший спосіб — скористатися властивостями проєкту:
- у наведеному вище прикладі послідовність [1-6] дозволяє перетворити папку [shared] на папку, куди можна розміщувати модулі для імпорту;
Наразі ми не будемо заявляти жодної папки, такої як [Sources Root], окрім кореневої папки проєкту:

Зробивши це, можна написати такий скрипт [fonc-07]:
# використання модулів
import sys
# ------------------------ main
print(f"Python path={sys.path}")
from fonctions.shared.fonctions_module_01 import f2
print(f2(100, 200))
- рядок 2: імпортуємо об’єкт [sys], щоб мати змогу використовувати в рядку 5 його атрибут [path], який надає так званий [Python Path] — список папок, які будуть перевірені на наявність імпортованих модулів;
- рядок 6: імпортуємо функцію f2 з модуля [fonctions_module_01]. Щоб вказати цей модуль, використовується шлях, що веде від кореневого каталогу проєкту до модуля. У Pycharm кореневий каталог проєкту завжди входить до переліку каталогів, що скануються під час пошуку імпортованого модуля у скрипті. Отже, цей каталог є частиною [Python Path] проєкту. Саме це ми зможемо перевірити за допомогою рядка 5;
- рядок 6: якби ми описували шлях від кореневого каталогу проєкту до папки [fonctions_module_01], то написали б [fonctions/shared/fonctions_module_01]. У шляху до модуля знак / замінюється крапкою. Отже, ми пишемо [fonctions.modules.fonctions_module_01];
- після рядка 6 функція f2 відома. Її використовують у рядку 8;
Результати
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_07.py
Python path=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages']
310
Process finished with exit code 0
Вище:
- виділено зеленим кольором, видно, що коренева папка проєкту є частиною [Python Path];
- виділено жовтим кольором, видно, що папка зі скриптом, що виконується, також є частиною [Python Path];
- інші елементи [Python Path] походять безпосередньо з інсталяційної папки Python;
Що відбувається, якщо не використовувати PyCharm для запуску [fonc-07]?


- у [1] виконується скрипт [fonc-07]. Ми знаходимося в папці [fonctions];
- у [2] бачимо, що папка виконання є частиною [Python Path]. Так відбувається завжди. Також можна побачити, що коренева папка [C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020] не є частиною [Python Path];
- у [3] інтерпретатор Python повідомляє, що не може знайти модуль [fonctions];
Щоб знайти імпортований модуль [fonctions.shared.fonctions_module_01], інтерпретатор Python шукає в папках [Python Path] підпапку з назвою [fonctions]. Він ніде його не знаходить. Справа в тому, що підпапка [fonctions] знаходиться в папці [C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020], яка не входить до складу [Python Path].
Скрипт [fonc-08] пропонує можливе рішення цієї проблеми.
6.8. Скрипт [fonc_08]: додавання папок до [Python Path]
[Python Path] можна змінити програмно, як показано у скрипті [fonc-08]:
# використання модулів
import os
import sys
# папка скрипта
script_dir = os.path.dirname(os.path.abspath(__file__))
# Python Path до зміни
print(f"Python path avant={sys.path}")
# додаємо папку [shared] до Python Path
sys.path.append(f"{script_dir}/shared")
# Шлях Python після зміни
print(f"Python path après={sys.path}")
# import f2
from fonctions_module_01 import f2
# ------------------------ main
print(f2(100, 200))
Примітки
- рядок 4: спеціальна змінна [__file__] — це ім’я скрипта, що виконується. Залежно від контексту виконання це ім’я може бути абсолютним (Pycharm) або відносним (консоль). Функція [os.path.abspath] повертає абсолютне ім’я файлу, ім’я якого їй передано. Функція [os.path.dirname] повертає абсолютне ім’я папки, що містить файл, ім’я якого їй передано;
- рядок 10: [sys.path] — це масив, що містить імена папок, які слід обшукувати під час пошуку модуля. До цього масиву додається коренева папка проєкту, визначена в рядку 4;
- виводимо [Python Path] до (рядок 8) та після (рядок 12) зміни;
- рядок 15: імпортується модуль [fonctions_module_01], що містить функцію f2;
Виконання в PyCharm дає такі результати:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_08.py
Python path avant=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages']
Python path après=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions/shared']
310
Process finished with exit code 0
- рядок 3: бачимо, що папка [shared] двічі присутня у [Python Path]. Цього можна уникнути, але в даному випадку це не заважає;
- рядок 4: функція f2 була успішно виконана;
Тепер запустимо [fonc-08] у терміналі:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions>python fonc_08.py
Python path avant=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages']
Python path après=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions/shared']
310
- рядок 2: як і раніше, папка [shared] відсутня у [Python Path];
- рядок 3: тепер вона там є;
- рядок 4: функція f2 знайдена;
6.9. Скрипт [fonc_09]: оголошення типів параметрів
Скрипт [fonc_09] показує, що можна оголосити типи параметрів функції, а також тип результату. Однак таке оголошення корисне лише для документації функції. Інтерпретатор Python не перевіряє, чи фактичні параметри функції дійсно мають очікуваний тип. Проте Pycharm сигналізує про невідповідності типів між фактичними та формальними параметрами. Ця одна причина є достатньою, щоб зробити оголошення типів незамінними.
Скрипт має такий вигляд:
# функція із зазначенням типів параметрів
# це слугує лише для документації, оскільки інтерпретатор Python не враховує цю інформацію
def show(param: int) -> int:
print(f"param={param}, type(param)={type(param)}")
return param + 1
# main -------------------------
print(show(4))
show("xyz")
Примітки:
- рядок 5: оголошується, що формальний параметр [param] має тип [int], а результат функції також має тип [int];
- рядок 11: фактичний параметр функції [show] має правильний тип;
- рядок 12: фактичний параметр функції [show] має неправильний тип;
Результати
- рядок 10: тип параметра [param] відповідає типу [str]. Коли з’являється це повідомлення, ми вже увійшли в код функції [show]. Отже, інтерпретатор Python прийняв, що фактичний параметр функції [show] має тип [str];
- рядок 7 коду викликає виняток, відображений у рядках 4–10 результатів;
проте PyCharm вказує на наявність аномалії:

У [1] PyCharm виділив помилковий виклик.
6.10. Скрипт [fonc_10]: іменовані параметри
Щоб передати параметри до функції, можна використовувати імена її формальних параметрів. У цьому випадку не обов’язково дотримуватися порядку формальних параметрів:
# фактичні параметри можна позначати їхніми формальними іменами
def f(x, y):
return x + y
# main
print(f(y=10, x=3))
Примітки
- рядок 2: функція f має два формальні параметри x та y;
- рядок 7: під час виклику функції f можна використовувати імена формальних параметрів. Ця практика може бути корисною принаймні у двох випадках:
- функція має багато параметрів, більшість із яких мають значення за замовчуванням. Під час виклику описана вище техніка дозволяє ініціалізувати лише ті параметри, для яких не бажано використовувати значення за замовчуванням;
- якщо формальні параметри мають змістовні імена, то використання іменованих параметрів під час виклику функції покращує читабельність коду;
Результати
6.11. Скрипт [fonc_11]: рекурсивна функція
Скрипт [fonc_11] є прикладом рекурсивної функції (яка викликає саму себе):
# рекурсивна функція
def fact(i: int) -> int:
# факторіал(1) дорівнює 1
# рекурсивна функція повинна в якийсь момент завершитися
if i == 1:
return 1
else:
# факторіал(i) = i * факторіал(i-1)
return i * fact(i - 1)
# ---------- main
print(f"fact(8)={fact(8)}")
Коментарі
- рядки 1–9: функція факторіалу;
- рядок 9: функція [factorielle] викликає саму себе;
- рядки 5–6: рекурсивна функція завжди повинна зупинятися, коли виконується умова, інакше виникає нескінченна рекурсія;
Результати
6.12. Скрипт [fonc_12]: рекурсивна функція
Функція [fonc_12] надає додаткові деталі щодо роботи рекурсії:
# рекурсивна функція
# поведінка параметра j
def fact(i: int, j: int) -> int:
# завершення рекурсивної функції
if i == 1:
print(f"j={j}")
return 1
else:
# обробка j
j += 1
print(f"avant fact j={j}")
# рекурсивність
f = fact(i - 1, j)
print(f"après fact j={j}")
# результат
return i * f
# ---------- main
print(f"fact(8)={fact(8, 0)}")
Коментарі
- рядок 5: ми як і раніше розглядаємо функцію факторіалу. До неї додаємо параметр [j];
- рядок 12: змінна j послідовно збільшується після кожного обчислення факторіалу. Її значення виводиться до (рядок 12) та після (рядок 16) рекурсії (рядок 15);
Результати
- рядки 2–8: бачимо, що значення [j] зростає, доки триває рекурсія, аж до виконання умови, за якої рекурсія зупиняється. З цього моменту повернення викликів функції [fact] відбувається у зворотному порядку до викликів;
- рядки 10–16: ці виводи відображають послідовні повернення з виклику функції факторіалу. Змінна [j] повертається до своїх значень аж до початкового значення 1;
