Pular para o conteúdo

Como usar Python para passagem de parâmetros por referência?

CodeMDD.io

Pass by Reference em Python: Conceito e Melhores Práticas

por Marius Mogyorosi

Após adquirir familiaridade com a linguagem Python, você pode perceber que suas funções não modificam os argumentos como você esperaria, especialmente se estiver familiarizado com outras linguagens de programação. Algumas linguagens tratam os argumentos de função como referências a variáveis existentes, o que é conhecido como passagem por referência. Outras linguagens os tratam como valores independentes, uma abordagem conhecida como passagem por valor.

Se você é um programador Python intermediário que deseja entender a maneira peculiar como o Python lida com argumentos de função, este tutorial é para você. Você implementará casos de uso reais de construções de passagem por referência em Python e aprenderá várias melhores práticas para evitar armadilhas com seus argumentos de função.

Neste tutorial, você aprenderá:

  • O que significa passar por referência e por que você faria isso
  • Como a passagem por referência difere tanto da passagem por valor quanto da abordagem única do Python
  • Como os argumentos de função se comportam no Python
  • Como você pode usar certos tipos mutáveis para passar por referência em Python
  • Quais são as melhores práticas para replicar a passagem por referência em Python

Bônus gratuito: 5 Pensamentos sobre o domínio do Python, um curso gratuito para desenvolvedores Python que mostra o caminho e a mentalidade que você precisará para levar suas habilidades em Python para o próximo nível.

Definindo Passagem por Referência

Antes de entrar nos detalhes técnicos da passagem por referência, é útil dar uma olhada mais de perto no termo em si, dividindo-o em componentes:

  • Pass significa fornecer um argumento para uma função.
  • Por referência significa que o argumento que você está passando para a função é uma referência a uma variável que já existe na memória, e não uma cópia independente dessa variável.

Como você está dando à função uma referência a uma variável existente, todas as operações realizadas nesta referência afetarão diretamente a variável à qual ela se refere. Vamos ver alguns exemplos de como isso funciona na prática.

Abaixo, você verá como passar variáveis por referência em C#. Observe o uso da palavra-chave ref nas linhas destacadas:

using System;
class Program
{
static void Main(string[] args)
{
int arg;
https://codemdd.io/ Passando por referência.
https://codemdd.io/ O valor 42 é atribuído a arg, então a função Test altera o valor de arg
arg = 42;
Test(ref arg);
https://codemdd.io/ Exibindo o valor de arg após chamada da função
Console.WriteLine(arg); https://codemdd.io/ Output: 84
}
static void Test(ref int value)
{
value = value * 2;
}
}

No código acima, estamos passando a variável arg por referência para a função Test. Dentro da função, estamos multiplicando o valor por 2. Como estamos passando a referência da variável, qualquer alteração feita na referência terá impacto direto na variável original. No final, o valor de arg é alterado para 84.

Essa é uma abordagem comum em várias linguagens de programação, mas e quanto ao Python? O Python possui uma abordagem única para a passagem de argumentos de função.

Contraste de Passagem por Referência e Passagem por Valor

No Python, os argumentos de função são passados por valor, mas com uma torção. O valor que está sendo passado é a referência para o objeto, e não o próprio objeto. Isso pode parecer confuso, mas vamos ver um exemplo para entender melhor.

def test(value):
value = value * 2
arg = 42
test(arg)
print(arg) # Output: 42

Ao contrário do exemplo em C#, o valor de arg não é alterado após a chamada da função test. Isso ocorre porque o valor que está sendo passado é uma referência ao objeto int, e não o próprio objeto. Quando atribuímos o novo valor a value dentro da função, estamos criando uma nova referência, que não afeta a variável original arg.

Esse comportamento é chamado de passagem por valor porque a referência é copiada ao passar o valor para a função. No entanto, o objeto referenciado permanece o mesmo. Portanto, embora não seja uma passagem por referência “tradicional” como em outras linguagens, ainda podemos alcançar resultados semelhantes em Python usando construções específicas.

Usando Construções de Passagem por Referência

É possível simular a passagem por referência em Python usando tipos mutáveis, como listas e dicionários. Como esses tipos são passados por referência, podemos realizar operações neles e ter as alterações refletidas fora da função. Vamos explorar algumas construções comuns de passagem por referência em Python.

