variaveis - tupla como parametro python




Como faço para passar uma variável por referência? (16)

A documentação do Python não parece 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 pela referência real?


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

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

  1. Python tem nomes e objetos.
  2. A atribuição liga 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 que existe para isso. A mutabilidade é irrelevante para essa questão.

Exemplo:

a = 1

Isso liga o nome a a um objeto do tipo inteiro que contém o valor 1.

b = x

Isso liga o nome b ao mesmo objeto ao qual o nome x está vinculado. Depois, o nome b não tem mais nada a ver com o nome x .

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

Assim, no código mostrado na pergunta, a declaraçã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 (que também contém uma string, mas poderia ter sido algo completamente diferente).


(edit - Blair atualizou sua resposta enormemente popular para que agora seja preciso)

Eu acho que é importante notar que o post atual com o maior número de votos (por Blair Conrad), embora seja correto em relação ao seu resultado, é enganoso e está incorreto no limite 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 é uma delas.

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 é passado por valor, todos os idiomas são passados ​​por valor, uma vez que algum dado (seja um "valor" ou uma "referência") deve ser enviado. No entanto, isso não significa que o Python seja passado por valor, no sentido de que um programador C pensaria nele.

Se você quer o comportamento, a resposta de Blair Conrad é boa. Mas se você quiser saber as porcas e parafusos do motivo pelo qual o Python não é nem passar por valor nem passar por referência, leia a resposta de David Cournapeau.


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

Objetos são alocados no heap e ponteiros para eles podem ser passados ​​em qualquer lugar.

  • Quando você faz uma atribuição como x = 1000 , é criada uma entrada de dicionário que mapeia a string "x" no namespace atual para um ponteiro para o objeto inteiro contendo 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 antigo mil objeto permanece inalterado (e pode ou não estar vivo, dependendo se algo se refere ao objeto).

  • Quando você faz uma nova atribuição, como y = x , é criada uma nova entrada de dicionário "y" que aponta para o mesmo objeto que a entrada para "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, quando o objeto inteiro mil é 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 x = []; y = x; x.append(10); print y x = []; y = x; x.append(10); print y será impresso [10] . A lista vazia foi criada. Ambos "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 tanto para "x" quanto para "y" (assim como uma atualização de banco de dados seria visível para cada conexão a esse banco de dados).

Espero que esclarece o problema para você.


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.


Nesse caso, a variável intitulada var no método Change recebe uma referência a self.variable e você atribui imediatamente uma string a var . Não está mais apontando para self.variable . O trecho de código a seguir mostra o que aconteceria se você modificasse a estrutura de dados apontada por var e self.variable , neste 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 de que alguém poderia esclarecer isso ainda mais.


O problema vem de um mal-entendido de quais variáveis ​​estão no Python. Se você está acostumado com as linguagens mais tradicionais, você tem um modelo mental do que acontece na seguinte sequência:

a = 1
a = 2

Você acredita que a é um local de memória que armazena o valor 1 e é atualizado para armazenar o valor 2 . Não é assim que as coisas funcionam em Python. Em vez disso, a começa como uma referência a um objeto com o valor 1 , em seguida, é reatribuído como uma referência a um objeto com o valor 2 . Esses dois objetos podem continuar a coexistir, embora não se refira mais ao primeiro; na verdade, 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 passado. Isso é separado da referência que foi usada na chamada de função, então não há como atualizar essa referência e fazer referência a uma função. 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 string 'Original' . Quando você chama Change você cria uma segunda referência var para o objeto. Dentro da função, você reatribui a var referência a um objeto de string 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 locais.

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

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

Tecnicamente, o Python sempre usa a passagem por valores de 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á exceção. Qualquer atribuição de variável significa copiar o valor de referência. Nenhuma exceção. Qualquer variável é o nome ligado 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 é automaticamente desreferenciado quando usado. Desta forma, trabalhando com o valor de referência, parece que você trabalha diretamente com o objeto alvo. Mas sempre há uma referência no meio, um passo a mais 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 lst externo 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 (por meio do caminho da seta azul tracejada). O amarelo escuro e feio é o dicionário interno. (Ele realmente poderia ser desenhado também como uma elipse verde. A cor e a forma só dizem que é interno.)

