Os argumentos são passados por atribuição . A lógica por trás disso é dupla:
- o parâmetro passado é realmente uma referência a um objeto (mas a referência é passada por valor)
- alguns tipos de dados são mutáveis, mas outros não
Assim:
Se você passar um objeto mutável para um método, o método fará uma referência ao mesmo objeto e você poderá modificá-lo para o deleite do seu coração, mas se você religar a referência no método, o escopo externo não saberá mais nada sobre ele e depois pronto, a referência externa ainda apontará para o objeto original.
Se você passar um objeto imutável para um método, ainda não poderá religar a referência externa e nem mesmo modificar o objeto.
Para deixar ainda mais claro, vamos dar alguns exemplos.
Lista - um tipo mutável
Vamos tentar modificar a lista que foi passada para um método:
def try_to_change_list_contents(the_list):
print('got', the_list)
the_list.append('four')
print('changed to', the_list)
outer_list = ['one', 'two', 'three']
print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)
Resultado:
before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']
Como o parâmetro transmitido é uma referência outer_list
, e não uma cópia dele, podemos usar os métodos da lista de mutação para alterá-lo e ter as alterações refletidas no escopo externo.
Agora vamos ver o que acontece quando tentamos alterar a referência que foi passada como parâmetro:
def try_to_change_list_reference(the_list):
print('got', the_list)
the_list = ['and', 'we', 'can', 'not', 'lie']
print('set to', the_list)
outer_list = ['we', 'like', 'proper', 'English']
print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)
Resultado:
before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']
Como o the_list
parâmetro foi passado por valor, atribuir uma nova lista a ele não teve nenhum efeito que o código fora do método pudesse ver. A the_list
era uma cópia da outer_list
referência, e tivemos the_list
apontam para uma nova lista, mas não havia nenhuma maneira de mudar o local onde outer_list
pontiagudo.
String - um tipo imutável
É imutável, então não há nada que possamos fazer para alterar o conteúdo da string
Agora, vamos tentar mudar a referência
def try_to_change_string_reference(the_string):
print('got', the_string)
the_string = 'In a kingdom by the sea'
print('set to', the_string)
outer_string = 'It was many and many a year ago'
print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)
Resultado:
before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago
Novamente, como o the_string
parâmetro foi passado por valor, atribuir uma nova string a ele não teve nenhum efeito que o código fora do método pudesse ver. A the_string
era uma cópia da outer_string
referência, e tivemos the_string
apontam para uma nova seqüência, mas não havia nenhuma maneira de mudar o local onde outer_string
pontiagudo.
Espero que isso esclareça um pouco as coisas.
EDIT: Observou-se que isso não responde à pergunta que @David originalmente perguntou: "Existe algo que eu possa fazer para passar a variável por referência real?". Vamos trabalhar nisso.
Como contornar isso?
Como mostra a resposta de Andrea, você pode retornar o novo valor. Isso não muda a maneira como as coisas são transmitidas, mas permite que você obtenha as informações que deseja:
def return_a_whole_new_string(the_string):
new_string = something_to_do_with_the_old_string(the_string)
return new_string
# then you could call it like
my_string = return_a_whole_new_string(my_string)
Se você realmente deseja evitar o uso de um valor de retorno, pode criar uma classe para manter seu valor e passá-lo para a função ou usar uma classe existente, como uma lista:
def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
new_string = something_to_do_with_the_old_string(stuff_to_change[0])
stuff_to_change[0] = new_string
# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)
do_something_with(wrapper[0])
Embora isso pareça um pouco complicado.