Como passo uma variável por referência?


2628

A documentação do Python parece pouco clara sobre se os parâmetros são passados ​​por referência ou valor, e o código a seguir produz o valor inalterado 'Original'

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Existe algo que eu possa fazer para passar a variável por referência real?


23
Para uma breve explicação / esclarecimento, consulte a primeira resposta a esta pergunta do stackoverflow . Como as strings são imutáveis, elas não serão alteradas e uma nova variável será criada; portanto, a variável "externa" ainda tem o mesmo valor.
PHILS

10
O código na resposta de BlairConrad é bom, mas a explicação fornecida por DavidCournapeau e DarenThomas está correta.
Ethan Furman

70
Antes de ler a resposta selecionada, considere ler este texto breve Outras línguas têm "variáveis", Python tem "nomes" . Pense em "nomes" e "objetos" em vez de "variáveis" e "referências" e evite muitos problemas semelhantes.
Lqc

24
Por que isso desnecessariamente usa uma classe? Não é Java: P
Peter R

2
outra solução alternativa é criar uma 'referência' do invólucro assim: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
robert

Respostas:


2831

Os argumentos são passados ​​por atribuição . A lógica por trás disso é dupla:

  1. o parâmetro passado é realmente uma referência a um objeto (mas a referência é passada por valor)
  2. 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_listparâ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_listera uma cópia da outer_listreferência, e tivemos the_listapontam para uma nova lista, mas não havia nenhuma maneira de mudar o local onde outer_listpontiagudo.

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_stringparâ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_stringera uma cópia da outer_stringreferência, e tivemos the_stringapontam para uma nova seqüência, mas não havia nenhuma maneira de mudar o local onde outer_stringpontiagudo.

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.


165
Em seguida, o mesmo está em C, quando você passa "por referência" você está passando realmente por valor de referência ... Definir "por referência": P
Andrea Ambu

104
Não sei se entendi seus termos. Estou fora do jogo C há algum tempo, mas quando estava nele, não havia "passagem por referência" - você podia passar coisas, e sempre passava por valor, portanto, o que estava na lista de parâmetros foi copiado. Mas às vezes a coisa era um ponteiro, que se podia seguir para a parte da memória (primitiva, matriz, estrutura, o que fosse), mas você não podia alterar o ponteiro que foi copiado do escopo externo - quando você terminava a função , o ponteiro original ainda apontava para o mesmo endereço. C ++ introduziu referências, que se comportaram de maneira diferente.
Blair Conrad

34
@ Zac Bowling Eu realmente não entendo como o que você está dizendo é relevante, no sentido prático, para esta resposta. Se um iniciante em Python quiser saber sobre a passagem por ref / val, a resposta desta resposta é: 1- Você pode usar a referência que uma função recebe como argumento para modificar o valor 'externo' de uma variável, desde que pois você não reatribui o parâmetro para se referir a um novo objeto. 2- Atribuir a um tipo imutável sempre criará um novo objeto, que quebra a referência que você teve à variável externa.
Cam Jackson

11
@CamJackson, você precisa de um exemplo melhor - os números também são objetos imutáveis ​​em Python. Além disso, não seria verdade dizer que qualquer tarefa sem assinatura no lado esquerdo dos iguais atribuirá o nome a um novo objeto, seja imutável ou não? def Foo(alist): alist = [1,2,3]irá não modificar o conteúdo de uma lista a partir da perspectiva chamadores.
Mark Ransom

59
-1. O código mostrado é bom, a explicação de como está completamente errado. Veja as respostas de DavidCournapeau ou DarenThomas para explicações corretas sobre o porquê.
Ethan Furman

685

O problema vem de um mal-entendido de quais variáveis ​​existem no Python. Se você está acostumado com a maioria dos idiomas tradicionais, possui um modelo mental do que acontece na seguinte sequência:

a = 1
a = 2

Você acredita que esse aé um local de memória que armazena o valor 1e é atualizado para armazenar o valor 2. Não é assim que as coisas funcionam no Python. Em vez disso, acomeça como uma referência a um objeto com o valor 1e depois é reatribuído como uma referência a um objeto com o valor 2. Esses dois objetos podem continuar a coexistir, embora anão se refiram mais ao primeiro; de fato, eles podem ser compartilhados por qualquer número de outras referências dentro do programa.