Evitando Objetos Duplicados

Em algumas situações, é útil evitar a duplicação de objetos ao passá-los como argumentos de função. Isso pode ocorrer quando o objeto é grande em termos de uso de memória ou quando a duplicação do objeto é desnecessária. Nesses casos, podemos usar a passagem por referência para evitar a criação de um novo objeto.

Um exemplo é quando uma função precisa modificar uma lista recebida como argumento, em vez de criar uma cópia dela. Veja o código abaixo:

def modify_list(lst):
lst.append(4)
my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 4]

Neste exemplo, a função modify_list recebe a lista my_list como argumento e, em seguida, acrescenta o valor 4 a ela. Como as listas são passadas por referência, qualquer alteração feita na lista dentro da função será refletida fora dela. Portanto, ao imprimir my_list após a chamada da função, vemos que o valor 4 foi adicionado à lista.

Essa construção é útil quando você precisa modificar um objeto mutável dentro de uma função e deseja evitar a criação de uma cópia do objeto.

Retornando Múltiplos Valores

Outro caso em que a passagem por referência é útil é quando queremos retornar múltiplos valores de uma função. Em Python, não é possível retornar múltiplos valores diretamente como em algumas outras linguagens. No entanto, podemos contornar essa limitação usando a passagem por referência.

Uma maneira de fazer isso é retornando uma lista ou tupla contendo os valores que queremos retornar. A partir do momento em que a lista ou tupla é passada como argumento e modificada dentro da função, as alterações serão visíveis fora dela.

def return_multiple_values(lst):
lst.append(4)
return lst
my_list = [1, 2, 3]
my_list = return_multiple_values(my_list)
print(my_list) # Output: [1, 2, 3, 4]

Neste exemplo, a função return_multiple_values recebe a lista my_list como argumento, adiciona o valor 4 a ela e retorna a lista modificada. Ao atribuir o resultado da função de volta a my_list, temos acesso aos valores modificados. Portanto, ao imprimir my_list, vemos que o valor 4 foi adicionado à lista.

Essa abordagem é útil quando você precisa retornar vários valores de uma função e deseja evitar o uso de estruturas de dados mais complexas, como objetos personalizados ou dicionários.

Criando Funções com Múltiplos Retornos Condicionalmente

Além de retornar múltiplos valores, também podemos criar funções que retornam diferentes valores condicionalmente, dependendo dos argumentos fornecidos. Isso é útil quando você deseja realizar diferentes cálculos ou retornar diferentes resultados com base em condições específicas.

Podemos alcançar isso retornando múltiplos valores usando a passagem por referência.

def conditional_return(value1, value2):
if value1 > value2:
return value1, value2
else:
return value2, value1
larger, smaller = conditional_return(5, 3)
print(larger) # Output: 5
print(smaller) # Output: 3

Neste exemplo, a função conditional_return recebe dois valores como argumentos e verifica qual valor é maior. Dependendo do resultado da comparação, a função retorna os valores em uma ordem específica. No momento em que a função é chamada e os valores são atribuídos a larger e smaller, eles refletem os valores retornados pela função.

Essa construção é útil quando você precisa retornar diferentes resultados condicionalmente e deseja evitar o uso de estruturas de controle mais complexas, como blocos if-else.

Passagem de Argumentos em Python

Para entender completamente como funcionam os argumentos de função em Python, é importante entender a atribuição em Python e como ela difere de outras linguagens.

Compreendendo Atribuição em Python

Em Python, atribuir um valor a uma variável significa criar uma referência para esse valor. Isso é diferente de atribuição em outras linguagens, onde o valor pode ser copiado para a variável.

Quando uma variável é passada como argumento para uma função em Python, uma nova referência para o mesmo objeto é criada. Isso significa que a variável original e a nova referência apontam para o mesmo objeto na memória.

def modify(value):
value = 10
x = 5
modify(x)
print(x) # Output: 5

Neste exemplo, o valor de x não é alterado após a chamada da função modify. Isso ocorre porque, dentro da função, estamos criando uma nova referência chamada value e atribuindo o valor 10 a ela. Essa operação não afeta a variável original x, porque ela ainda está apontando para o valor original 5 na memória.

