Passar um número inteiro por referência em Python

99

Como posso passar um inteiro por referência em Python?

Quero modificar o valor de uma variável que estou passando para a função. Eu li que tudo em Python é passado por valor, mas deve haver um truque fácil. Por exemplo, em Java você poderia passar os tipos de referência de Integer, Long, etc.

  1. Como posso passar um inteiro em uma função por referência?
  2. quais são as melhores práticas?
CodeKingPlusPlus
fonte
veja isso para uma maneira agradável, embora ligeiramente complicada, de envolver seus ints em uma classe anônima (que é mutável) que se comportará como uma 'referência': stackoverflow.com/a/1123054/409638 ou seja, ref = type ('', () , {'n': 1})
robert

Respostas:

110

Não funciona bem assim em Python. Python passa referências para objetos. Dentro de sua função, você tem um objeto - você está livre para modificar esse objeto (se possível). No entanto, os inteiros são imutáveis . Uma solução alternativa é passar o número inteiro em um contêiner que pode sofrer mutação:

def change(x):
    x[0] = 3

x = [1]
change(x)
print x

Isso é feio / desajeitado na melhor das hipóteses, mas você não vai se sair melhor em Python. A razão é porque no Python, atribuição ( =) pega qualquer objeto que seja o resultado do lado direito e o vincula ao que estiver no lado esquerdo * (ou o passa para a função apropriada).

Compreendendo isso, podemos ver porque não há como alterar o valor de um objeto imutável dentro de uma função - você não pode alterar nenhum de seus atributos porque é imutável, e você não pode simplesmente atribuir à "variável" um novo valor porque então você está realmente criando um novo objeto (que é diferente do antigo) e dando a ele o nome que o objeto antigo tinha no namespace local.

Normalmente, a solução alternativa é simplesmente retornar o objeto que você deseja:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* No primeiro caso de exemplo acima, 3realmente é passado para x.__setitem__.

mgilson
fonte
11
"Python passa objetos." Não. Python passa referências (ponteiros para objetos).
user102008
6
@ user102008 - Ponto justo. A semântica neste ponto fica muito confusa, eu acho. Em última análise, também não são "indicadores". Pelo menos, certamente não no mesmo sentido que você fez em C. (Eles não precisam ser referenciados, por exemplo). No meu modelo mental, é mais fácil pensar nas coisas como "Nomes" e "Objetos". A atribuição vincula "Nome" (s) à esquerda a "Objeto" (s) à direita. Ao chamar uma função, você passa o "Objeto" (vinculando-o efetivamente a um novo nome local dentro da função).
mgilson
Obviamente, esta é uma descrição do que são referências a python , mas não é fácil encontrar uma maneira de descrevê-la sucintamente para aqueles que não estão familiarizados com a terminologia.
mgilson
2
Não é de admirar que nos confundamos com a terminologia. Aqui, ele é descrito como chamada por objeto e aqui é descrito como passagem por valor . Em outro lugar, é chamado de "passagem por referência" com um asterisco no que isso realmente significa ... Basicamente, o problema é que a comunidade ainda não descobriu como chamá-lo
mgilson
1
As referências do Python são exatamente iguais às referências do Java. E as referências Java estão de acordo com os ponteiros JLS para objetos. E há basicamente uma bijeção completa entre referências Java / Python e ponteiros C ++ para objetos na semântica, e nada mais descreve essa semântica também. "Eles não precisam ser desreferenciados" Bem, o .operador simplesmente desreferencia o lado esquerdo. Os operadores podem ser diferentes em diferentes idiomas.
user102008
31

A maioria dos casos em que você precisa passar por referência é onde você precisa retornar mais de um valor ao chamador. Uma "prática recomendada" é usar vários valores de retorno, o que é muito mais fácil de fazer em Python do que em linguagens como Java.

Aqui está um exemplo simples:

def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once
Gabe
fonte
9

