Domanda Come posso passare una variabile per riferimento?


La documentazione di Python non è chiara se i parametri vengono passati per riferimento o valore e il codice seguente produce il valore invariato 'Originale'

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

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

C'è qualcosa che posso fare per passare la variabile per riferimento reale?


2053
2018-06-12 10:23


origine


risposte:


Gli argomenti sono passato per incarico. La logica alla base di questo è duplice:

  1. il parametro passato è in realtà a riferimento a un oggetto (ma il riferimento è passato per valore)
  2. alcuni tipi di dati sono mutabili, ma altri no

Così:

  • Se passi a mutevole oggetto in un metodo, il metodo ottiene un riferimento a quello stesso oggetto e puoi mutarlo alla gioia del tuo cuore, ma se ricolleghi il riferimento nel metodo, l'ambito esterno non ne saprà nulla, e dopo aver finito, il riferimento esterno punterà ancora sull'oggetto originale.

  • Se passi un immutabile oggetto di un metodo, non puoi ancora ricollegare il riferimento esterno e non puoi nemmeno mutare l'oggetto.

Per renderlo ancora più chiaro, facciamo alcuni esempi.

Elenco: un tipo mutabile

Proviamo a modificare l'elenco passato a un metodo:

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)

Produzione:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Poiché il parametro passato è un riferimento a outer_listnon una sua copia, possiamo usare i metodi della lista mutante per cambiarlo e fare in modo che le modifiche si riflettano nello scope esterno.

Ora vediamo cosa succede quando proviamo a cambiare il riferimento passato come parametro:

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)

Produzione:

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']

Dal momento che il the_list il parametro è stato passato per valore, assegnando una nuova lista non ha avuto alcun effetto che il codice esterno al metodo potesse vedere. Il the_list era una copia del outer_list riferimento, e abbiamo avuto the_list puntare a una nuova lista, ma non c'era modo di cambiare dove outer_list appuntito.

Stringa - un tipo immutabile

È immutabile, quindi non c'è nulla che possiamo fare per cambiare il contenuto della stringa

Ora proviamo a cambiare il riferimento

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)

Produzione:

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

Di nuovo, poiché il the_string il parametro è stato passato per valore, l'assegnazione di una nuova stringa non ha avuto alcun effetto che il codice esterno al metodo potesse vedere. Il the_string era una copia del outer_string riferimento, e abbiamo avuto the_string puntare a una nuova stringa, ma non c'era modo di cambiare dove outer_string appuntito.

Spero che questo chiarisca un po 'le cose.

MODIFICARE: È stato notato che questo non risponde alla domanda che @David ha originariamente chiesto, "C'è qualcosa che posso fare per passare la variabile per riferimento reale?". Lavoriamo su questo.

Come possiamo aggirare questo?

Come mostra la risposta di @ Andrea, potresti restituire il nuovo valore. Questo non cambia il modo in cui le cose vengono passate, ma ti permette di ottenere le informazioni che vuoi escludere:

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 volessi davvero evitare di usare un valore di ritorno, potresti creare una classe per conservare il tuo valore e passarlo nella funzione o usare una classe esistente, come una 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])

Anche se questo sembra un po 'macchinoso.


2221
2018-06-12 11:18



Il problema deriva da un fraintendimento di quali variabili sono in Python. Se sei abituato alle lingue più tradizionali, hai un modello mentale di ciò che accade nella seguente sequenza:

a = 1
a = 2

Tu ci credi a è una posizione di memoria che memorizza il valore 1, quindi viene aggiornato per memorizzare il valore 2. Non è così che funzionano le cose in Python. Piuttosto, a inizia come riferimento a un oggetto con il valore 1, quindi viene riassegnato come riferimento a un oggetto con il valore 2. Questi due oggetti potrebbero continuare a coesistere anche se a non si riferisce più al primo; in effetti possono essere condivisi da un numero qualsiasi di altri riferimenti all'interno del programma.

Quando si chiama una funzione con un parametro, viene creato un nuovo riferimento che si riferisce all'oggetto passato. Questo è separato dal riferimento utilizzato nella chiamata di funzione, quindi non c'è modo di aggiornare quel riferimento e farlo riferirsi ad un nuovo oggetto. Nel tuo esempio:

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

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

self.variable è un riferimento all'oggetto stringa 'Original'. Quando chiami Change crei un secondo riferimento var all'oggetto. All'interno della funzione si riassegna il riferimento var a un oggetto stringa diverso 'Changed', ma il riferimento self.variable è separato e non cambia.