Você pode usar a função id() 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) ligado à variável de referência que contém o valor de referência para o 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 para o destino.

Valores de referência estão ocultos no Python. Não há nenhum tipo de usuário explícito 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 dentro do contêiner - apenas as referências aos elementos são.


Um truque simples que eu normalmente uso é apenas 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.)


Apesar de passar por referência não é nada que se encaixa bem em python e deve ser raramente usado, existem algumas soluções alternativas que realmente podem trabalhar para obter o objeto atualmente atribuído a uma variável local ou até mesmo reatribuir uma variável local de dentro de uma função chamada.

A idéia básica é ter uma função que pode fazer esse acesso e pode 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 invólucro.

def change(wrapper):
    wrapper(7)

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

A mesma ideia funciona para ler e deleditar uma variável.

Por apenas ler, existe até uma maneira mais curta de apenas usar o lambda: xqual retorna um callable que, quando chamado, retorna o valor atual de x. Isso é um pouco como "chamada por nome" usada em idiomas no passado distante.

Passar 3 wrappers para acessar uma variável é um pouco complicado, então eles podem ser agrupados em uma classe que tenha 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

Pythons "reflection" support torna possível obter um objeto que seja 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. Então, o atributo access to wrappedé traduzido para um item de acesso 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 informa que alterar o dicionário pode não funcionar, mas parece funcionar para mim.


Como o seu exemplo é orientado a objetos, você pode fazer a seguinte alteração 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'

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ária é 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 que você adicionaria a verificação de erros na consulta dit.


O esquema pass-by-assignment do Python não é exatamente o mesmo que a opção de parâmetros de referência do C ++, mas acaba por ser muito similar 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 inteiros e strings são passados ​​por referência de objeto ao invés de copiar, mas como você não pode alterar objetos imutáveis ​​no lugar de qualquer forma, o efeito é muito parecido com 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 de objeto, que é semelhante à maneira como C passa matrizes como ponteiros - objetos mutáveis ​​podem ser alterados no lugar da função, muito parecido com matrizes C .

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 um valor em outras linguagens de programação) e não a referência a esse objeto. Em outras palavras, quando você liga:

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

my_list = [0, 1]
change_me(my_list)

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

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

que obviamente não irá alterar o objeto passado para a função. Se a função ficou assim:

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

Então a chamada resultaria em:

[0, 1].append(2)

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


Eu usei o seguinte método para converter rapidamente alguns códigos Fortran para Python. É verdade que não é passado por referência como a pergunta original foi colocada, mas é um trabalho 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

Muitos insights em respostas aqui, mas eu acho que um ponto adicional não é claramente mencionado aqui explicitamente. Citando a documentação em 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 dentro do corpo da função, ela é assumida como local. Se uma variável receber um novo valor dentro da função, a variável é implicitamente local, e você precisa declará-la explicitamente como "global". Embora um pouco surpreendente no início, a consideração de um momento explica isso. Por um lado, exigir variáveis ​​atribuídas globais fornece uma barreira contra efeitos colaterais não intencionais. Por outro lado, se global era necessário para todas as referências globais, você estaria usando global o tempo todo.Você teria que declarar como global cada referência a uma função interna ou a um componente de um módulo importado.Esta desordem iria derrotar a utilidade da declaração global para identificar os efeitos colaterais ".

Mesmo ao passar um objeto mutável para uma função, isso ainda se aplica. E para mim explica claramente o motivo 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, portanto, cria um novo objeto local e quebra o link para o objeto original.


Você pode simplesmente usar uma classe vazia como uma instância para armazenar objetos de referência porque os atributos de objetos internos são armazenados em um dicionário de instâncias. 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)




pass-by-reference