Quando você chama uma função com um parâmetro, é criada uma nova referência que se refere ao objeto transmitido. Isso é separado da referência que foi usada na chamada de função, portanto, não há como atualizar essa referência e fazê-la se referir a um parâmetro. novo objeto. No seu exemplo:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variableé uma referência ao objeto de sequência 'Original'. Quando você chama, Changecria uma segunda referência varao objeto. Dentro da função, você reatribui a referência vara um objeto de sequência diferente 'Changed', mas a referência self.variableé separada e não muda.

A única maneira de contornar isso é passar um objeto mutável. Como as duas referências se referem ao mesmo objeto, quaisquer alterações no objeto são refletidas nos dois lugares.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

106
Boa explicação sucinta. Seu parágrafo "Quando você chama uma função ..." é uma das melhores explicações que ouvi sobre a frase bastante enigmática de que 'parâmetros de função Python são referências, passadas por valor'. Eu acho que se você entender esse parágrafo sozinho, todo o resto meio que faz sentido e flui como uma conclusão lógica a partir daí. Então você só precisa estar ciente quando estiver criando um novo objeto e ao modificar um existente.
cam

3
Mas como você pode reatribuir a referência? Eu pensei que você não pudesse alterar o endereço de 'var', mas que sua string "Changed" agora seria armazenada no endereço de memória 'var'. Sua descrição faz parecer que "Alterado" e "Original" pertencem a diferentes lugares na memória e você apenas alterna 'var' para um endereço diferente. Isso está correto?
Glassjawed

9
@ Glassjawed, acho que você está entendendo. "Changed" e "Original" são dois objetos de string diferentes em diferentes endereços de memória e 'var' muda de um ponto para outro.
Mark Ransom

o uso da função id () ajuda a esclarecer as questões, porque fica claro quando o Python cria um novo objeto (pelo menos é o que penso).
Tim Timsonson

1
@MinhTran nos termos mais simples, uma referência é algo que "se refere" a um objeto. A representação física disso é provavelmente um ponteiro, mas isso é simplesmente um detalhe de implementação. É realmente uma noção abstrata no coração.
Mark Ransom

329

Eu achei as outras respostas bastante longas e complicadas, então criei este diagrama simples para explicar como o Python trata variáveis ​​e parâmetros. insira a descrição da imagem aqui


2
adorável, facilita identificar a diferença sutil de que existe uma tarefa intermediária, não óbvia para um espectador casual. 1
user22866

9
Não importa se A é mutável ou não. Se você atribuir algo diferente a B, A não muda . Se um objeto é mutável, você pode modificá-lo, com certeza. Mas isso não tem nada a ver com a atribuição direta a um nome ..
Martijn Pieters

1
@ Martijn Você está certo. Tirei a parte da resposta que menciona mutabilidade. Eu não acho que pode ficar mais simples agora.
Zenadix 25/05

4
Obrigado pela atualização, muito melhor! O que confunde a maioria das pessoas é a atribuição a uma assinatura; por exemplo B[0] = 2, vs. atribuição direta B = 2,.
Martijn Pieters

11
"A é atribuído a B." Isso não é ambíguo? Eu acho que no inglês comum isso pode significar um A=Bou outro B=A.
24516 Hatshepsut

240

Não é passagem por valor ou passagem por referência - é chamada por objeto. Veja isto, de Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

Aqui está uma citação significativa:

"... variáveis ​​[nomes] não são objetos; elas não podem ser indicadas por outras variáveis ​​ou referidas por objetos."

No seu exemplo, quando o Changemétodo é chamado - um espaço para nome é criado para ele; e varse torna um nome, dentro desse espaço de nome, para o objeto string 'Original'. Esse objeto, em seguida, tem um nome em dois espaços para nome. Em seguida, var = 'Changed'vincula var- se a um novo objeto de sequência e, portanto, o espaço de nome do método esquece 'Original'. Finalmente, esse espaço para nome é esquecido e a string 'Changed'junto com ele.


21
Acho difícil comprar. Para mim, assim como Java, os parâmetros são ponteiros para objetos na memória, e esses ponteiros são passados ​​pela pilha ou registradores.
Luciano

10
Isto não é como java. Um dos casos em que não é o mesmo são objetos imutáveis. Pense na função trivial lambda x: x. Aplique isso para x = [1, 2, 3] e x = (1, 2, 3). No primeiro caso, o valor retornado será uma cópia da entrada e idêntico no segundo caso.
David Cournapeau

26
Não, é exatamente como a semântica de Java para objetos. Não sei ao certo o que você quer dizer com "No primeiro caso, o valor retornado será uma cópia da entrada e idêntico no segundo caso". mas essa afirmação parece claramente incorreta.
Mike Graham

