Los argumentos se pasan por asignación . La razón detrás de esto es doble:
- el parámetro pasado es en realidad una referencia a un objeto (pero la referencia se pasa por valor)
- algunos tipos de datos son mutables, pero otros no
Entonces:
Si pasa un objeto mutable a un método, el método obtiene una referencia a ese mismo objeto y puede mutarlo para deleite de su corazón, pero si vuelve a vincular la referencia en el método, el alcance externo no sabrá nada al respecto, y después Una vez hecho, la referencia externa seguirá apuntando al objeto original.
Si pasa un objeto inmutable a un método, aún no puede volver a vincular la referencia externa, y ni siquiera puede mutar el objeto.
Para hacerlo aún más claro, tengamos algunos ejemplos.
Lista: un tipo mutable
Intentemos modificar la lista que se pasó a un 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)
Salida:
before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']
Dado que el parámetro pasado es una referencia outer_list
, no una copia, podemos usar los métodos de la lista de mutaciones para cambiarlo y hacer que los cambios se reflejen en el alcance externo.
Ahora veamos qué sucede cuando intentamos cambiar la referencia que se pasó 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)
Salida:
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 el the_list
parámetro se pasó por valor, asignarle una nueva lista no tuvo ningún efecto que el código fuera del método pudiera ver. El the_list
era una copia de la outer_list
referencia, y tuvimos que the_list
apuntan a una nueva lista, pero no había manera de cambiar el lugar donde outer_list
puntiagudo.
Cadena: un tipo inmutable
Es inmutable, por lo que no hay nada que podamos hacer para cambiar el contenido de la cadena.
Ahora, intentemos cambiar la referencia
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)
Salida:
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
Nuevamente, dado que el the_string
parámetro se pasó por valor, asignarle una nueva cadena no tuvo ningún efecto que el código fuera del método pudiera ver. El the_string
era una copia de la outer_string
referencia, y tuvimos que the_string
apuntan a una nueva cadena, pero no había manera de cambiar el lugar donde outer_string
puntiagudo.
Espero que esto aclare un poco las cosas.
EDITAR: Se ha observado que esto no responde a la pregunta que @David originalmente preguntó: "¿Hay algo que pueda hacer para pasar la variable por referencia real?". Trabajemos en eso.
¿Cómo lo solucionamos?
Como muestra la respuesta de @ Andrea, puede devolver el nuevo valor. Esto no cambia la forma en que se pasan las cosas, pero le permite recuperar la información que desea:
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)
Si realmente quisiera evitar el uso de un valor de retorno, podría crear una clase para mantener su valor y pasarlo a la función o usar una clase existente, como una 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])
Aunque esto parece un poco engorroso.