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?
python
reference
parameter-passing
pass-by-reference
David Sykes
fonte
fonte
Respostas:
Os argumentos são passados por atribuição . A lógica por trás disso é dupla:
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:
Resultado:
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:
Resultado:
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. Athe_list
era uma cópia daouter_list
referência, e tivemosthe_list
apontam para uma nova lista, mas não havia nenhuma maneira de mudar o local ondeouter_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
Resultado:
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. Athe_string
era uma cópia daouter_string
referência, e tivemosthe_string
apontam para uma nova seqüência, mas não havia nenhuma maneira de mudar o local ondeouter_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:
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:
Embora isso pareça um pouco complicado.
fonte
def Foo(alist): alist = [1,2,3]
irá não modificar o conteúdo de uma lista a partir da perspectiva chamadores.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:
Você acredita que esse
a
é um local de memória que armazena o valor1
e é atualizado para armazenar o valor2
. Não é assim que as coisas funcionam no Python. Em vez disso,a
começa como uma referência a um objeto com o valor1
e depois é reatribuído como uma referência a um objeto com o valor2
. Esses dois objetos podem continuar a coexistir, emboraa
nã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:
self.variable
é uma referência ao objeto de sequência'Original'
. Quando você chama,Change
cria uma segunda referênciavar
ao objeto. Dentro da função, você reatribui a referênciavar
a um objeto de sequência diferente'Changed'
, mas a referênciaself.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.
fonte
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.
fonte
B[0] = 2
, vs. atribuição diretaB = 2
,.A=B
ou outroB=A
.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:
No seu exemplo, quando o
Change
método é chamado - um espaço para nome é criado para ele; evar
se 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'
vinculavar
- 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.fonte
swap
função que pode trocar duas referências, como este:a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
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:
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.
fonte
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 y
imprimirá[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ê.
fonte
id()
função retorna o valor do ponteiro (referência do objeto), como sugere a resposta do pepr?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:
Se o argumento foi passado por valor, o externo
lst
nã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.
fonte
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.
Isso é tudo o que existe. A mutabilidade é irrelevante para esta questão.
Exemplo:
Isso vincula o nome
a
a um objeto do tipo número inteiro que contém o valor 1.Isso vincula o nome
b
ao mesmo objeto ao qual o nomex
está atualmente vinculado. Depois, o nomeb
não tem mais nada a ver com o nomex
.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 nomevar
(no escopo da funçãoChange
) ao objeto que contém o valor'Original'
e a atribuiçãovar = 'Changed'
(no corpo da funçãoChange
) 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.variable
passar[self.variable]
e modificar a funçãovar[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.
fonte
int
declara uma variável, masInteger
não o faz. Ambos declaram variáveis. AInteger
variável é um objeto, aint
variável é uma primitiva. Como exemplo, você demonstrou como suas variáveis funcionam mostrandoa = 1; b = a; a++ # doesn't modify b
. Isso também é verdade no Python (usando+= 1
como não existe++
no Python)!Um truque simples que normalmente uso é envolvê-lo em uma lista:
(Sim, eu sei que isso pode ser inconveniente, mas às vezes é simples o suficiente para fazer isso.)
fonte
(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.
fonte
void swap(int& x, int& y) { int temp = x; x = y; y = temp; }
trocará as variáveis passadas para ele. Em Pascal, você usa emvar
vez de&
.Você tem respostas muito boas aqui.
fonte
Nesse caso, a variável intitulada
var
no métodoChange
recebe uma referênciaself.variable
e você atribui imediatamente uma string avar
. Não está mais apontando paraself.variable
. O seguinte trecho de código mostra o que aconteceria se você modificar a estrutura de dados apontada porvar
eself.variable
, nesse caso, uma lista:Tenho certeza que alguém poderia esclarecer isso ainda mais.
fonte
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:
fonte
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:
fonte
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.
dá:
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.
fonte
Aqui está a explicação simples (espero) do conceito
pass by object
usado 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:
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_me
tentará fazer algo como:que obviamente não mudará o objeto passado para a função. Se a função estivesse assim:
A chamada resultaria em:
o que obviamente mudará o objeto. Esta resposta explica bem.
fonte
list = [1, 2, 3]
causas reutilizam olist
nome para outra coisa e esquecem o objeto passado originalmente. No entanto, você pode tentarlist[:] = [1, 2, 3]
(a propósito, olist
nome 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?[0, 1] = [1, 2, 3]
é simplesmente um mau exemplo. Não há nada parecido em Python.alist[2]
conta como o nome de um terceiro elemento da lista. Mas acho que não entendi qual era o seu problema. :-)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:
Nos métodos de instância, você normalmente se refere
self
ao 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ê passaself
o primeiro argumento paradef Change
.Outra solução seria criar um método estático como este:
fonte
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. ;-)
É um truque feio, mas funciona. ;-P
fonte
p
é uma referência a um objeto de lista mutável que, por sua vez, armazena o objetoobj
. A referência 'p' é passada parachangeRef
. No interiorchangeRef
, uma nova referência é criada (a nova referência é chamadaref
) que aponta para o mesmo objeto de lista quep
aponta 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 aref
referência para alterar o objeto no índice 0, para que ele posteriormente armazene oPassByReference('Michael')
objeto. A alteração no objeto de lista foi feita,ref
mas essa alteração é visível parap
.p
eref
apontam para um objeto de lista que armazena o único objetoPassByReference('Michael')
,. Então segue-se quep[0].name
retornaMichael
. Obviamente,ref
agora ficou fora do escopo e pode ser coletado como lixo, mas mesmo assim.name
, doPassByReference
objeto original associado à referênciaobj
. De fato,obj.name
retornaráPeter
. Os comentários acima assumem a definiçãoMark Ransom
dada.PassByReference
objeto por outroPassByReference
na sua lista e se referiu ao último dos dois objetos.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.
fonte
dict
e, em seguida, retorna odict
. 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,dict
mas também ser capaz de sinalizar a reconstrução. Tentei passar umbool
por 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).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) ounonlocal
(para variáveis locais em uma função) em uma função de wrapper.A mesma idéia funciona para ler e
del
escrever uma variável.Para apenas ler, há ainda uma maneira mais curta de usar,
lambda: x
que 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:
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:
Aqui, a
ByRef
classe envolve um acesso ao dicionário. Portanto, o acesso ao atributowrapped
é traduzido para um acesso ao item no dicionário passado. Ao passar o resultado do builtinlocals
e 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.fonte
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:
no código real, é claro, você adicionaria a verificação de erros na pesquisa de ditado.
fonte
Como os dicionários são passados por referência, você pode usar uma variável dict para armazenar quaisquer valores referenciados dentro dela.
fonte
A passagem por referência em Python é bem diferente do conceito de passagem por referência em C ++ / Java.
fonte
Como seu exemplo é orientado a objetos, você pode fazer as seguintes alterações para obter um resultado semelhante:
fonte
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.
fonte
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"):
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.
fonte