23
É exatamente o mesmo que em Java. Referências a objetos são passadas por valor. Quem pensa diferente deve anexar o código Python para uma swapfunção que pode trocar duas referências, como este: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann

24
É exatamente o mesmo que Java quando você passa objetos em Java. No entanto, Java também possui primitivas, que são passadas copiando o valor da primitiva. Assim, eles diferem nesse caso.
Claudiu

174

Pense nas coisas que estão sendo passadas por atribuição em vez de por referência / por valor. Dessa forma, é sempre claro o que está acontecendo, desde que você entenda o que acontece durante a tarefa normal.

Portanto, ao passar uma lista para uma função / método, a lista é atribuída ao nome do parâmetro. Anexar à lista resultará na modificação da lista. A reatribuição da lista dentro da função não alterará a lista original, pois:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Como tipos imutáveis ​​não podem ser modificados, eles parecem passados ​​por valor - passar um int para uma função significa atribuir o int ao parâmetro da função. Você só pode reatribuir isso, mas isso não altera o valor das variáveis ​​originais.


3
À primeira vista, essa resposta parece contornar a pergunta original. Depois de uma segunda leitura, percebi que isso deixa o assunto bem claro. Um bom acompanhamento a este conceito "nome de atribuição" pode ser encontrada aqui: O código gosta de uma Pythonista: Idiomatic Python
Christian Groleau

65

Effbot (aka Fredrik Lundh) descreveu o estilo de passagem variável do Python como chamada por objeto: http://effbot.org/zone/call-by-object.htm

Os objetos são alocados na pilha e os ponteiros para eles podem ser passados ​​para qualquer lugar.

  • Quando você faz uma atribuição como x = 1000, é criada uma entrada de dicionário que mapeia a cadeia "x" no espaço para nome atual para um ponteiro para o objeto inteiro que contém mil.

  • Quando você atualiza "x" com x = 2000, um novo objeto inteiro é criado e o dicionário é atualizado para apontar para o novo objeto. O objeto antigo de mil é inalterado (e pode ou não estar vivo, dependendo se algo mais se refere ao objeto).

  • Quando você executa uma nova atribuição, como y = x, é criada uma nova entrada de dicionário "y" que aponta para o mesmo objeto que a entrada de "x".

  • Objetos como strings e inteiros são imutáveis . Isso significa simplesmente que não há métodos que possam alterar o objeto depois que ele foi criado. Por exemplo, uma vez que o objeto inteiro mil seja criado, ele nunca será alterado. A matemática é feita criando novos objetos inteiros.

  • Objetos como listas são mutáveis . Isso significa que o conteúdo do objeto pode ser alterado por qualquer coisa que aponte para o objeto. Por exemplo, x = []; y = x; x.append(10); print yimprimirá [10]. A lista vazia foi criada. "X" e "y" apontam para a mesma lista. O método append modifica (atualiza) o objeto de lista (como adicionar um registro a um banco de dados) e o resultado é visível para "x" e "y" (assim como uma atualização de banco de dados seria visível para todas as conexões com esse banco de dados).

Espero que esclareça o problema para você.


2
Eu realmente gosto de aprender sobre isso com um desenvolvedor. É verdade que a id()função retorna o valor do ponteiro (referência do objeto), como sugere a resposta do pepr?
Honest Abe

4
@HonestAbe Sim, no CPython o id () retorna o endereço. Mas em outros python, como PyPy e Jython, o id () é apenas um identificador de objeto exclusivo.
Raymond Hettinger 14/01

59

Tecnicamente, o Python sempre usa valores de passagem por referência . Vou repetir minha outra resposta para apoiar minha declaração.

O Python sempre usa valores de passagem por referência. Não há nenhuma exceção. Qualquer atribuição de variável significa copiar o valor de referência. Sem exceção. Qualquer variável é o nome vinculado ao valor de referência. Sempre.

Você pode pensar em um valor de referência como o endereço do objeto de destino. O endereço é desreferenciado automaticamente quando usado. Dessa forma, trabalhando com o valor de referência, parece que você trabalha diretamente com o objeto de destino. Mas sempre há uma referência no meio, mais um passo para pular para o alvo.

Aqui está o exemplo que prova que o Python usa a passagem por referência:

Exemplo ilustrado de passar o argumento

Se o argumento foi passado por valor, o externo lstnão pôde ser modificado. O verde são os objetos de destino (o preto é o valor armazenado no interior, o vermelho é o tipo de objeto), o amarelo é a memória com o valor de referência interno - desenhado como a seta. A seta sólida azul é o valor de referência que foi passado para a função (através do caminho da seta azul tracejada). O feio amarelo escuro é o dicionário interno. (Na verdade, ele também pode ser desenhado como uma elipse verde. A cor e a forma apenas dizem que é interna.)

