Передача по ссылке в Python: общие сведения и рекомендации
Оглавление
- Определение передачи по ссылке
- Противопоставление передачи по ссылке и передачи по значению
- Использование сквозных ссылочных конструкций
- Передача аргументов в Python
- Реплицирование передачи по ссылке с помощью Python
- Заключение
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Переход по ссылке в Python: Лучшие практики
После некоторого знакомства с Python вы можете заметить случаи, когда ваши функции не изменяют аргументы на месте, как вы могли бы ожидать, особенно если вы знакомы с другими языками программирования. Некоторые языки обрабатывают аргументы функции как ссылки на существующие переменные, что называется передачей по ссылке. Другие языки обрабатывают их как независимых значений, подход, известный как передача по значению.
Если вы программист среднего уровня на Python и хотите понять специфический способ обработки аргументов функций в Python, то это руководство для вас. Вы реализуете реальные варианты использования конструкций с передачей по ссылке в Python и изучите несколько рекомендаций, позволяющих избежать ошибок с аргументами вашей функции.
В этом уроке вы узнаете:
- Что значит передавать по ссылке и почему вы хотели бы это сделать
- Чем передача по ссылке отличается от передачи по значению и Уникального подхода Python
- Как аргументы функции ведут себя в Python
- Как вы можете использовать определенные изменяемые типы для передачи по ссылке в Python
- Каковы рекомендации по репликации передачи по ссылке в Python
Бесплатный бонус: 5 Размышления о мастерстве владения Python - бесплатный курс для разработчиков Python, который показывает вам план действий и мышление, с которым вы будете работать. вам нужно поднять свои навыки работы с Python на новый уровень.
Определение передачи по ссылке
Прежде чем вы углубитесь в технические детали передачи по ссылке, полезно более подробно рассмотреть сам термин, разбив его на составляющие:
- Передать означает предоставить аргумент функции.
- По ссылке означает, что аргумент, который вы передаете функции, является ссылкой на переменную, которая уже существует в памяти, а не независимой копией этой переменной.
Поскольку вы даете функции ссылку на существующую переменную, все операции, выполняемые с этой ссылкой, будут напрямую влиять на переменную, на которую она ссылается. Давайте рассмотрим несколько примеров того, как это работает на практике.
Ниже вы увидите, как передавать переменные по ссылке в C#. Обратите внимание на использование ключевого слова ref в выделенных строках:
using System; // Source: // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters class Program { static void Main(string[] args) { int arg; // Passing by reference. // The value of arg in Main is changed. arg = 4; squareRef(ref arg); Console.WriteLine(arg); // Output: 16 } static void squareRef(ref int refParameter) { refParameter *= refParameter; } }предварительно> кодовый блок>Как вы можете видеть,
refParameterизsquareRef()должно быть объявлено с помощью ключевого словаref, и вы также должны использовать это ключевое слово при вызове функции. Тогда аргумент будет передан по ссылке и может быть изменен на месте.В Python нет ключевого слова
refили чего-либо эквивалентного ему. Если вы попытаетесь максимально точно воспроизвести приведенный выше пример в Python, то увидите другие результаты:>>> def main(): ... arg = 4 ... square(arg) ... print(arg) ... >>> def square(n): ... n *= n ... >>> main() 4предварительно> кодовый блок>В этом случае переменная
argявляется , а не измененной на месте. Похоже, что Python рассматривает предоставленный вами аргумент как отдельное значение, а не как ссылку на существующую переменную. Означает ли это, что Python передает аргументы по значению, а не по ссылке?Не совсем. Python передает аргументы не по ссылке и не по значению, а по присваиванию. Ниже вы быстро ознакомитесь с деталями передачи по значению и по ссылке, прежде чем более подробно рассмотреть подход Python. После этого вы ознакомитесь с некоторыми рекомендациями для достижения эквивалента передачи по ссылке в Python.
Противопоставление передачи по ссылке и передачи по значению
Когда вы передаете аргументы функции по ссылке, эти аргументы являются только ссылками на существующие значения. В отличие от этого, когда вы передаете аргументы по значению, эти аргументы становятся независимыми копиями исходных значений.
Давайте вернемся к примеру на C#, на этот раз без использования ключевого слова
ref. Это приведет к тому, что программа будет использовать стандартное поведение передачи по значению:using System; // Source: // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters class Program { static void Main(string[] args) { int arg; // Passing by value. // The value of arg in Main is not changed. arg = 4; squareVal(arg); Console.WriteLine(arg); // Output: 4 } static void squareVal(int valParameter) { valParameter *= valParameter; } }предварительно> кодовый блок>Здесь вы можете видеть, что
squareVal()не изменяет исходную переменную. Скорее,valParameterявляется независимой копией исходной переменнойarg. Хотя это соответствует поведению, которое вы могли бы наблюдать в Python, помните, что Python не передает данные точно по значению. Давайте докажем это.Встроенный в Python
id()возвращает целое число, представляющее адрес памяти нужного объекта. Используяid(), вы можете проверить следующие утверждения:
- Аргументы функции изначально ссылаются на тот же адрес, что и их исходные переменные.
- Переназначение аргумента внутри функции присваивает ей новый адрес, в то время как исходная переменная остается неизмененной.
В приведенном ниже примере обратите внимание, что адрес
xизначально совпадает с адресомn, но изменяется после переназначения, в то время как адресnникогда не меняется:>>> def main(): ... n = 9001 ... print(f"Initial address of n: {id(n)}") ... increment(n) ... print(f" Final address of n: {id(n)}") ... >>> def increment(x): ... print(f"Initial address of x: {id(x)}") ... x += 1 ... print(f" Final address of x: {id(x)}") ... >>> main() Initial address of n: 140562586057840 Initial address of x: 140562586057840 Final address of x: 140562586057968 Final address of n: 140562586057840предварительно> кодовый блок>Тот факт, что начальные адреса
nиxсовпадают при вызовеincrement(), доказывает, что аргументxпередается не по значению. В противном случаеnиxимели бы разные адреса памяти.Прежде чем вы узнаете подробности о том, как Python обрабатывает аргументы, давайте рассмотрим некоторые практические примеры использования передачи по ссылке.
Использование конструкций передачи по ссылкам
Передача переменных по ссылке - это одна из нескольких стратегий, которые вы можете использовать для реализации определенных шаблонов программирования. Хотя это редко бывает необходимо, передача по ссылке может быть полезным инструментом.
В этом разделе вы рассмотрите три наиболее распространенных шаблона, для которых передача по ссылке является практичным подходом. Затем вы увидите, как можно реализовать каждый из этих шаблонов с помощью Python.
Как избежать дублирования объектов
Как вы уже видели, передача переменной по значению приведет к созданию копии этого значения и сохранению ее в памяти. В языках, в которых по умолчанию используется передача по значению, вы можете получить преимущества в производительности, если будете передавать переменную по ссылке, особенно если переменная содержит много данных. Это будет более очевидно, когда ваш код выполняется на компьютерах с ограниченными ресурсами.
В Python, однако, это никогда не является проблемой. Вы увидите почему в следующем разделе..
Возвращает несколько значений
Одним из наиболее распространенных применений передачи по ссылке является создание функции, которая изменяет значение ссылочных параметров, возвращая при этом другое значение. Вы можете изменить свой пример передачи по ссылке на C#, чтобы проиллюстрировать этот метод:
using System; class Program { static void Main(string[] args) { int counter = 0; // Passing by reference. // The value of counter in Main is changed. Console.WriteLine(greet("Alice", ref counter)); Console.WriteLine("Counter is {0}", counter); Console.WriteLine(greet("Bob", ref counter)); Console.WriteLine("Counter is {0}", counter); // Output: // Hi, Alice! // Counter is 1 // Hi, Bob! // Counter is 2 } static string greet(string name, ref int counter) { string greeting = "Hi, " + name + "!"; counter++; return greeting; } }предварительно> кодовый блок>В приведенном выше примере
greet()возвращает строку приветствия , а также изменяет значениеcounter. Теперь попытайтесь воспроизвести это как можно точнее на Python:>>> def main(): ... counter = 0 ... print(greet("Alice", counter)) ... print(f"Counter is {counter}") ... print(greet("Bob", counter)) ... print(f"Counter is {counter}") ... >>> def greet(name, counter): ... counter += 1 ... return f"Hi, {name}!" ... >>> main() Hi, Alice! Counter is 0 Hi, Bob! Counter is 0предварительно> кодовый блок>
counterв приведенном выше примере значение не увеличивается, потому что, как вы уже знали, в Python нет возможности передавать значения по ссылке. Итак, как вы можете достичь того же результата, что и с C#?По сути, ссылочные параметры в C# позволяют функции не только возвращать значение, но и оперировать дополнительными параметрами. Это эквивалентно возврату нескольких значений!
К счастью, Python уже поддерживает возврат нескольких значений. Строго говоря, функция Python, которая возвращает несколько значений, на самом деле возвращает кортеж, содержащий каждое значение:
>>> def multiple_return(): ... return 1, 2 ... >>> t = multiple_return() >>> t # A tuple (1, 2) >>> # You can unpack the tuple into two variables: >>> x, y = multiple_return() >>> x 1 >>> y 2предварительно> кодовый блок>Как вы можете видеть, чтобы вернуть несколько значений, вы можете просто использовать
returnключевое слово, за которым следуют значения или переменные, разделенные запятыми.Вооружившись этим приемом, вы можете изменить оператор
returnвgreet()из вашего предыдущего кода на Python, чтобы он возвращал как приветствие, так и счетчик:>>> def main(): ... counter = 0 ... print(greet("Alice", counter)) ... print(f"Counter is {counter}") ... print(greet("Bob", counter)) ... print(f"Counter is {counter}") ... >>> def greet(name, counter): ... return f"Hi, {name}!", counter + 1 ... >>> main() ('Hi, Alice!', 1) Counter is 0 ('Hi, Bob!', 1) Counter is 0предварительно> кодовый блок>Это по-прежнему выглядит неправильно. Хотя
greet()теперь возвращает несколько значений, они выводятся какtuple, что не входит в ваши намерения. Кроме того, исходная переменнаяcounterостается на прежнем уровне.0.Чтобы очистить ваши выходные данные и получить желаемые результаты, вам придется переназначать вашу переменную
counterпри каждом вызовеgreet():>>> def main(): ... counter = 0 ... greeting, counter = greet("Alice", counter) ... print(f"{greeting}\nCounter is {counter}") ... greeting, counter = greet("Bob", counter) ... print(f"{greeting}\nCounter is {counter}") ... >>> def greet(name, counter): ... return f"Hi, {name}!", counter + 1 ... >>> main() Hi, Alice! Counter is 1 Hi, Bob! Counter is 2предварительно> кодовый блок>Теперь, после переназначения каждой переменной с помощью вызова
greet(), вы можете увидеть желаемые результаты!Присвоение возвращаемых значений переменным - лучший способ достичь тех же результатов, что и передача по ссылке в Python. Вы узнаете почему, а также о некоторых дополнительных методах в разделе, посвященном рекомендациям..
Создание условных функций с множественным возвратом
Это конкретный вариант использования, при котором функция может быть использована в условном операторе и имеет дополнительные побочные эффекты, такие как изменение внешней переменной, которая была передана в качестве аргумента.
Рассмотрим стандарт Int32.Функция TryParse в C#, которая возвращает логическое значение и одновременно оперирует ссылкой на целочисленный аргумент:
public static bool TryParse (string s, out int result);предварительно> кодовый блок>Эта функция пытается преобразовать
stringв 32-разрядное целое число со знаком, используя ключевое словоout. Есть два возможных результата:
- Если синтаксический анализ завершится успешно, то выходному параметру будет присвоено результирующее целое число, и функция вернет
true.- Если синтаксический анализ завершится неудачей, то выходному параметру будет присвоено значение
0, и функция вернет значениеfalse.Вы можете увидеть это на практике в следующем примере, в котором делается попытка преобразовать несколько различных строк:
using System; // Source: // https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=netcore-3.1#System_Int32_TryParse_System_String_System_Int32__ public class Example { public static void Main() { String[] values = { null, "160519", "9432.0", "16,667", " -322 ", "+4302", "(100);", "01FA" }; foreach (var value in values) { int number; if (Int32.TryParse(value, out number)) { Console.WriteLine("Converted '{0}' to {1}.", value, number); } else { Console.WriteLine("Attempted conversion of '{0}' failed.", value ?? "<null>"); } } } }предварительно> кодовый блок>Приведенный выше код, который пытается преобразовать строки другого формата в целые числа с помощью
TryParse(), выдает следующее:Attempted conversion of '<null>' failed. Converted '160519' to 160519. Attempted conversion of '9432.0' failed. Attempted conversion of '16,667' failed. Converted ' -322 ' to -322. Converted '+4302' to 4302. Attempted conversion of '(100);' failed. Attempted conversion of '01FA' failed.предварительно> кодовый блок>Чтобы реализовать аналогичную функцию в Python, вы могли бы использовать несколько возвращаемых значений, как вы видели ранее:
def tryparse(string, base=10): try: return True, int(string, base=base) except ValueError: return False, Noneпредварительно> кодовый блок>Это
tryparse()возвращает два значения. Первое значение указывает, было ли преобразование успешным, а второе содержит результат (илиNone, в случае сбоя).Однако использование этой функции немного затруднительно, поскольку вам нужно распаковывать возвращаемые значения при каждом вызове. Это означает, что вы не можете использовать функции внутри
ifзаявление:>>> success, result = tryparse("123") >>> success True >>> result 123 >>> # We can make the check work >>> # by accessing the first element of the returned tuple, >>> # but there's no way to reassign the second element to `result`: >>> if tryparse("456")[0]: ... print(result) ... 123предварительно> кодовый блок>Несмотря на то, что обычно это работает, возвращая несколько значений,
tryparse()нельзя использовать при проверке условий. Это означает, что вам предстоит еще кое-что сделать.Вы можете воспользоваться гибкостью Python и упростить функцию, чтобы возвращать одно значение разных типов в зависимости от того, успешно ли выполнено преобразование:
def tryparse(string, base=10): try: return int(string, base=base) except ValueError: return Noneпредварительно> кодовый блок>Благодаря возможности функций Python возвращать различные типы данных, теперь вы можете использовать эту функцию в рамках условного оператора. Но как? Разве вам не пришлось бы сначала вызвать функцию, присвоив ей возвращаемое значение, а затем проверить само значение?
Используя преимущества гибкости Python в отношении типов объектов, а также новые выражения присваивания в Python 3.8, вы можете вызвать эту упрощенную функцию с помощью условного оператора
ifи получают возвращаемое значение, если проверка прошла успешно:>>> if (n := tryparse("123")) is not None: ... print(n) ... 123 >>> if (n := tryparse("abc")) is None: ... print(n) ... None >>> # You can even do arithmetic! >>> 10 * tryparse("10") 100 >>> # All the functionality of int() is available: >>> 10 * tryparse("0a", base=16) 100 >>> # You can also embed the check within the arithmetic expression! >>> 10 * (n if (n := tryparse("123")) is not None else 1) 1230 >>> 10 * (n if (n := tryparse("abc")) is not None else 1) 10предварительно> кодовый блок>Ничего себе! Эта версия Python для
tryparse()еще более мощная, чем версия C#, что позволяет использовать ее в условных операторах и арифметических выражениях.Проявив немного изобретательности, вы воспроизвели конкретный и полезный шаблон передачи по ссылке, фактически не передавая аргументы по ссылке. Фактически, вы снова присваиваете возвращаемые значения при использовании оператора выражения присваивания(
:=) и используете возвращаемое значение непосредственно в выражениях Python.Итак, вы узнали, что означает передача по ссылке, чем она отличается от передачи по значению и чем подход Python отличается от обоих подходов. Теперь вы готовы поближе познакомиться с тем, как Python обрабатывает аргументы функций!
Передача аргументов в Python
Python передает аргументы путем присваивания. То есть, когда вы вызываете функцию Python, каждый аргумент функции становится переменной, которой присваивается переданное значение.
Таким образом, вы можете узнать важные подробности о том, как Python обрабатывает аргументы функций, поняв, как работает сам механизм присваивания, даже за пределами функций.
Понимание присваивания в Python
Справочник по языку Python для операторов присваивания содержит следующие сведения:
- Если целью присвоения является идентификатор или имя переменной, то это имя привязывается к объекту. Например, в
x = 2,x- это имя, а2- объект.- Если имя уже привязано к отдельному объекту, то оно повторно привязывается к новому объекту. Например, если
xуже равно2и вы вводитеx = 3, то имя переменнойxбудет изменено на3.Все объекты Python реализованы в определенной структуре. Одним из свойств этой структуры является счетчик, который отслеживает, сколько имен было привязано к этому объекту.
Примечание: Этот счетчик называется счетчик ссылок, поскольку он отслеживает, сколько ссылок или имен указывают на один и тот же объект . Не путайте счетчик ссылок с концепцией передачи по ссылке, поскольку они никак не связаны.
Документация по Python содержит дополнительные сведения о количестве ссылок .
Давайте придерживаться примера
x = 2и рассмотрим, что происходит, когда вы присваиваете значение новой переменной:
- Если объект, представляющий значение
2, уже существует, то он извлекается. В противном случае он создается.- Счетчик ссылок этого объекта увеличивается.
- В текущее пространство имен добавлена запись, чтобы привязать идентификатор
xк объекту, представляющему2. Эта запись на самом деле представляет собой пару ключ-значение, хранящуюся в словаре! Представление этого словаря возвращается с помощьюlocals()илиglobals().Теперь вот что произойдет, если вы присвоите
xдругому значению:
- Счетчик ссылок объекта, представляющего
2, уменьшается.- Счетчик ссылок объекта, представляющего новое значение, увеличивается.
- Словарь для текущего пространства имен обновляется, чтобы связать
xс объектом, представляющим новое значение.Python позволяет получать количество ссылок для произвольных значений с помощью функции
sys.getrefcount(). Вы можете использовать ее, чтобы проиллюстрировать, как присвоение увеличивает и уменьшает количество ссылок. Обратите внимание, что интерактивный интерпретатор использует поведение, которое приведет к другим результатам, поэтому вам следует запустить следующий код из файла:from sys import getrefcount print("--- Before assignment ---") print(f"References to value_1: {getrefcount('value_1')}") print(f"References to value_2: {getrefcount('value_2')}") x = "value_1" print("--- After assignment ---") print(f"References to value_1: {getrefcount('value_1')}") print(f"References to value_2: {getrefcount('value_2')}") x = "value_2" print("--- After reassignment ---") print(f"References to value_1: {getrefcount('value_1')}") print(f"References to value_2: {getrefcount('value_2')}")предварительно> кодовый блок>Этот скрипт покажет количество ссылок для каждого значения до присвоения, после присвоения и после переназначения:
--- Before assignment --- References to value_1: 3 References to value_2: 3 --- After assignment --- References to value_1: 4 References to value_2: 3 --- After reassignment --- References to value_1: 3 References to value_2: 4предварительно> кодовый блок>Эти результаты иллюстрируют взаимосвязь между идентификаторами (именами переменных) и объектами Python, которые представляют различные значения. Когда вы присваиваете нескольким переменным одно и то же значение, Python увеличивает счетчик ссылок для существующего объекта и обновляет текущее пространство имен, а не создает повторяющиеся объекты в памяти.
В следующем разделе вы будете опираться на свое текущее понимание операций присваивания, изучая, как Python обрабатывает аргументы функций.
Изучение аргументов функции
Аргументами функции в Python являются локальные переменные. Что это значит? Локальная - это одна из областей Python. Эти области представлены словарями пространств имен, упомянутыми в предыдущем разделе. Вы можете использовать
locals()иglobals()для получения локального и глобального словарей пространств имен соответственно.При выполнении каждая функция имеет свое собственное локальное пространство имен:
>>> def show_locals(): ... my_local = True ... print(locals()) ... >>> show_locals() {'my_local': True}предварительно> кодовый блок>Используя
locals(), вы можете продемонстрировать, что аргументы функции становятся обычными переменными в локальном пространстве имен функции. Давайте добавим к функции аргументmy_arg:>>> def show_locals(my_arg): ... my_local = True ... print(locals()) ... >>> show_locals("arg_value") {'my_arg': 'arg_value', 'my_local': True}предварительно> кодовый блок>Вы также можете использовать
sys.getrefcount(), чтобы показать, как аргументы функции увеличивают счетчик ссылок для объекта:>>> from sys import getrefcount >>> def show_refcount(my_arg): ... return getrefcount(my_arg) ... >>> getrefcount("my_value") 3 >>> show_refcount("my_value") 5предварительно> кодовый блок>Приведенный выше скрипт выводит количество ссылок для
"my_value"сначала снаружи, затем внутриshow_refcount(), показывая увеличение количества ссылок не на одну, а на две!Это потому, что в дополнение к самому
show_refcount()вызовsys.getrefcount()внутриshow_refcount()также получаетmy_argв качестве аргумента. Это помещаетmy_argв локальное пространство имен дляsys.getrefcount(), добавляя дополнительную ссылку на"my_value".Изучив пространства имен и количество ссылок внутри функций, вы можете увидеть, что аргументы функции работают точно так же, как присваивания: Python создает привязки в локальном пространстве имен функции между идентификаторами и объектами Python, которые представляют значения аргументов. Каждая из этих привязок увеличивает счетчик ссылок на объект.
Теперь вы можете видеть, как Python передает аргументы путем присваивания!
Реплицирующий проход по ссылке с помощью Python
Изучив пространства имен в предыдущем разделе, вы, возможно, спросите, почему
globalне упоминается как один из способов изменения переменных, как если бы они передавались по ссылке:>>> def square(): ... # Not recommended! ... global n ... n *= n ... >>> n = 4 >>> square() >>> n 16предварительно> кодовый блок>Использование инструкции
globalобычно снижает ясность вашего кода. Это может привести к ряду проблем, включая следующие:
- Свободные переменные, казалось бы, ни с чем не связанные
- Функции без явных аргументов для указанных переменных
- Функции, которые нельзя использовать в общем случае с другими переменными или аргументами, поскольку они полагаются на одну глобальную переменную
- Отсутствие потокобезопасности при использовании глобальных переменных
Сравните предыдущий пример со следующим, который явно возвращает значение:
>>> def square(n): ... return n * n ... >>> square(4) 16предварительно> кодовый блок>Намного лучше! Вы избегаете всех потенциальных проблем с глобальными переменными и, требуя аргумент, делаете вашу функцию более понятной.
Несмотря на то, что Python не является ни языком с передачей ссылок, ни языком с передачей значений, у него нет недостатков в этом отношении. Его гибкость более чем удовлетворяет поставленной задаче.
Наилучшая практика: Возврат и переназначение
Вы уже говорили о том, как возвращать значения из функции и переназначать их переменной. Для функций, которые работают с одним значением, возвращать значение намного проще, чем использовать ссылку. Кроме того, поскольку Python уже использует указатели за кулисами, не было бы никаких дополнительных преимуществ в производительности, даже если бы он мог передавать аргументы по ссылке.
Цель - написать одноцелевые функции, которые возвращают одно значение, а затем (повторно) присваивают это значение переменным, как в следующем примере:
def square(n): # Accept an argument, return a value. return n * n x = 4 ... # Later, reassign the return value: x = square(x)предварительно> кодовый блок>Возвращаемые и присваиваемые значения также делают ваши намерения более явными, а ваш код более понятным и тестируемым.
Для функций, которые работают с несколькими значениями, вы уже видели, что Python способен возвращать кортеж значений. Вы превзошли даже элегантность Int32.TryParse() в C# благодаря гибкости Python!
Если вам нужно работать с несколькими значениями, вы можете написать универсальные функции, которые возвращают несколько значений, а затем (повторно) присваивать эти значения переменным. Вот пример:
def greet(name, counter): # Return multiple values return f"Hi, {name}!", counter + 1 counter = 0 ... # Later, reassign each return value by unpacking. greeting, counter = greet("Alice", counter)предварительно> кодовый блок>При вызове функции, возвращающей несколько значений, вы можете назначить несколько переменных одновременно.
Рекомендуемая практика: Используйте атрибуты объектов
Атрибуты объектов занимают свое собственное место в стратегии присвоения Python. В языковом справочнике Python для операторов присваивания указано, что если целью является атрибут объекта, который поддерживает присваивание, то объекту будет предложено выполнить присваивание для этого атрибута. Если вы передаете объект в качестве аргумента функции, то его атрибуты могут быть изменены на месте.
Напишите функции, которые принимают объекты с атрибутами, а затем работают непосредственно с этими атрибутами, как в следующем примере:
>>> # For the purpose of this example, let's use SimpleNamespace. >>> from types import SimpleNamespace >>> # SimpleNamespace allows us to set arbitrary attributes. >>> # It is an explicit, handy replacement for "class X: pass". >>> ns = SimpleNamespace() >>> # Define a function to operate on an object's attribute. >>> def square(instance): ... instance.n *= instance.n ... >>> ns.n = 4 >>> square(ns) >>> ns.n 16предварительно> кодовый блок>Обратите внимание, что
square()необходимо записать для работы непосредственно с атрибутом, который будет изменен без необходимости переназначать возвращаемое значение.Стоит повторить, что вы должны убедиться, что атрибут поддерживает присвоение! Вот тот же пример с
namedtuple, атрибуты которого доступны только для чтения:>>> from collections import namedtuple >>> NS = namedtuple("NS", "n") >>> def square(instance): ... instance.n *= instance.n ... >>> ns = NS(4) >>> ns.n 4 >>> square(ns) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in square AttributeError: can't set attributeпредварительно> кодовый блок>Попытки изменить атрибуты, которые не допускают изменения, приводят к
AttributeError.Кроме того, вам следует помнить о атрибутах класса. Они останутся неизменными, а атрибут экземпляра будет создан и изменен:
>>> class NS: ... n = 4 ... >>> ns = NS() >>> def square(instance): ... instance.n *= instance.n ... >>> ns.n 4 >>> square(ns) >>> # Instance attribute is modified. >>> ns.n 16 >>> # Class attribute remains unchanged. >>> NS.n 4предварительно> кодовый блок>Поскольку атрибуты класса остаются неизменными при изменении с помощью экземпляра класса, вам нужно помнить о необходимости ссылки на атрибут экземпляра.
Рекомендуемая практика: Используйте словари и списки
Словари в Python представляют собой объектный тип, отличный от всех других встроенных типов. Они называются типами отображения. Документация Python по типам отображения дает некоторое представление об этом термине:
A сопоставление объектов с хэшируемыми значениями произвольным объектам. Сопоставления - это изменяемые объекты. В настоящее время существует только один стандартный тип отображения - словарь. (Источник)
В этом руководстве не рассматривается, как реализовать пользовательский тип отображения, но вы можете воспроизвести передачу по ссылке, используя словарь humble. Вот пример использования функции, которая работает непосредственно с элементами словаря:
>>> # Dictionaries are mapping types. >>> mt = {"n": 4} >>> # Define a function to operate on a key: >>> def square(num_dict): ... num_dict["n"] *= num_dict["n"] ... >>> square(mt) >>> mt {'n': 16}предварительно> кодовый блок>Поскольку вы переназначаете значение ключу словаря, работа с элементами словаря по-прежнему является формой присвоения. Со словарями вы получаете дополнительную практичность доступа к измененному значению через тот же объект словаря.
Хотя списки не являются сопоставляемыми типами, вы можете использовать их аналогично словарям благодаря двум важным характеристикам: возможность подписки и изменяемость. Эти характеристики заслуживают более подробного объяснения, но давайте сначала рассмотрим рекомендации по имитации передачи по ссылке с использованием списков Python.
Чтобы воспроизвести передачу по ссылке с использованием списков, напишите функцию, которая работает непосредственно с элементами списка:
>>> # Lists are both subscriptable and mutable. >>> sm = [4] >>> # Define a function to operate on an index: >>> def square(num_list): ... num_list[0] *= num_list[0] ... >>> square(sm) >>> sm [16]предварительно> кодовый блок>Поскольку вы переназначаете значение элементу в списке, работа с элементами списка по-прежнему является формой присвоения. Подобно словарям, списки позволяют получить доступ к измененному значению через тот же объект списка.
Теперь давайте рассмотрим возможность подписки. Объект является доступным для подписки, когда к подмножеству его структуры можно получить доступ по позициям индекса:
>>> subscriptable = [0, 1, 2] # A list >>> subscriptable[0] 0 >>> subscriptable = (0, 1, 2) # A tuple >>> subscriptable[0] 0 >>> subscriptable = "012" # A string >>> subscriptable[0] '0' >>> not_subscriptable = {0, 1, 2} # A set >>> not_subscriptable[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'set' object is not subscriptableпредварительно> кодовый блок>Списки, кортежи и строки могут быть подписаны, а наборы - нет. Попытка получить доступ к элементу объекта, который не является подписываемым, приведет к возникновению проблемы.
TypeError.Изменчивость - это более широкая тема, требующая дополнительного изучения и ссылки на документацию. Короче говоря, объект является изменяемым, если его структура может быть изменена на месте, а не требует переназначения:
>>> mutable = [0, 1, 2] # A list >>> mutable[0] = "x" >>> mutable ['x', 1, 2] >>> not_mutable = (0, 1, 2) # A tuple >>> not_mutable[0] = "x" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> not_mutable = "012" # A string >>> not_mutable[0] = "x" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment >>> mutable = {0, 1, 2} # A set >>> mutable.remove(0) >>> mutable.add("x") >>> mutable {1, 2, 'x'}предварительно> кодовый блок>Списки и наборы могут изменяться, как и словари и другие типы сопоставлений. Строки и кортежи не могут изменяться. Попытка изменить элемент неизменяемого объекта приведет к возникновению ошибки.
TypeError.Заключение
Python работает иначе, чем языки, поддерживающие передачу аргументов по ссылке или по значению. Аргументы функции становятся локальными переменными, присваиваемыми каждому значению, которое было передано функции. Но это не мешает вам достичь тех же результатов, которые вы ожидали бы при передаче аргументов по ссылке в других языках.
В этом уроке вы узнали:
- Как Python обрабатывает присвоение значений переменным
- Как аргументы функции передаются путем присваивания в Python
- Почему возвращаемые значения лучше всего воспроизводить по ссылке
- Как использовать атрибуты, словари и списки в качестве альтернативы рекомендации
Вы также изучили некоторые дополнительные рекомендации по воспроизведению конструкций с передачей по ссылке в Python. Вы можете использовать эти знания для реализации шаблонов, которые традиционно требовали поддержки передачи по ссылке.
Чтобы продолжить знакомство с Python, я рекомендую вам углубиться в некоторые связанные темы, с которыми вы столкнулись здесь, такие как изменчивость, выражения присваивания и Пространства имен и область применения Python.
Оставайтесь любопытными, и увидимся в следующий раз!
<статус завершения article-slug="python-pass-by-reference" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/статьи/python-pass-by-reference/bookmark/" data-api-url-адрес статуса завершения статьи="/api/v1/articles/python-pass-by-reference/completion_status/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0APass по ссылке в Python: Справочная информация и рекомендации" email-subject="Статья о Python для вас" twitter-text="Интересная #Статья о Python от @realpython:" url="https://realpython.com/python-pass-by-reference /" url-title="Передача по ссылке в Python: общие сведения и рекомендации"> кнопка поделиться>Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Переход по ссылке в Python: Лучшие практики
Back to Top