Стратегії обчислення
Стратегія обчислення — це семантика використання формальних і фактичних параметрів функції.
Кожна стратегія обчислення визначає, коли слід обчислювати аргументи функції і які знзчення передавати. Часто поняття стратегії обчислення називають "способом передачі параметрів", хоча для деяких стратегій обчислення (наприклад, «виклик по необхідності») такий термін не є коректним.
Існує декілька стратегій обчислення, ось деякі:
- Виклик по значенню (call-by-value). Сама розповсюджена стратегія обчислення. При виклику по значенню, вираз-аргумент обчислюється, і отримане значенння зв'язується з відповідним формальним параметром функції (зазвичай за допомогою копіювання цього значення у новую ділянку пам'яті). При цьому, якщо мова програмування дозволяє функціям присвоювати значення своїм параметрам, то зміни будуть стосуватися лише цих локальних копій, але видимі у місці виклика функції значення залишаться незміненими після повернення.
- Виклик по посиланню При виклику по посиланню (call-by-reference), або передачі по посиланню (pass-by- reference), функція неявно отримує посилання на змінну, яку було використано у якості аргумента, змість копії її значення. Зазвичай це означає, що функція може здійснювати модифікацію (тобто змінювати стан) змінної, переданої в якості параметра, і це також матиме ефект у контексті, що викликав функцію.
- Виклик по співвикористанню (виклик зі спільним використанням ресурсів, call-by-sharing). Значення у мові програмування основані на об'єктах, а не на примітивних типах. При виклику по співвикористанню функція отримує значення, яке містить копію посилання на об'єкт. Сам об'єкт не копіюється — він буде використовуватись спільно. Як наслідок, присвоєння аргументу у тілі функції не має ефекта у контексті, що її викликав, але присвоєння компонентам цього аргумента — має.
Стратегія обчислення в Python
В Python використовується стратегія обчислення "виклик по співвикористанню". На практиці це означає, що при передачі параметрів в функцію в Python ми не можемо прив'язати фактичні параметры до інших об'єктів або змінити їх, якщо вони належать до немутабельним типів, однак можемо модифікувати їх значення, якщо вони належать до мутабельних типів.
>>> def double(obj):
... print(f'До: {obj}')
... obj = obj * 2
... print(f'Після: {obj}')
...
>>> x = 7
>>> double(x)
До: 7
Після: 14
>>> x
7
>>>
Значенн змінної x
не змінилось оскільки вона вказує на немутабельний int
.
Спробуємо інший виклик:
>>> x = [1, 2]
>>> double(x)
До: [1, 2]
Після: [1, 2, 1, 2]
>>> x
[1, 2]
>>>
Функції було передано мутабельний list
.
Але значенн змінної x
не змінилось!
Чому так?
- Функція приймає параметр
obj
. - В функції змінній
obj
присвоюється значення. - Спочатку обчислюється вираз після знака присвоєння. Це значення на який вказує парамет
obj
- Потім локальній змінній
obj
присвоюється обчислене значення. - Об'єкт, на який вказував параметр
obj
не змінюється
Давайте все ж таки спробуємо змінити переданий функції об'єкт:
>>> def append(obj):
... print(f'До: {obj}')
... obj.append(7)
... print(f'Після: {obj}')
...
>>> x = [1, 2]
>>> append(x)
До: [1, 2]
Після: [1, 2, 7]
>>> x
[1, 2, 7]
>>>
Значенн змінної x
змінилось оскільки вона вказує на мутабельний list
і цей об'єкт було змінено всередині функції.
Дуже часто можна зустріти ствердження, що у Python аргументи передаються по посиланню. Однак, як ми впевнились вище, це не зовсім відповідає дійсності. Просто завжди пам'ятайте про це.
Резюме
- в Python використовується стратегія обчислення "передача по співвикористанню"
- немутабельний аргумент ніколи не можна змінити всередині функції
- мутабельний аргумент можна змінити, але не можна присвоїти йому нове значення
Додаткові матеріали
"Стратегії обчислення" у Wikipedia
Завдання
Не запускаючи код на виконання визначте яке значення буде відповідати змінній my_list
після його виконання і поясніть чому саме так. Можете перевірити себе запустивши код.
def modify_list(lst):
lst = [777]
my_list = [1, 2]
modify_list(my_list)