Você pode usar a id()função interna para saber qual é o valor de referência (ou seja, o endereço do objeto de destino).

Em linguagens compiladas, uma variável é um espaço de memória capaz de capturar o valor do tipo. No Python, uma variável é um nome (capturado internamente como uma string) vinculado à variável de referência que mantém o valor de referência no objeto de destino. O nome da variável é a chave no dicionário interno, a parte do valor desse item de dicionário armazena o valor de referência no destino.

Os valores de referência estão ocultos no Python. Não há nenhum tipo explícito de usuário para armazenar o valor de referência. No entanto, você pode usar um elemento de lista (ou elemento em qualquer outro tipo de contêiner adequado) como a variável de referência, porque todos os contêineres armazenam os elementos também como referências aos objetos de destino. Em outras palavras, os elementos não estão realmente contidos no contêiner - apenas as referências aos elementos.


1
Na verdade, isso é confirmado por seu valor de referência. +1 para esta resposta, embora o exemplo não tenha sido bom.
BugShotGG

30
Inventar uma nova terminologia (como "passar por valor de referência" ou "chamar por objeto" não é útil). "Chamar por (valor | referência | nome)" são termos padrão. "referência" é um termo padrão. A passagem de referências por valor descreve com precisão o comportamento do Python, Java e um host de outras linguagens, usando terminologia padrão.
cayhorstmann

4
@cayhorstmann: O problema é que a variável Python não tem o mesmo significado terminológico que em outras línguas. Dessa forma, a chamada por referência não se encaixa bem aqui. Além disso, como você define exatamente o termo referência ? Informalmente, a maneira Python pode ser facilmente descrita como passando o endereço do objeto. Mas não se encaixa com uma implementação potencialmente distribuída do Python.
21812 pepr #

1
Gosto dessa resposta, mas você pode considerar se o exemplo está realmente ajudando ou prejudicando o fluxo. Além disso, se você substituiu 'valor de referência' com 'referência de objeto' você estaria usando terminologia que poderíamos considerar 'oficial', como visto aqui: Funções Definindo
Honest Abe

2
Há uma nota de rodapé indicada no final dessa citação, que diz: "Na verdade, chamar por referência a objeto seria uma descrição melhor, pois se um objeto mutável for passado, o chamador verá as alterações que o destinatário fizer nele ... " Concordo com você que a confusão é causada pela tentativa de ajustar a terminologia estabelecida com outros idiomas. Semântica à parte, as coisas que precisam ser entendidas são: dicionários / namespaces, operações de ligação de nomes e a relação de nome → ponteiro → objeto (como você já sabe).
Honest Abe

56

Não há variáveis ​​no Python

A chave para entender a passagem de parâmetros é parar de pensar em "variáveis". Existem nomes e objetos em Python e juntos eles parecem variáveis, mas é útil sempre distinguir os três.

  1. Python tem nomes e objetos.
  2. A atribuição vincula um nome a um objeto.
  3. Passar um argumento para uma função também vincula um nome (o nome do parâmetro da função) a um objeto.

Isso é tudo o que existe. A mutabilidade é irrelevante para esta questão.

Exemplo:

a = 1

Isso vincula o nome aa um objeto do tipo número inteiro que contém o valor 1.

b = x

Isso vincula o nome bao mesmo objeto ao qual o nome xestá atualmente vinculado. Depois, o nome bnão tem mais nada a ver com o nome x.

Consulte as seções 3.1 e 4.2 na referência da linguagem Python 3.

Como ler o exemplo na pergunta

No código mostrado na pergunta, a instrução self.Change(self.variable)vincula o nome var(no escopo da função Change) ao objeto que contém o valor 'Original'e a atribuição var = 'Changed'(no corpo da função Change) atribui o mesmo nome novamente: a algum outro objeto (o que acontece segurar uma corda também, mas poderia ter sido algo totalmente diferente).

Como passar por referência

Portanto, se o que você deseja alterar é um objeto mutável, não há problema, pois tudo é efetivamente passado por referência.

Se for um objeto imutável (por exemplo, um bool, número, string), o caminho a seguir é envolvê-lo em um objeto mutável.
A solução rápida e suja para isso é uma lista de um elemento (em vez de self.variablepassar [self.variable]e modificar a função var[0]).
A abordagem mais pitônica seria a introdução de uma classe trivial de um atributo. A função recebe uma instância da classe e manipula o atributo.