Não exatamente passando um valor diretamente, mas usando-o como se fosse passado.

x = 7
def my_method():
    nonlocal x
    x += 1
my_method()
print(x) # 8

Ressalvas:

  • nonlocal foi introduzido em python 3
  • Se o escopo delimitador for global, use em globalvez de nonlocal.
Uri
fonte
7

Na verdade, a prática recomendada é dar um passo atrás e perguntar se você realmente precisa fazer isso. Por quê que você deseja modificar o valor de uma variável que está passando para a função?

Se você precisar fazer isso para um hack rápido, a maneira mais rápida é passar um listsegurando o inteiro e colocar um[0] torno de cada uso dele, como a resposta de mgilson demonstra.

Se você precisar fazer isso para algo mais significativo, escreva um classque tenha um intcomo atributo, para que você possa apenas defini-lo. É claro que isso força você a encontrar um bom nome para a classe e para o atributo - se você não conseguir pensar em nada, volte e leia a frase novamente algumas vezes e, em seguida, use o list.

De modo mais geral, se você está tentando portar algum idioma Java diretamente para Python, está fazendo isso errado. Mesmo quando há algo diretamente correspondente (como com static/ @staticmethod), você ainda não deseja usá-lo na maioria dos programas Python apenas porque o usaria em Java.

abarnert
fonte
JAVA não passa inteiros por referência (inteiros ou qualquer objeto. Os objetos em caixa também são substituídos na atribuição).
Luis Masuelli
@LuisMasuelli Bem, as primitivas em caixa são tratadas como objetos, a única coisa que impede seu uso como o OP deseja é o fato de que as caixas são imutáveis ​​(e como as próprias primitivas também são imutáveis, a coisa toda é imutável e só pode ser alterada em nível variável)
Kroltan de
Um bom caso de uso para isso é contar um número de chamadas (um total, não uma profundidade) dentro de uma função recursiva. Você precisa de um número que possa ser incrementado em todas as chamadas ramificadas. Um int padrão simplesmente não
resolve
4

Em Python, todo valor é uma referência (um ponteiro para um objeto), assim como os não primitivos em Java. Além disso, como Java, Python só tem passagem por valor. Então, semanticamente, eles são praticamente os mesmos.

Já que você mencionou Java em sua pergunta, gostaria de ver como você consegue o que deseja em Java. Se você pode mostrar em Java, posso mostrar como fazer exatamente de forma equivalente em Python.

newacct
fonte
4

Uma matriz numpy de elemento único é mutável e, ainda assim, para a maioria dos propósitos, pode ser avaliada como se fosse uma variável python numérica. Portanto, é um contêiner de número por referência mais conveniente do que uma lista de elemento único.

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

resultado:

3
Tela trisoloriana
fonte
4

Talvez não seja a forma pítônica, mas você pode fazer isso

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref
Sergey Kovalev
fonte
Cara que é inteligente.
xilpex
3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     
Shruti
fonte
2

Talvez um pouco mais autodocumentado do que o truque da lista de comprimento 1 seja o velho truque do tipo vazio:

def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)
mheyman
fonte
Ou envolva o número em uma classe: github.com/markrages/python_mutable_number
markrages
0

Em Python, tudo é passado por valor, mas se você quiser modificar algum estado, pode alterar o valor de um inteiro dentro de uma lista ou objeto que é passado para um método.

recursivo
fonte
3
Eu acho, porque everything is passed by valuenão é verdade. Citar documentos:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Astery
1
Listas, objetos e dicionários são passados ​​por referência
AN88
2
Se isso fosse verdade, a atribuição a um parâmetro em uma função seria refletida no site da chamada. Como Astery entre aspas, a passagem é por valor e esses valores são referências a objetos.
recursivo
0

A resposta correta, é usar uma classe e colocar o valor dentro da classe, isso permite que você passe por referência exatamente como deseja.

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)

Chris Pugmire
fonte