L'unico modo per aggirare questo è passare un oggetto mutevole. Poiché entrambi i riferimenti si riferiscono allo stesso oggetto, eventuali modifiche all'oggetto si riflettono in entrambe le posizioni.

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

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

529
2017-11-15 17:45



Ho trovato le altre risposte piuttosto lunghe e complicate, quindi ho creato questo semplice diagramma per spiegare il modo in cui Python tratta variabili e parametri. enter image description here


242
2017-09-04 16:05



Non è né pass-by-value né pass-by-reference: è call-by-object. Vedi questo, di Fredrik Lundh:

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

Ecco una citazione significativa:

"... le variabili [nomi] sono non oggetti; non possono essere indicati da altre variabili o indicati da oggetti. "

Nel tuo esempio, quando il Change il metodo è chiamato - a namespace è creato per questo; e var diventa un nome, all'interno di tale spazio dei nomi, per l'oggetto stringa 'Original'. L'oggetto ha quindi un nome in due spazi dei nomi. Il prossimo, var = 'Changed' lega var a un nuovo oggetto stringa, e quindi lo spazio dei nomi del metodo dimentica 'Original'. Alla fine, quel namespace è stato dimenticato e la stringa 'Changed' insieme ad esso.


219
2018-06-12 12:55



Pensa a cose che vengono passate per incarico invece che per riferimento / valore. In questo modo, è sempre chiaro, ciò che sta accadendo finché capisci cosa succede durante il normale incarico.

Quindi, quando si passa una lista a una funzione / metodo, la lista viene assegnata al nome del parametro. L'aggiunta all'elenco comporterà la modifica dell'elenco. Riassegnazione della lista dentro la funzione non cambierà la lista originale, poiché:

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

Dal momento che i tipi immutabili non possono essere modificati, loro sembrare come essere passati per valore - passare un int in una funzione significa assegnare l'int al parametro functions. Puoi riassegnarlo solo sempre, ma non cambierà il valore delle variabili originarie.


142
2018-06-12 12:17



Effbot (alias Fredrik Lundh) ha descritto lo stile di passaggio delle variabili di Python come call-by-object: http://effbot.org/zone/call-by-object.htm