20
"Python não tem variáveis" é um slogan bobo e confuso, e eu realmente gostaria que as pessoas parassem de dizer isso ... :( O restante desta resposta é boa!
Ned Batchelder

9
Pode ser chocante, mas não é bobo. E também não acho confuso: espero que abra a mente do destinatário para a explicação que está por vir e a coloque em uma atitude útil "Gostaria de saber o que eles têm em vez de variáveis". (Sim, sua milhagem pode variar.)
Lutz Prechelt

13
você também diria que o Javascript não tem variáveis? Eles funcionam da mesma forma que o Python. Além disso, Java, Ruby, PHP, .... Eu acho que uma técnica de ensino melhor é: "As variáveis ​​do Python funcionam de maneira diferente das do C."
Ned Batchelder

9
Sim, Java tem variáveis. O mesmo acontece com Python, JavaScript, Ruby, PHP etc. Você não diria em Java que intdeclara uma variável, mas Integernão o faz. Ambos declaram variáveis. A Integervariável é um objeto, a intvariável é uma primitiva. Como exemplo, você demonstrou como suas variáveis ​​funcionam mostrando a = 1; b = a; a++ # doesn't modify b. Isso também é verdade no Python (usando += 1como não existe ++no Python)!
Ned Batchelder

1
O conceito de "variável" é complexo e geralmente vago: uma variável é um contêiner para um valor identificado por um nome. No Python, os valores são objetos, os contêineres são objetos (vê o problema?) E os nomes são realmente coisas separadas. Eu acredito que é muito mais difícil obter uma compreensão precisa das variáveis ​​dessa maneira. A explicação de nomes e objetos parece mais difícil, mas na verdade é mais simples.
Lutz Prechelt

43

Um truque simples que normalmente uso é envolvê-lo em uma lista:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Sim, eu sei que isso pode ser inconveniente, mas às vezes é simples o suficiente para fazer isso.)


7
+1 para uma pequena quantidade de texto, fornecendo a solução essencial para o problema de o Python não ter passagem por referência. (Como um comentário / pergunta subsequente que se encaixa aqui e em qualquer lugar nesta página: Não está claro para o meu porque o python não pode fornecer uma palavra-chave "ref", como o C #, que simplesmente envolve o argumento do chamador em uma lista como este, e tratar as referências ao parâmetro dentro da função como elemento de 0 a lista).
M Katz

5
Agradável. Para passar por ref, envolva [] 's.
Justas

38

(editar - Blair atualizou sua resposta enormemente popular para que agora seja precisa)

Penso que é importante observar que a publicação atual com mais votos (de Blair Conrad), embora correta em relação ao resultado, é enganosa e limítrofe incorreta com base em suas definições. Embora existam muitas linguagens (como C) que permitem ao usuário passar por referência ou passar por valor, o Python não é um deles.

A resposta de David Cournapeau aponta para a resposta real e explica por que o comportamento no post de Blair Conrad parece estar correto, enquanto as definições não são.

Na medida em que o Python é transmitido por valor, todas as linguagens são transmitidas por valor, pois alguns dados (seja um "valor" ou uma "referência") devem ser enviados. No entanto, isso não significa que o Python seja transmitido por valor no sentido em que um programador C pensaria nisso.

Se você quer o comportamento, a resposta de Blair Conrad é boa. Mas se você deseja conhecer os detalhes do porquê o Python não passa por valor ou por referência, leia a resposta de David Cournapeau.


4
Simplesmente não é verdade que todos os idiomas sejam chamados por valor. Em C ++ ou Pascal (e certamente muitos outros que eu não conheço), você chama por referência. Por exemplo, em C ++, void swap(int& x, int& y) { int temp = x; x = y; y = temp; }trocará as variáveis ​​passadas para ele. Em Pascal, você usa em varvez de &.
Cayhorstmann

2
Pensei ter respondido a isso há muito tempo, mas não o vejo. Para ser completo - cayhorstmann não entendeu minha resposta. Eu não estava dizendo que tudo é chamado de valor nos termos que a maioria das pessoas aprende sobre C / C ++ . Simplesmente foi passado algum valor (valor, nome, ponteiro etc.) e que os termos usados ​​na resposta original de Blair eram imprecisos.
precisa saber é o seguinte

24

Você tem respostas muito boas aqui.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )

2
sim, no entanto, se você fizer x = [2, 4, 4, 5, 5], y = x, X [0] = 1, imprimir x # [1, 4, 4, 5, 5] imprimir y # [1 , 4, 4, 5, 5]
laycat

