- В Питоне переменные передаются по ссылке или по значению? Есть подводные камни?
- Изменяемые (mutable)
- Подводные камни
- Что с этим можно сделать
- Что почитать
- Попробуйте бесплатные уроки по Python
- Что вы не знали и передаче параметров в функции
- Передача параметров в других языках
- Использует ли Python модель передачи по значению?
- Использует ли Python передачу по ссылке?
- Объекты в Python
- Изменяемые и неизменяемые объекты
- Переменные и их имя
- В Python используется передача параметров через присваивание
- Как передать переменную по ссылке в Python?
- Возвращение кортежа
- Использование глобальных переменных
- Передача изменяемого (mutable) объекта
- Передача словаря
- Передача экземпляра класса
В Питоне переменные передаются по ссылке или по значению? Есть подводные камни?
Неизменяемые объекты передаются по значению. Это значит, что при изменении значения переменной будет создан новый объект. К этому типу относятся:
- числовые данные (int, float, complex)
- символьные строки (str)
- кортежи (tuple) При инициализации переменной незменяемого типа создается объект (например, целое число), этот объект имеет некоторый идентификатор:
оператор = связывает переменную a и объект посредством ссылки. При этом вы не можете изменить сам объект, т.е. когда вы присвоите переменной новое значение, интерпретатор создаст новый объект (если до этого этот объект был создан, то переменная просто получит ссылку), а первоначальный объект удалится из памяти сбощиком мусора, если ссылок на него больше нет.
Изменяемые (mutable)
Изменяемые объекты передаются по ссылке. Это значит, что при изменении значения переменной объект будет изменен. К этому типу относятся:
Подводные камни
Создадим список a , установим для переменной b ссылку на a , прибавим к b элемент списка и выведем их значения и идентификаторы на экран:
>>> a = [1, 2] >>> b = a >>> b.append(3) >>> print(a, b) [1, 2, 3] [1, 2, 3] >>> print(id(a), id(b)) 139748057891656 139748057891656
Как мы видим, переменные имеют одинаковые id и элементы списка. Если ты не знаешь об этой особенности изменяемых объетов, то такое поведение программы для тебя становится полной неожиданностью и может привести к ошибке в работе программы. Таким же образом с помощью ссылки на изменяемый объект, переменная передается в функцию:
>>> def add_value(a): . a.append(3) >>> b = [1, 2] >>> add_value(b) >>> print(b) [1, 2, 3]
Даже возвращая None , функция изменила список b , чего бы нам не хотелось.
Что с этим можно сделать
Для того, чтобы передать в функцию изменяемую переменную как значение, нужно сделать копию изменяемого элемента. Создадим копию списка:
Тоже самое можем сделать вот так:
новый_лист = list(старый_лист)
Переменная новый_лист ссылается на новый объект:
>>> id(старый_лист), id(новый_лист) (139748050279112, 139748057891656)
Это дает нам возможность изменять оба объекта независимо друг от друга.
Что почитать
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Что вы не знали и передаче параметров в функции
В Python при вызове функции используется передача параметров по ссылке или по значению? Сегодня мы попытаемся разобраться. В этой статье вы узнаете: Python не использует модели передачи по ссылке и по значению, а применяет передачу параметров через присваивание; о характеристиках объектов; разницу между изменяемыми(mutable) и неизменяемыми (immutable) объектами.
Передача параметров в других языках
Многие языки программирования предлагают две модели передачи параметров в функции/процедуры:
И то, какую модель использует язык программирования, важно знать, поскольку за эффективность кода отвечает программист лично. А теперь попробуем разобраться, какую модель использует язык Python.
Использует ли Python модель передачи по значению?
При передачи по значению в функцию данные копируются в момент её вызова. Здесь важно понять, что данные (значения) именно копируются, это не те же самые значения. Поэтому в теле функции с ними можно делать всё что угодно, в т.ч. изменять, и это не отразится на исходных данных (тех, которые передали), так как это всего лишь их копии.
В Python не применяется модель передачи по значению, но в некоторых случаях можно видеть нечто похожее. Например, рассмотрим следующий пример:
def foo(x): x = 4 a = 3 foo(a) print(a) # 3
Функция вызывается, но изменения оказанные на переменную a больше не действую после возврата из функции, ведь a всё ещё равна 3. Поэтому может показаться, что используется передача по значению, но это не так. Python не копирует значения параметров при вызове функции. Если мы рассмотрим другую функцию:
def clearly_not_pass_by_value(my_list): my_list[0] = 42 l = [1, 2, 3] clearly_not_pass_by_value(l) print(l) # [42, 2, 3]
— то мы четко видим, что элемент исходного списка l был изменен после вызова функции. Значит ли это, что объект списка использовался один и тот же?
Использует ли Python передачу по ссылке?
При передачи по ссылке в момент вызова функции передаются адреса переменных, причем с адресами работают так, как если бы это была обычная переменная (поэтому не нужно дополнительно проводить разыменование, как это делается в Си). Такая модель подразумевает, что исходные переменные и параметры функции — это одни и те же объекты. Изменяя параметры в теле функции, вы изменяете их и в вызывающем контексте.
Так вот Python не использует и эту модель передачи параметров. Взглянем на следующий код:
def not_pass_by_reference(my_list): my_list = [42, 73, 0] l = [1, 2, 3] not_pass_by_reference(l) print(l) # [1, 2, 3]
Мы предполагаем, что функция должна полностью изменить список, но этого не происходит, он остался прежним. Так какая же модель передачи параметров в функции в используется в Python?
Объекты в Python
В Python всё является объектом. Каждый объект характеризуется следующими характеристиками:
- идентифицирумостью (каждый объект обладает уникальными номером),
- типом (каждый тип обладает своими операциями, которые можно применять),
- содержимое самого объекта.
Эти характеристики можно узнать следующим образом:
>>> id(obj) 2698212637504 >>> type(obj) >>> obj [1, 2, 3]
Эти знания нам пригодятся в дальнейшем.
Изменяемые и неизменяемые объекты
Объекты в Python могут быть изменяемыми (mutable) или неизменяемыми (immutable). Это свойство полностью зависит от типа объекта. Иными словами, не/изменяемость является характеристикой типа, а не конкретных объектов.
Тип является изменяемым, если содержимое объекта может быть изменено без изменений его идентификатора и типа.
Список (list) является изменяемым типом данных. Почему? Потому что списки являются контейнерами: в них можно добавлять данные и из можно удалять данные. Иными словами, их можно спокойно изменять.
Ниже приведен пример того, как содержимое списка меняется, но идентификатор остается тем же самым, что бы мы ни делали.
>>> obj = [] >>> id(obj) 2287844221184 >>> obj.append(0); obj.extend([1, 2, 3]); obj [42, 0, 1, 2, 3] >>> id(obj) 2287844221184 >>> obj.pop(0); obj.pop(0); obj.pop(); obj 42 0 3 [1, 2] >>> id(obj) 2287844221184
С другой стороны, содержимое неизменяемых объектов нельзя изменить. Они существует так, как их инициализировали в первый раз. Строка str является хорошим примером неизменяемого типа.
Вызывая различные методы, призванные как-то модифицировать состояние, объекты с неизменяемым типом что-то возвращают, в отличие от изменяемых. Например, метод списка append ничего не возвращает (он изменяет существующий), а методы строки возвращают новую строку (новый объект):
>>> [].append(0) # Ничего не возвращается >>> "Hello, world!".upper() # Возвращается новая строка 'HELLO, WORLD!"
Другим сигналом неизменяемости объекта является невозможность изменить отдельные его элементы, например, при присваивании через индексы:
>>> obj[0] 'H' >>> obj[0] = "h" Traceback (most recent call last): File "", line 1, in TypeError: 'str' object does not support item assignment
Для справки, в Python встроенные типы:
Переменные и их имя
Ещё необходимо понять, что название переменной — это не то же самое, что и сам объект. Например, имя obj был просто некой меткой, которая была связана с объектом с идентификатором 2287844221184 , типом list и содержимым 1, 2, 3 . Это называется механизмом связывания, о котором мы говорил тут.
Мы вольны связывать и другие метки к этому же самому объекту:
>>> foo = bar = baz = obj >>> id(foo) 2698212637504 >>> id(bar) 2698212637504 >>> id(baz) 2698212637504 >>> id(obj) 2698212637504 # При этом, если изменить объект: >>> obj = 5 >>> id(obj) 94830320197056
Как видим все метки обращаются к одному объекту. В Python даже есть специальный оператор is , который проверяет являются ли два объекты одной сущностью (читай: равны ли их id ).
>>> foo is obj True >>> bar is foo True >>> obj is foo True
Изменяя содержимое через одну переменную, эти изменения можно пронаблюдать через другие, поскольку это один и тот же объект. При этом нужно понимать, объекты, которые имеют тот же тип и содержимое, но не id , не являются одним объектом:
В Python используется передача параметров через присваивание
Теперь мы готовы понять, что за модель передачи параметров используется в Python. При вызове функции каждый параметр связывается с соответствующим объектом, указанным в сигнатуре функции.
Так, если мы передаем неизменяемые параметр (например, int ), то у нас нет возможности хоть как-то его изменить. Каждый раз, когда используется присваивание, то создается новый объект, хоть и имеющий то же самое имя. Взгляните на данный пример:
def foo(bar): bar = 3 return bar a = 5 foo(a)
Вызов функции для неизменяемого типа подразумевает, что используется связывание bar = 5 . Сразу после этого в теле функции осуществляется второе связывание bar = 3 . Но все эти объекты разные. Поэтому при использовании неизменяемых объектов в качестве параметров, их передача осуществляется как будто по схеме передаче значений.
Как передать переменную по ссылке в Python?
В Питоне аргументы функций передаются путем присваивания. Поскольку присваивание просто создает новые ссылки на объекты, то между именами аргумента в вызывающем и вызываемом методе нет общей ссылки.
Как же передать аргумент по ссылке? Вы можете добиться нужного эффекта несколькими способами.
Возвращение кортежа
def my_func(x, y): # x и y локальные имена, которым присвоены новые объекты x = 'новое значение' y = y + 1 # возвращаем новые значения return x, y a, b = 'старое значение', 9 a, b = my_func(a, b) print(a, b) результат: новое значение 10
Это почти всегда самое ясное решение.
Использование глобальных переменных
Использование глобальных переменных не рекомендуется, т. к. не потокобезопасно.
Передача изменяемого (mutable) объекта
def func1(a): # 'a' ссылается на изменяемый список a[0] = 'новое значение' a[1] = a[1] + 1 args = ['старое значение', 9] func1(args) print(args) результат: ['новое значение', 10]
Передача словаря
def func2(args): # args - изменяемый словарь args['a'] = 'новое значение' args['b'] = args['b'] + 1 args = func2(args) print(args) результат:
Передача экземпляра класса
class MyNamespace: def __init__(self, /, **args): for key, value in args.items(): setattr(self, key, value) def func3(args): args.a = 'новое значение' args.b = args.b + 1 args = Namespace(a='старое значение', b=9) func3(args) vars(args) результат:
Почти никогда не бывает веской причины, чтобы все так усложнять.
Лучший вариант из представленных – это вернуть кортеж, содержащий несколько возвращаемых значений.