Explorando os Argumentos de Função

Em Python, os argumentos de função são passados como referências aos objetos que eles representam. Isso inclui tanto os objetos imutáveis quanto os objetos mutáveis. No entanto:

  • Se um objeto imutável for passado como argumento e uma reatribuição for feita dentro da função, uma nova referência é criada, mas a variável original permanece inalterada fora da função.
  • Se um objeto mutável for passado como argumento e alterado dentro da função, as alterações serão refletidas fora da função, porque objetos mutáveis são passados por referência.

Essa distinção é importante ao lidar com argumentos de função em Python e ao entender como as alterações em argumentos são tratadas.

Replicando a Passagem por Referência com Python

Embora o Python não ofereça suporte direto à passagem por referência, é possível replicar alguns dos comportamentos utilizando construções específicas. Aqui estão algumas práticas recomendadas para replicar a passagem por referência em Python:

  1. Retornar e reatribuir: Em vez de modificar diretamente um argumento, você pode retornar o resultado da função e reatribuí-lo à variável original.
  2. Usar atributos de objeto: Se o argumento for um objeto mutável, você pode modificar seus atributos diretamente dentro da função.
  3. Usar dicionários e listas: Dicionários e listas são objetos mutáveis em Python e podem ser usados para passar por referência.

Vamos explorar essas práticas recomendadas em exemplos práticos.

Melhor Prática: Retornar e Reatribuir

Uma das maneiras mais simples de replicar a passagem por referência é retornar o resultado de uma função e reatribuí-lo à variável original. Isso nos permite modificar objetos mutáveis sem alterar diretamente os argumentos da função.

def modify_list(lst):
lst.append(4)
return lst
my_list = [1, 2, 3]
my_list = modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 4]

Neste exemplo, a função modify_list recebe a lista my_list como argumento, acrescenta o valor 4 a ela e retorna a lista modificada. Ao atribuir o resultado da função de volta a my_list, a modificação é feita sem modificar diretamente o argumento da função.

Essa é uma prática comum em Python quando queremos modificar objetos mutáveis passados como argumentos de função.

Melhor Prática: Usar Atributos de Objeto

Outra prática recomendada é modificar os atributos de um objeto mutável diretamente dentro da função. Isso é útil quando queremos evitar o retorno do objeto e as reatribuições.

class Person:
def __init__(self, name):
self.name = name
def modify_person(person):
person.name = "John Doe"
p = Person("Jane Doe")
modify_person(p)
print(p.name) # Output: "John Doe"

Neste exemplo, a classe Person tem um atributo name. Na função modify_person, modificamos diretamente o atributo name do objeto person. As alterações são refletidas diretamente no objeto original, sem a necessidade de retorná-lo e reatribuí-lo.

Essa abordagem é útil quando queremos modificar um objeto mutável sem a necessidade de retorná-lo e reatribuí-lo.

Melhor Prática: Usar Dicionários e Listas

Uma terceira prática recomendada é usar dicionários e listas, que são objetos mutáveis em Python, para permitir a passagem por referência.

def modify_dict(dictionary):
dictionary["key"] = "value"
my_dict = {"key": "original"}
modify_dict(my_dict)
print(my_dict) # Output: {"key": "value"}

Nesse exemplo, a função modify_dict recebe um dicionário e modifica um de seus valores. Como os dicionários são objetos mutáveis, as alterações são refletidas diretamente no dicionário original.

Essa construção é útil quando precisamos modificar um objeto mutável e queremos evitar retornos e reatribuições.

Conclusão

Embora o Python não ofereça suporte direto à passagem por referência, é possível replicar alguns de seus comportamentos usando construções específicas. Neste tutorial, você aprendeu sobre a passagem por referência em Python, a diferença entre passagem por referência e passagem por valor e explorou várias construções para passar argumentos por referência em Python.

Lembre-se de usar as práticas recomendadas apresentadas neste tutorial ao trabalhar com argumentos de função em Python. Isso ajudará você a evitar comportamentos inesperados e a aproveitar ao máximo a manipulação de objetos mutáveis dentro de funções.