19

Nesse caso, a variável intitulada varno método Changerecebe uma referência self.variablee você atribui imediatamente uma string a var. Não está mais apontando para self.variable. O seguinte trecho de código mostra o que aconteceria se você modificar a estrutura de dados apontada por vare self.variable, nesse caso, uma lista:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

Tenho certeza que alguém poderia esclarecer isso ainda mais.


18

O esquema de passagem por atribuição do Python não é exatamente o mesmo que a opção de parâmetros de referência do C ++, mas acaba sendo muito semelhante ao modelo de passagem de argumentos da linguagem C (e outros) na prática:

  • Argumentos imutáveis ​​são efetivamente passados ​​" por valor ". Objetos como números inteiros e seqüências de caracteres são passados ​​por referência a objeto, e não por cópia, mas como você não pode alterar objetos imutáveis ​​de qualquer maneira, o efeito é como fazer uma cópia.
  • Argumentos mutáveis ​​são efetivamente passados ​​" por ponteiro ". Objetos como listas e dicionários também são passados ​​por referência a objetos, semelhante à maneira como C passa matrizes como ponteiros - objetos mutáveis ​​podem ser alterados no lugar na função, assim como matrizes C.

16

Como você pode indicar, precisa ter um objeto mutável, mas deixe-me sugerir que você verifique as variáveis ​​globais, pois elas podem ajudá-lo ou até mesmo resolver esse tipo de problema!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

exemplo:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2

5
Fiquei tentado a postar uma resposta semelhante - o questionador original pode não saber que o que ele queria era de fato usar uma variável global, compartilhada entre funções. Aqui está o link que eu teria compartilhado: stackoverflow.com/questions/423379/… Em resposta ao @Tim, o Stack Overflow não é apenas um site de perguntas e respostas, é um vasto repositório de conhecimento de referência que só fica mais forte e com mais nuances. como um wiki- ativo com mais entrada.
Max P Magee

13

Muitas idéias são respondidas aqui, mas acho que um ponto adicional não é claramente mencionado aqui explicitamente. Citando a partir da documentação python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"No Python, variáveis ​​que são referenciadas apenas dentro de uma função são implicitamente globais. Se uma variável recebe um novo valor em qualquer lugar do corpo da função, é assumido como local. Se uma variável receber um novo valor dentro da função, a variável é implicitamente local e é necessário declará-la explicitamente como "global". Embora um pouco surpreendente a princípio, uma consideração momentânea explica isso: por um lado, exigir global para variáveis ​​atribuídas fornece uma barreira contra efeitos colaterais indesejados. por outro lado, se global fosse necessário para todas as referências globais, você estaria usando global o tempo todo.Você teria que declarar global todas as referências a uma função interna ou a um componente de um módulo importado. derrotaria a utilidade da declaração global para identificar efeitos colaterais ".

Mesmo ao passar um objeto mutável para uma função, isso ainda se aplica. E, para mim, explica claramente a razão da diferença de comportamento entre atribuir ao objeto e operar no objeto na função.

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

dá:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

A atribuição a uma variável global que não é declarada global, cria um novo objeto local e interrompe o vínculo com o objeto original.


10

Aqui está a explicação simples (espero) do conceito pass by objectusado no Python.
Sempre que você passa um objeto para a função, o próprio objeto é passado (o objeto no Python é na verdade o que você chamaria de valor em outras linguagens de programação), não a referência a esse objeto. Em outras palavras, quando você liga para:

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

O objeto atual - [0, 1] (que seria chamado de valor em outras linguagens de programação) está sendo passado. Então, de fato, a função change_metentará fazer algo como:

[0, 1] = [1, 2, 3]

que obviamente não mudará o objeto passado para a função. Se a função estivesse assim:

def change_me(list):
   list.append(2)

A chamada resultaria em:

[0, 1].append(2)

o que obviamente mudará o objeto. Esta resposta explica bem.


2
O problema é que a tarefa faz algo além do que você espera. As list = [1, 2, 3]causas reutilizam o listnome para outra coisa e esquecem o objeto passado originalmente. No entanto, você pode tentar list[:] = [1, 2, 3](a propósito, o listnome está errado para uma variável. Pensar [0, 1] = [1, 2, 3]é um absurdo completo. De qualquer forma, o que você acha que significa que o próprio objeto é passado ? O que é copiado para a função na sua opinião?
pepr

Objetos @pepr não são literais. Eles são objetos. A única maneira de falar sobre eles é dar-lhes alguns nomes. É por isso que é tão simples quando você o compreende, mas é extremamente complicado de explicar. :-)
Veky