Gli oggetti sono allocati nell'heap e i puntatori a loro possono essere passati ovunque.

  • Quando fai un incarico come x = 1000, viene creata una voce di dizionario che associa la stringa "x" nello spazio dei nomi corrente a un puntatore all'oggetto intero contenente mille.

  • Quando aggiorni "x" con x = 2000, viene creato un nuovo oggetto intero e il dizionario viene aggiornato per puntare al nuovo oggetto. Il vecchio oggetto mille è invariato (e può o non può essere vivo a seconda che qualcun altro faccia riferimento all'oggetto).

  • Quando fai un nuovo incarico come y = x, viene creata una nuova voce di dizionario "y" che punta allo stesso oggetto della voce "x".

  • Oggetti come stringhe e interi immutabile. Ciò significa semplicemente che non ci sono metodi che possono modificare l'oggetto dopo che è stato creato. Ad esempio, una volta creato l'intero numero mille, non cambierà mai. La matematica viene eseguita creando nuovi oggetti interi.

  • Gli oggetti come gli elenchi sono mutevole. Ciò significa che il contenuto dell'oggetto può essere modificato da qualsiasi cosa che punta all'oggetto. Per esempio, x = []; y = x; x.append(10); print y stamperà [10]. La lista vuota è stata creata. Sia "x" che "y" puntano alla stessa lista. Il aggiungere il metodo modifica (aggiorna) l'oggetto list (come aggiungere un record a un database) e il risultato è visibile sia per "x" che per "y" (proprio come un aggiornamento del database sarebbe visibile a ogni connessione a quel database).

Spero che chiarisca il problema per te.


53
2018-03-29 04:41



tecnicamente, Python usa sempre il passaggio per valori di riferimento. Ho intenzione di ripetere la mia altra risposta per sostenere la mia affermazione.

Python utilizza sempre valori di riferimento pass-by. Non c'è eccezione. Qualsiasi assegnazione variabile significa copiare il valore di riferimento. Nessuna eccezione. Qualsiasi variabile è il nome associato al valore di riferimento. Sempre.

Puoi pensare a un valore di riferimento come indirizzo dell'oggetto target. L'indirizzo viene automaticamente dereferenziato quando utilizzato. In questo modo, lavorando con il valore di riferimento, sembra che tu lavori direttamente con l'oggetto di destinazione. Ma c'è sempre un riferimento nel mezzo, un passo in più per saltare al bersaglio.

Ecco l'esempio che dimostra che Python utilizza il passaggio per riferimento:

Illustrated example of passing the argument

Se l'argomento è stato passato per valore, l'argomento lst non può essere modificato Il verde sono gli oggetti di destinazione (il nero è il valore memorizzato all'interno, il rosso è il tipo di oggetto), il giallo è la memoria con il valore di riferimento all'interno - disegnato come la freccia. La freccia blu continua è il valore di riferimento passato alla funzione (tramite il percorso della freccia blu tratteggiata). Il brutto giallo scuro è il dizionario interno. (In realtà potrebbe essere disegnato anche come un'ellisse verde. Il colore e la forma dicono solo che è interno.)

Puoi usare il id() funzione integrata per apprendere quale sia il valore di riferimento (ovvero, l'indirizzo dell'oggetto target).

Nei linguaggi compilati, una variabile è uno spazio di memoria in grado di acquisire il valore del tipo. In Python, una variabile è un nome (catturato internamente come stringa) associato alla variabile di riferimento che contiene il valore di riferimento dell'oggetto di destinazione. Il nome della variabile è la chiave nel dizionario interno, la parte valore di quella voce del dizionario memorizza il valore di riferimento sulla destinazione.

I valori di riferimento sono nascosti in Python. Non esiste alcun tipo di utente esplicito per la memorizzazione del valore di riferimento. Tuttavia, è possibile utilizzare un elemento di lista (o elemento in qualsiasi altro tipo di contenitore adatto) come variabile di riferimento, poiché tutti i contenitori memorizzano gli elementi anche come riferimenti agli oggetti di destinazione. In altre parole, gli elementi non sono in realtà contenuti all'interno del contenitore, ma solo i riferimenti agli elementi.


53
2017-09-15 18:53



Un semplice trucco che uso normalmente è quello di racchiuderlo in una lista:

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

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

(Sì, so che questo può essere sconveniente, ma a volte è abbastanza semplice farlo).


36
2017-08-05 22:52



(modifica - Blair ha aggiornato la sua risposta estremamente popolare in modo che sia ora precisa)

Penso che sia importante notare che il post corrente con il maggior numero di voti (di Blair Conrad), pur essendo corretto rispetto al suo risultato, è fuorviante ed è borderline errato in base alle sue definizioni. Mentre ci sono molti linguaggi (come C) che permettono all'utente di passare per riferimento o passare per valore, Python non è uno di questi.

La risposta di David Cournapeau indica la vera risposta e spiega perché il comportamento nel post di Blair Conrad sembra essere corretto mentre le definizioni non lo sono.

Nella misura in cui Python passa per valore, tutte le lingue passano per valore poiché è necessario inviare una parte di dati (che si tratti di un "valore" o di un "riferimento"). Tuttavia, ciò non significa che Python passi per valore nel senso che un programmatore C potrebbe pensarci.

Se vuoi il comportamento, la risposta di Blair Conrad va bene. Ma se vuoi sapere quali sono i motivi per cui Python non passa né per valore né per riferimento, leggi la risposta di David Cournapeau.


34
2018-06-27 12:33



Non ci sono variabili in Python

La chiave per comprendere il passaggio dei parametri è smettere di pensare alle "variabili". Ci sono nomi e oggetti in Python e insieme loro appaiono come variabili, ma è utile distinguere sempre i tre.

  1. Python ha nomi e oggetti.
  2. L'assegnazione lega un nome a un oggetto.
  3. Passare un argomento in una funzione associa anche un nome (il nome del parametro della funzione) a un oggetto.

Questo è tutto ciò che c'è da fare. La mutabilità è irrilevante per questa domanda.

Esempio:

a = 1

Questo lega il nome a a un oggetto di tipo intero che contiene il valore 1.

b = x

Questo lega il nome b allo stesso oggetto che il nome x è attualmente legato a. Successivamente, il nome b non ha niente a che fare con il nome x più.

Vedi sezioni 3.1 e 4.2 nel riferimento al linguaggio Python 3.


Quindi nel codice mostrato nella domanda, la dichiarazione self.Change(self.variable) lega il nome var (nell'ambito della funzione Change) all'oggetto che contiene il valore 'Original' e il compito var = 'Changed' (nel corpo della funzione Change) assegna di nuovo lo stesso nome: ad un altro oggetto (che capita di contenere anche una stringa ma potrebbe essere qualcos'altro interamente).


25
2018-02-11 11:29



Hai delle ottime risposte qui.

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' )

22
2018-06-12 12:16