Passaggio di un numero intero per riferimento in Python


97

Come posso passare un numero intero per riferimento in Python?

Voglio modificare il valore di una variabile che sto passando alla funzione. Ho letto che tutto in Python è passato per valore, ma deve esserci un trucco facile. Ad esempio, in Java è possibile passare i tipi di riferimento di Integer, Longecc

  1. Come posso passare un numero intero in una funzione per riferimento?
  2. Quali sono le migliori pratiche?

guarda questo per un modo carino, anche se leggermente contorto, di racchiudere i tuoi int in una classe anonima (che è modificabile) che si comporterà come un 'riferimento': stackoverflow.com/a/1123054/409638 ie ref = type ('', () , {'n': 1})
robert

Risposte:


107

Non funziona in questo modo in Python. Python passa i riferimenti agli oggetti. All'interno della tua funzione hai un oggetto: sei libero di mutare quell'oggetto (se possibile). Tuttavia, i numeri interi non sono modificabili . Una soluzione alternativa consiste nel passare il numero intero in un contenitore che può essere modificato:

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

x = [1]
change(x)
print x

Questo è brutto / goffo nella migliore delle ipotesi, ma non farai di meglio in Python. Il motivo è perché in Python, assignment ( =) prende qualunque oggetto sia il risultato del lato destro e lo lega a qualunque cosa si trovi sul lato sinistro * (o lo passa alla funzione appropriata).

Comprendendo questo, possiamo vedere perché non c'è modo di cambiare il valore di un oggetto immutabile all'interno di una funzione: non puoi cambiare nessuno dei suoi attributi perché è immutabile e non puoi semplicemente assegnare alla "variabile" un nuovo valore perché in realtà stai creando un nuovo oggetto (che è distinto da quello vecchio) e dandogli il nome che il vecchio oggetto aveva nello spazio dei nomi locale.

Di solito la soluzione alternativa consiste nel restituire semplicemente l'oggetto che desideri:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* Nel primo caso di esempio sopra, 3viene effettivamente passato a x.__setitem__.


11
"Python passa gli oggetti." No. Python passa i riferimenti (puntatori a oggetti).
user102008

6
@ user102008 - Punto giusto. La semantica a questo punto diventa molto confusa, credo. In definitiva, non sono nemmeno "indicatori". Almeno, di certo non nello stesso senso in cui lo hai fatto tu C. (Ad esempio, non è necessario dereferenziarli). Nel mio modello mentale, è più facile pensare alle cose come "Nomi" e "Oggetti". L'assegnazione associa un "Nome" a sinistra a un "Oggetto" a destra. Quando chiamate una funzione, passate l '"Oggetto" (associandolo effettivamente a un nuovo nome locale all'interno della funzione).
mgilson

Questa è ovviamente una descrizione di cosa sono i riferimenti a Python , ma non è facile trovare un modo per descriverlo sinteticamente a coloro che non hanno familiarità con la terminologia.
mgilson

2
Non c'è da stupirsi che ci confondiamo con la terminologia. Qui lo abbiamo descritto come call-by-object e qui è descritto come pass-by-value . Altrove si chiama "pass-by-reference" con un asterisco su cosa significa effettivamente ... Fondamentalmente, il problema è che la comunità non ha capito come chiamarlo
mgilson

1
I riferimenti a Python sono identici ai riferimenti a Java. E i riferimenti Java sono in base ai puntatori JLS agli oggetti. E c'è fondamentalmente una biiezione completa tra i riferimenti Java / Python e i puntatori C ++ agli oggetti nella semantica, e nient'altro descrive anche questa semantica. "Non è necessario dereferenziarli" Bene, l' .operatore semplicemente dereferenzia il lato sinistro. Gli operatori possono essere diversi nelle diverse lingue.
user102008

31

Nella maggior parte dei casi in cui è necessario passare per riferimento, è necessario restituire più di un valore al chiamante. Una "best practice" consiste nell'usare più valori di ritorno, cosa molto più facile da fare in Python che in linguaggi come Java.

Ecco un semplice esempio:

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

9

Non esattamente passando un valore direttamente, ma usandolo come se fosse passato.

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

Avvertenze:

  • nonlocal è stato introdotto in python 3
  • Se l'ambito di inclusione è quello globale, utilizzare globalinvece di nonlocal.

7

In realtà, la migliore pratica è fare un passo indietro e chiedere se è davvero necessario farlo. Perché vuoi modificare il valore di una variabile che stai passando alla funzione?

Se hai bisogno di farlo per un rapido hack, il modo più veloce è passare un listtenendo l'intero e attaccare un [0]intorno a ogni uso, come dimostra la risposta di mgilson.

Se hai bisogno di farlo per qualcosa di più significativo, scrivi a classche abbia intcome attributo, quindi puoi semplicemente impostarlo. Ovviamente questo ti costringe a trovare un buon nome per la classe e per l'attributo: se non riesci a pensare a nulla, torna indietro e leggi di nuovo la frase alcune volte, quindi usa il list.

Più in generale, se stai provando a portare un idioma Java direttamente su Python, lo stai facendo male. Anche quando c'è qualcosa che corrisponde direttamente (come con static/ @staticmethod), non vuoi comunque usarlo nella maggior parte dei programmi Python solo perché lo useresti in Java.


JAVA non trasmette numeri interi per riferimento (numeri interi o qualsiasi oggetto. Anche gli oggetti in scatola vengono sostituiti al momento dell'assegnazione).
Luis Masuelli

@LuisMasuelli Ebbene, le primitive inscatolate sono trattate proprio come oggetti, l'unica cosa che ne impedisce l'uso come vuole l'OP è il fatto che le scatole sono immutabili (e poiché anche le primitive stesse sono immutabili, l'intera cosa è immutabile e può essere modificata solo a livello variabile)
Kroltan

Un buon caso d'uso per questo è contare un numero di chiamate (un totale, non una profondità) all'interno di una funzione ricorsiva. Hai bisogno di un numero che possa essere incrementato in tutte le chiamate ramificate. Un int standard semplicemente non lo taglia
z33k

4

In Python, ogni valore è un riferimento (un puntatore a un oggetto), proprio come le non primitive in Java. Inoltre, come Java, Python ha solo il passaggio per valore. Quindi, semanticamente, sono praticamente la stessa cosa.

Dato che hai menzionato Java nella tua domanda, mi piacerebbe vedere come ottieni ciò che desideri in Java. Se puoi mostrarlo in Java, posso mostrarti come farlo esattamente in modo equivalente in Python.


4

Un array numpy a singolo elemento è mutabile e tuttavia per la maggior parte degli scopi può essere valutato come se fosse una variabile numerica Python. Pertanto, è un contenitore del numero di riferimento più conveniente rispetto a un elenco a elemento singolo.

    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)

produzione:

3

3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     

3

Forse non è un modo pitonico, ma puoi farlo

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref

Uomo intelligente.
xilpex


0

In Python, tutto viene passato per valore, ma se vuoi modificare uno stato, puoi cambiare il valore di un intero all'interno di una lista o di un oggetto passato a un metodo.


3
Penso, perché everything is passed by valuenon è proprio vero. Documenti di citazione:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Astery

1
Elenchi, oggetti e dizionari vengono passati per riferimento
AN88

2
Se ciò fosse vero, l'assegnazione a un parametro in una funzione si rifletterebbe nel sito della chiamata. Come citato da Astery, il passaggio è per valore e quei valori sono riferimenti a oggetti.
ricorsivo

0

La risposta corretta è usare una classe e inserire il valore all'interno della classe, questo ti consente di passare per riferimento esattamente come desideri.

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)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.