@ Veky: Estou ciente disso. De qualquer forma, o literal da lista é convertido no objeto de lista. Na verdade, qualquer objeto no Python pode existir sem um nome e pode ser usado mesmo quando não recebe nenhum nome. E você pode pensar neles como objetos anônimos. Pense nos objetos sendo os elementos de uma lista. Eles não precisam de um nome. Você pode acessá-los através da indexação ou da iteração na lista. Enfim, eu insisto [0, 1] = [1, 2, 3]é simplesmente um mau exemplo. Não há nada parecido em Python.
pepr 12/05

@pepr: Não quero dizer necessariamente nomes de definição de Python, apenas nomes comuns. É claro que alist[2]conta como o nome de um terceiro elemento da lista. Mas acho que não entendi qual era o seu problema. :-)
Veky

Argh. Meu inglês é obviamente muito pior que meu Python. :-) Vou tentar apenas mais uma vez. Eu apenas disse que você tem que dar alguns nomes ao objeto apenas para falar sobre eles. Com esses "nomes", não quis dizer "nomes como definidos pelo Python". Eu sei mecanismos Python, não se preocupe.
Veky

8

Além de todas as excelentes explicações sobre como essas coisas funcionam no Python, não vejo uma sugestão simples para o problema. Como você parece criar objetos e instâncias, a maneira pitônica de lidar com variáveis ​​de instância e alterá-las é a seguinte:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.Change()
        print self.variable

    def Change(self):
        self.variable = 'Changed'

Nos métodos de instância, você normalmente se refere selfao acesso aos atributos da instância. É normal definir atributos de instância __init__e ler ou alterá-los nos métodos de instância. É também por isso que você passa selfo primeiro argumento para def Change.

Outra solução seria criar um método estático como este:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.variable = PassByReference.Change(self.variable)
        print self.variable

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var

6

Há um pequeno truque para passar um objeto por referência, mesmo que a linguagem não o torne possível. Também funciona em Java, é a lista com um item. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

É um truque feio, mas funciona. ;-P


pé uma referência a um objeto de lista mutável que, por sua vez, armazena o objeto obj. A referência 'p' é passada para changeRef. No interior changeRef, uma nova referência é criada (a nova referência é chamada ref) que aponta para o mesmo objeto de lista que paponta para. Mas como as listas são mutáveis, as alterações na lista são visíveis pelas duas referências. Nesse caso, você usou a refreferência para alterar o objeto no índice 0, para que ele posteriormente armazene o PassByReference('Michael')objeto. A alteração no objeto de lista foi feita, refmas essa alteração é visível para p.
Minh Tran

Portanto, agora, as referências pe refapontam para um objeto de lista que armazena o único objeto PassByReference('Michael'),. Então segue-se que p[0].nameretorna Michael. Obviamente, refagora ficou fora do escopo e pode ser coletado como lixo, mas mesmo assim.
Minh Tran

Porém, você não alterou a variável de instância privada name, do PassByReferenceobjeto original associado à referência obj. De fato, obj.nameretornará Peter. Os comentários acima assumem a definição Mark Ransomdada.
Minh Tran

A propósito, não concordo que seja um hack (o que entendo como referência a algo que funciona, mas por razões desconhecidas, não testadas ou não intencionais pelo implementador). Você simplesmente substituiu um PassByReferenceobjeto por outro PassByReferencena sua lista e se referiu ao último dos dois objetos.
Minh Tran

5

Usei o método a seguir para converter rapidamente alguns códigos do Fortran em Python. É verdade que não é passado por referência como a pergunta original foi colocada, mas é uma solução simples em alguns casos.

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c

Sim, isso também resolve o 'passe por referência' no meu caso de uso. Eu tenho uma função que basicamente limpa valores em um dicte, em seguida, retorna o dict. No entanto, durante a limpeza, pode se tornar aparente uma reconstrução de uma parte do sistema. Portanto, a função deve não apenas retornar os limpos, dictmas também ser capaz de sinalizar a reconstrução. Tentei passar um boolpor referência, mas ofc isso não funciona. Descobrindo como resolver isso, achei sua solução (basicamente retornando uma tupla) para funcionar melhor, além de não ser uma hack / solução alternativa (IMHO).
kasimir

3

Embora a passagem por referência não seja nada que se encaixe bem no python e deva ser raramente usada, existem algumas soluções alternativas que podem funcionar para obter o objeto atualmente atribuído a uma variável local ou até reatribuir uma variável local de dentro de uma função chamada.

A idéia básica é ter uma função que possa fazer esse acesso e possa ser passada como objeto para outras funções ou armazenada em uma classe.

Uma maneira é usar global(para variáveis ​​globais) ou nonlocal(para variáveis ​​locais em uma função) em uma função de wrapper.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

A mesma idéia funciona para ler e delescrever uma variável.

Para apenas ler, há ainda uma maneira mais curta de usar, lambda: xque retorna uma chamada que, quando chamada, retorna o valor atual de x. É um pouco como "chamar pelo nome" usado em idiomas no passado distante.

Passar 3 invólucros para acessar uma variável é um pouco complicado, para que eles possam ser agrupados em uma classe que possui um atributo de proxy:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

O suporte à "reflexão" do Pythons torna possível obter um objeto capaz de reatribuir um nome / variável em um determinado escopo sem definir funções explicitamente nesse escopo:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Aqui, a ByRefclasse envolve um acesso ao dicionário. Portanto, o acesso ao atributo wrappedé traduzido para um acesso ao item no dicionário passado. Ao passar o resultado do builtin localse o nome de uma variável local, isso acaba acessando uma variável local. A documentação do python a partir do 3.5 recomenda que alterar o dicionário pode não funcionar, mas parece funcionar para mim.


3

dada a maneira como o python lida com valores e referências a eles, a única maneira de referenciar um atributo de instância arbitrário é pelo nome:

class PassByReferenceIsh:
    def __init__(self):
        self.variable = 'Original'
        self.change('variable')
        print self.variable

    def change(self, var):
        self.__dict__[var] = 'Changed'

no código real, é claro, você adicionaria a verificação de erros na pesquisa de ditado.


3

Como os dicionários são passados ​​por referência, você pode usar uma variável dict para armazenar quaisquer valores referenciados dentro dela.

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value

3

A passagem por referência em Python é bem diferente do conceito de passagem por referência em C ++ / Java.

  • Java & C #: tipos primitivos (incluir cadeia de caracteres) passam por valor (cópia); o tipo de referência é passado por referência (cópia de endereço) para que todas as alterações feitas no parâmetro na função chamada sejam visíveis para o chamador.
  • C ++: passagem por referência ou passagem por valor são permitidas. Se um parâmetro for passado por referência, você poderá modificá-lo ou não, dependendo se o parâmetro foi passado como const ou não. No entanto, const ou não, o parâmetro mantém a referência ao objeto e a referência não pode ser atribuída para apontar para um objeto diferente na função chamada.
  • Python: Python é "referência por objeto a objeto", do qual se costuma dizer: "As referências a objetos são transmitidas por valor". [Leia aqui] 1. Tanto o chamador quanto a função se referem ao mesmo objeto, mas o parâmetro na função é uma nova variável que está apenas mantendo uma cópia do objeto no chamador. Como o C ++, um parâmetro pode ser modificado ou não funcionar - Isso depende do tipo de objeto passado. por exemplo; Um tipo de objeto imutável não pode ser modificado na função chamada, enquanto um objeto mutável pode ser atualizado ou reinicializado. Uma diferença crucial entre atualizar ou redesignar / reinicializar a variável mutável é que o valor atualizado é refletido de volta na função chamada, enquanto o valor reinicializado não. O escopo de qualquer atribuição de novo objeto a uma variável mutável é local para a função no python. Os exemplos fornecidos por @ blair-conrad são ótimos para entender isso.

1
Velho, mas me sinto obrigado a corrigi-lo. Cordas são passados por referência em Java e C #, e não por valor
John

2

Como seu exemplo é orientado a objetos, você pode fazer as seguintes alterações para obter um resultado semelhante:

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change('variable')
        print(self.variable)

    def change(self, var):
        setattr(self, var, 'Changed')

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'

1
Embora isso funcione. Não é passar por referência. É 'passagem por referência a objeto'.
Bishwas Mishra

1

Você pode simplesmente usar uma classe vazia como uma instância para armazenar objetos de referência, porque os atributos internos dos objetos são armazenados em um dicionário de instância. Veja o exemplo.

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)

1

Como parece não ser mencionado em nenhum lugar, uma abordagem para simular referências como conhecidas em, por exemplo, C ++, é usar uma função "update" e passar isso em vez da variável real (ou melhor, "nome"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Isso é útil principalmente para "referências somente de saída" ou em uma situação com vários threads / processos (tornando a função de atualização thread / multiprocessamento segura).

Obviamente o acima não permite ler o valor, apenas atualizá-lo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.