Come faccio a passare una variabile per riferimento?


2628

La documentazione di Python sembra poco chiara sul fatto che i parametri vengano 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 effettivo?


23
Per una breve spiegazione / chiarimento, vedere la prima risposta a questa domanda StackOverflow . Poiché le stringhe sono immutabili, non verranno modificate e verrà creata una nuova variabile, quindi la variabile "esterna" ha ancora lo stesso valore.
PhilS,

10
Il codice nella risposta di BlairConrad è buono, ma la spiegazione fornita da DavidCournapeau e DarenThomas è corretta.
Ethan Furman,

70
Prima di leggere la risposta selezionata, ti preghiamo di leggere questo breve testo Altre lingue hanno "variabili", Python ha "nomi" . Pensa a "nomi" e "oggetti" invece di "variabili" e "riferimenti" e dovresti evitare molti problemi simili.
lqc,

24
Perché questo usa inutilmente una classe? Questo non è Java: P
Peter R

2
un'altra soluzione consiste nel creare un 'riferimento' wrapper in questo modo: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
robert

Risposte:


2831

Gli argomenti vengono passati per incarico . La logica alla base è duplice:

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

Così:

  • Se passi un oggetto mutabile in un metodo, il metodo ottiene un riferimento a quello stesso oggetto e puoi mutarlo per la gioia del tuo cuore, ma se ricolleghi il riferimento nel metodo, l'ambito esterno non ne saprà nulla e dopo hai finito, il riferimento esterno punterà comunque verso l'oggetto originale.

  • Se passi un oggetto immutabile a 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 modificabile

Proviamo a modificare l'elenco che è stato 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 outer_list, non una copia di esso, possiamo usare i metodi dell'elenco di mutazioni per cambiarlo e far sì che le modifiche si riflettano nell'ambito esterno.

Ora vediamo cosa succede quando proviamo a modificare 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']

Poiché il the_listparametro è stato passato per valore, l'assegnazione di un nuovo elenco non ha avuto alcun effetto che il codice esterno al metodo potesse vedere. L' the_listera una copia del outer_listriferimento, e abbiamo dovuto the_listpuntare a una nuova lista, ma non c'era modo di cambiare doveouter_list punta.

String - un tipo immutabile

È immutabile, quindi non c'è nulla che possiamo fare per modificare 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

Ancora una volta, poiché il the_stringparametro è stato passato per valore, l'assegnazione di una nuova stringa non ha avuto alcun effetto sul codice esterno al metodo. L' the_stringera una copia del outer_stringriferimento, e abbiamo dovuto the_stringpuntare a una nuova stringa, ma non c'era modo di cambiare dove outer_stringpunta.

Spero che questo chiarisca un po 'le cose.

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

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 consente di ottenere le informazioni che desideri restituire:

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 davvero volessi 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 un elenco:

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 'ingombrante.


165
Quindi lo stesso è in C, quando passi "per riferimento" stai effettivamente passando per valore il riferimento ... Definisci "per riferimento": P
Andrea Ambu,

104
Non sono sicuro di aver compreso i tuoi termini. Sono stato fuori dal gioco C per un po ', ma quando ci ero dentro, non c'era "passaggio per riferimento" - potevi passare le cose, ed era sempre passare per valore, quindi qualunque cosa fosse nella lista dei parametri è stato copiato. Ma a volte la cosa era un puntatore, che si poteva seguire al pezzo di memoria (primitivo, array, struct, qualunque cosa), ma non si poteva cambiare il puntatore che era stato copiato dall'ambito esterno - quando si finiva la funzione , il puntatore originale puntava ancora allo stesso indirizzo. Il C ++ ha introdotto riferimenti, che si sono comportati diversamente.
Blair Conrad,

34
@Zac Bowling Non capisco come ciò che stai dicendo sia rilevante, in senso pratico, per questa risposta. Se un nuovo arrivato Python voluto sapere su passando per ref / Val, poi l'asporto da questa risposta è: 1- È possibile utilizzare il riferimento che una funzione riceve come i suoi argomenti, per modificare il valore di 'fuori' di una variabile, a patto poiché non si riassegna il parametro per fare riferimento a un nuovo oggetto. 2- L'assegnazione a un tipo immutabile crea sempre un nuovo oggetto, che interrompe il riferimento che hai avuto alla variabile esterna.
Cam Jackson,

11
@CamJackson, hai bisogno di un esempio migliore: i numeri sono anche oggetti immutabili in Python. Inoltre, non sarebbe vero dire che qualsiasi assegnazione senza sottoscrizione sul lato sinistro degli uguali riassegnerà il nome a un nuovo oggetto, che sia immutabile o no? def Foo(alist): alist = [1,2,3]sarà non modificare i contenuti della lista dal punto di vista chiamanti.
Mark Ransom,

59
-1. Il codice mostrato è buono, la spiegazione di come è completamente sbagliato. Vedi le risposte di DavidCournapeau o DarenThomas per spiegazioni corrette sul perché.
Ethan Furman,

685

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

a = 1
a = 2

Si ritiene che asia un percorso di memoria che memorizza il valore 1, quindi viene aggiornato per memorizzare il valore 2. Non è così che funzionano le cose in Python. Piuttosto, ainizia come riferimento a un oggetto con il valore 1, quindi viene riassegnato come riferimento a un oggetto con il valore 2. Questi due oggetti possono continuare a coesistere anche se anon si riferiscono più al primo; infatti possono essere condivisi da qualsiasi numero 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 che è stato usato nella chiamata di funzione, quindi non c'è modo di aggiornare quel riferimento e farlo fare riferimento a 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 varall'oggetto. All'interno della funzione si riassegna il riferimento vara un oggetto stringa diverso 'Changed', ma il riferimento self.variableè separato e non cambia.

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

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

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

106
Buona spiegazione sintetica. Il tuo paragrafo "Quando chiami una funzione ..." è una delle migliori spiegazioni che ho sentito parlare della frase piuttosto criptica che "i parametri della funzione Python sono riferimenti, passati per valore". Penso che se capisci quel paragrafo da solo, ogni altra cosa ha senso e scorre come una logica conclusione da lì. Quindi devi solo essere consapevole di quando stai creando un nuovo oggetto e quando ne stai modificando uno esistente.
Cam Jackson,

3
Ma come si può riassegnare il riferimento? Pensavo non potessi cambiare l'indirizzo di 'var' ma che la tua stringa "Changed" ora sarebbe stata memorizzata nell'indirizzo di memoria 'var'. La tua descrizione sembra invece che "Modificato" e "Originale" appartengano a luoghi diversi della memoria e tu cambi semplicemente "var" a un indirizzo diverso. È corretto?
Glassjawed,

9
@Glassjawed, penso che tu lo stia ottenendo. "Modificato" e "Originale" sono due diversi oggetti stringa con indirizzi di memoria diversi e "var" cambia dal puntamento all'uno al puntamento all'altro.
Mark Ransom,

l'uso della funzione id () aiuta a chiarire le cose, perché chiarisce quando Python crea un nuovo oggetto (quindi penso comunque).
Tim Richardson,

1
@MinhTran nei termini più semplici, un riferimento è qualcosa che "si riferisce" a un oggetto. La rappresentazione fisica di questo è molto probabilmente un puntatore, ma è semplicemente un dettaglio di implementazione. È davvero una nozione astratta nel cuore.
Mark Ransom,

329

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. inserisci qui la descrizione dell'immagine


2
adorabile, rende facile individuare la sottile differenza che esiste un compito intermedio, non ovvio per uno spettatore occasionale. +1
user22866

9
Non importa se A sia mutabile o meno. Se assegni qualcosa di diverso a B, A non cambia . Se un oggetto è mutabile, puoi certamente mutarlo. Ma ciò non ha nulla a che fare con l'assegnazione diretta a un nome.
Martijn Pieters

1
@Martijn Hai ragione. Ho rimosso la parte della risposta che menziona la mutabilità. Non penso che ora possa essere più semplice.
Zenadix,

4
Grazie per l'aggiornamento, molto meglio! Ciò che confonde la maggior parte delle persone è l'assegnazione a un abbonamento; ad esempio B[0] = 2, contro assegnazione diretta, B = 2.
Martijn Pieters

11
"A è assegnato a B." Non è ambiguo? Penso nell'inglese ordinario che può significare uno A=Bo B=A.
Hatshepsut,

240

Non è né pass-by-value o 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] non sono oggetti; non possono essere indicate da altre variabili o citate da oggetti."

Nel tuo esempio, quando Changeviene chiamato il metodo, viene creato uno spazio dei nomi per esso; e vardiventa un nome, all'interno di quello spazio dei nomi, per l'oggetto stringa 'Original'. Tale oggetto ha quindi un nome in due spazi dei nomi. Successivamente, si var = 'Changed'lega vara un nuovo oggetto stringa e quindi lo spazio dei nomi del metodo si dimentica 'Original'. Infine, quello spazio dei nomi viene dimenticato e la stringa 'Changed'insieme ad esso.


21
Trovo difficile da acquistare. Per me è proprio come Java, i parametri sono puntatori agli oggetti in memoria e quei puntatori vengono passati attraverso lo stack o i registri.
Luciano,

10
Questo non è come Java. Uno dei casi in cui non è lo stesso sono gli oggetti immutabili. Pensa alla banale funzione lambda x: x. Applicalo per x = [1, 2, 3] e x = (1, 2, 3). Nel primo caso, il valore restituito sarà una copia dell'input e identico nel secondo caso.
David Cournapeau,

26
No, è esattamente come la semantica di Java per gli oggetti. Non sono sicuro di cosa intendi per "Nel primo caso, il valore restituito sarà una copia dell'input e identico nel secondo caso". ma questa affermazione sembra chiaramente errata.
Mike Graham,

23
È esattamente lo stesso di Java. I riferimenti agli oggetti vengono passati per valore. Chiunque la pensi diversamente dovrebbe allegare il codice Python per una swapfunzione che può scambiare due riferimenti, come questo: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann,

24
È esattamente lo stesso di Java quando si passano oggetti in Java. Tuttavia, Java ha anche delle primitive, che vengono passate copiando il valore della primitiva. Quindi differiscono in quel caso.
Claudiu,

174

Pensa alle cose che vengono passate per incarico anziché per riferimento / per valore. In questo modo, è sempre chiaro cosa sta succedendo finché capisci cosa succede durante il normale incarico.

Pertanto, quando si passa un elenco a una funzione / metodo, l'elenco viene assegnato al nome del parametro. L'aggiunta all'elenco comporterà la modifica dell'elenco. La riassegnazione dell'elenco all'interno della funzione non modifica l'elenco originale, poiché:

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

Poiché i tipi immutabili non possono essere modificati, sembrano essere passati per valore: passare un int in una funzione significa assegnare l'int al parametro della funzione. Puoi solo riassegnarlo, ma non cambierà il valore delle variabili originali.


3
A prima vista questa risposta sembra eludere la domanda originale. Dopo una seconda lettura ho capito che questo rende la questione abbastanza chiara. Un buon seguito a questo concetto di "assegnazione di nomi" può essere trovato qui: Code Like a Pythonista: Idiomatic Python
Christian Groleau

65

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

Gli oggetti vengono allocati sull'heap e i puntatori possono essere passati ovunque.

  • Quando si effettua un compito come x = 1000, viene creata una voce del dizionario che mappa la stringa "x" nello spazio dei nomi corrente su un puntatore all'oggetto intero contenente mille.

  • Quando si aggiorna "x" con x = 2000, viene creato un nuovo oggetto intero e il dizionario viene aggiornato per puntare al nuovo oggetto. Il vecchio mille oggetti è invariato (e può essere o meno vivo a seconda che qualcos'altro si riferisca all'oggetto).

  • Quando si esegue una nuova assegnazione, ad esempio y = x, viene creata una nuova voce del dizionario "y" che punta allo stesso oggetto della voce per "x".

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

  • Oggetti come elenchi sono mutabili . Ciò significa che il contenuto dell'oggetto può essere modificato da qualsiasi cosa che punti all'oggetto. Ad esempio, x = []; y = x; x.append(10); print ystamperà [10]. L'elenco vuoto è stato creato. Sia "x" che "y" indicano lo stesso elenco. Il metodo append modifica (aggiorna) l'oggetto elenco (come l'aggiunta di un record a un database) e il risultato è visibile sia a "x" che a "y" (così come un aggiornamento del database sarebbe visibile a ogni connessione a quel database).

Spero che questo chiarisca il problema per te.


2
Apprezzo molto imparare questo da uno sviluppatore. È vero che la id()funzione restituisce il valore del puntatore (riferimento all'oggetto), come suggerisce la risposta di pepr?
Onesto Abe

4
@HonestAbe Sì, in CPython l' id () restituisce l'indirizzo. Ma in altri pitoni come PyPy e Jython, l' id () è solo un identificatore di oggetto univoco.
Raymond Hettinger,

59

Tecnicamente, Python utilizza sempre valori di riferimento per passaggio . Ripeterò la mia altra risposta a sostegno della mia dichiarazione.

Python utilizza sempre valori pass-by-reference. Non ci sono eccezioni. Qualsiasi assegnazione di variabili 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 l'indirizzo dell'oggetto target. L'indirizzo viene automaticamente referenziato quando utilizzato. In questo modo, lavorando con il valore di riferimento, sembra che lavori direttamente con l'oggetto target. 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:

Esempio illustrato di trasmissione dell'argomento

Se l'argomento è stato passato per valore, l'esterno lstnon può essere modificato. Il verde sono gli oggetti target (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 solida blu è il valore di riferimento che è stato 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.)

È possibile utilizzare la 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 che è in grado di acquisire il valore del tipo. In Python, una variabile è un nome (acquisito internamente come stringa) associato alla variabile di riferimento che contiene il valore di riferimento sull'oggetto target. Il nome della variabile è la chiave nel dizionario interno, la parte del valore di tale elemento 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 elenco (o un 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 effettivamente contenuti all'interno del contenitore - lo sono solo i riferimenti agli elementi.


1
In realtà questo è confermato il suo passaggio per valore di riferimento. +1 per questa risposta sebbene l'esempio non sia stato buono.
BugShotGG

30
Inventare una nuova terminologia (come "passa per valore di riferimento" o "chiama per oggetto" non è utile). "Chiama per (valore | riferimento | nome)" sono termini standard. "riferimento" è un termine standard. Passare i riferimenti per valore descrive accuratamente il comportamento di Python, Java e una miriade di altre lingue, usando una terminologia standard.
cayhorstmann,

4
@cayhorstmann: il problema è che la variabile Python non ha lo stesso significato terminologico che in altre lingue. In questo modo, la chiamata per riferimento non si adatta bene qui. Inoltre, come si definisce esattamente il termine riferimento ? Informalmente, il modo Python potrebbe essere facilmente descritto come passare l'indirizzo dell'oggetto. Ma non si adatta a un'implementazione potenzialmente distribuita di Python.
pepr

1
Mi piace questa risposta, ma potresti considerare se l'esempio sta davvero aiutando o danneggiando il flusso. Inoltre, se sostituissi "valore di riferimento" con "riferimento a oggetto", utilizzeresti una terminologia che potremmo considerare "ufficiale", come mostrato qui: Definizione di funzioni
Onesto Abe

2
C'è una nota a piè di pagina indicata alla fine di quella citazione, che recita: "In realtà, chiamare per riferimento all'oggetto sarebbe una descrizione migliore, poiché se un oggetto mutabile viene passato, il chiamante vedrà tutte le modifiche apportate dal chiamante ... " Concordo con te sul fatto che la confusione è causata dal tentativo di adattare la terminologia stabilita con altre lingue. Semantica a parte, le cose che devono essere comprese sono: dizionari / spazi dei nomi, operazioni di associazione dei nomi e relazione tra nome → puntatore → oggetto (come già sapete).
Onesto Abe

56

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 appaiono come variabili, ma è utile distinguere sempre i tre.

  1. Python ha nomi e oggetti.
  2. L'assegnazione associa 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

Ciò associa il nome aa un oggetto di tipo intero che contiene il valore 1.

b = x

Ciò lega il nome ballo stesso oggetto a cui xè attualmente associato il nome . Successivamente, il nome bnon ha più nulla a che fare con il nome x.

Vedere le sezioni 3.1 e 4.2 nel riferimento alla lingua di Python 3.

Come leggere l'esempio nella domanda

Nel codice mostrato nella domanda, l'istruzione self.Change(self.variable)lega il nome var(nell'ambito della funzione Change) all'oggetto che contiene il valore 'Original'e l'assegnazione var = 'Changed'(nel corpo della funzione Change) assegna nuovamente lo stesso nome: a qualche altro oggetto (che accade tenere anche una stringa ma avrebbe potuto essere qualcos'altro del tutto).

Come passare per riferimento

Quindi, se la cosa che vuoi cambiare è un oggetto mutabile, non ci sono problemi, poiché tutto viene effettivamente passato per riferimento.

Se si tratta di un oggetto immutabile (ad es. Un bool, un numero, una stringa), la strada da percorrere è avvolgerlo in un oggetto mutabile.
La soluzione rapida per questo è un elenco di un elemento (invece di self.variable, passa [self.variable]e nella funzione modifica var[0]).
L' approccio più pitonico sarebbe quello di introdurre una banale classe a un attributo. La funzione riceve un'istanza della classe e manipola l'attributo.


20
"Python non ha variabili" è uno slogan sciocco e confuso, e vorrei davvero che la gente smettesse di dirlo ... :( Il resto di questa risposta è buona!
Ned Batchelder,

9
Può essere scioccante, ma non è sciocco. E non penso che sia confuso: si spera che apra la mente del destinatario per la spiegazione che sta arrivando e la mette in un utile atteggiamento "Mi chiedo cosa abbiano invece delle variabili". (Sì, il tuo chilometraggio può variare.)
Lutz Prechelt,

13
diresti anche che Javascript non ha variabili? Funzionano allo stesso modo di Python. Inoltre, Java, Ruby, PHP, .... Penso che una migliore tecnica di insegnamento sia "Le variabili di Python funzionano in modo diverso da quelle di C."
Ned Batchelder,

9
Sì, Java ha variabili. Lo stesso vale per Python, JavaScript, Ruby, PHP, ecc. Non si direbbe in Java che intdichiara una variabile, ma Integernon lo è. Entrambi dichiarano variabili. La Integervariabile è un oggetto, la intvariabile è una primitiva. Ad esempio, hai dimostrato come funzionano le tue variabili mostrando a = 1; b = a; a++ # doesn't modify b. Questo è esattamente vero anche in Python (usando += 1poiché non esiste ++in Python)!
Ned Batchelder,

1
Il concetto di "variabile" è complesso e spesso vago: una variabile è un contenitore per un valore, identificato da un nome. In Python, i valori sono oggetti, i contenitori sono oggetti (vedi il problema?) E i nomi sono in realtà cose separate. Credo che sia molto più difficile ottenere una comprensione accurata delle variabili in questo modo. La spiegazione di nomi e oggetti appare più difficile, ma in realtà è più semplice.
Lutz Prechelt,

43

Un semplice trucco che di solito uso è semplicemente inserirlo in un elenco:

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

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

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


7
+1 per una piccola quantità di testo che fornisce la soluzione alternativa essenziale al problema di Python che non ha riferimenti pass-by. (Come commento / domanda di follow-on che si adatta qui e in qualsiasi parte di questa pagina: non è chiaro perché Python non è in grado di fornire una parola chiave "ref" come fa C #, che semplicemente racchiude l'argomento del chiamante in un elenco come questo, e tratta i riferimenti all'argomento all'interno della funzione come il nono elemento dell'elenco.)
M Katz

5
Bello. Per passare per ref, avvolgere in [] 's.
Justas,

38

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

Penso che sia importante notare che l'attuale post 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 consentono 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 è passato per valore, tutte le lingue sono passate per valore poiché alcuni dati (sia esso un "valore" o un "riferimento") devono essere inviati. Tuttavia, ciò non significa che Python passi per valore, nel senso che un programmatore C dovrebbe pensarci.

Se vuoi il comportamento, la risposta di Blair Conrad va bene. Ma se vuoi conoscere i dettagli del perché Python non è né passare per valore né passare per riferimento, leggi la risposta di David Cournapeau.


4
Semplicemente non è vero che tutte le lingue sono chiamate per valore. In C ++ o Pascal (e sicuramente molti altri che non conosco), hai chiamato per riferimento. Ad esempio, in C ++, void swap(int& x, int& y) { int temp = x; x = y; y = temp; }scambieranno le variabili passate ad esso. In Pascal, usi varinvece di &.
cayhorstmann,

2
Pensavo di aver risposto molto tempo fa, ma non lo vedo. Per completezza - cayhorstmann ha frainteso la mia risposta. Non stavo dicendo che tutto è chiamata in base al valore nei termini che la maggior parte delle persone apprende per la prima volta riguardo al C / C ++ . Era semplicemente passato un certo valore (valore, nome, puntatore, ecc.) E i termini usati nella risposta originale di Blair erano inaccurati.
KobeJohn,

24

Hai delle risposte davvero buone 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' )

2
sì, tuttavia se si esegue x = [2, 4, 4, 5, 5], y = x, X [0] = 1, stampa x # [1, 4, 4, 5, 5] stampa y # [1 , 4, 4, 5, 5]
laycat

19

In questo caso alla variabile denominata varnel metodo Changeviene assegnato un riferimento self.variablee si assegna immediatamente una stringa a var. Non punta più a self.variable. Il frammento di codice seguente mostra cosa accadrebbe se si modificasse la struttura dei dati a cui punta vare self.variable, in questo caso un elenco:

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

Sono sicuro che qualcun altro potrebbe chiarirlo ulteriormente.


18

Lo schema del passaggio per assegnazione di Python non è abbastanza uguale all'opzione dei parametri di riferimento di C ++, ma risulta molto simile al modello di passaggio di argomenti del linguaggio C (e altri) in pratica:

  • Gli argomenti immutabili vengono effettivamente passati " per valore ". Oggetti come numeri interi e stringhe vengono passati per riferimento oggetto anziché tramite copia, ma poiché non è possibile modificare oggetti immutabili in atto, l'effetto è molto simile a fare una copia.
  • Gli argomenti mutabili vengono effettivamente passati " per puntatore ". Anche oggetti come elenchi e dizionari vengono passati per riferimento ad oggetti, il che è simile al modo in cui C passa le matrici come puntatori: gli oggetti mutabili possono essere cambiati in atto nella funzione, proprio come le matrici C.

16

Come puoi affermare, devi avere un oggetto mutabile, ma lascia che ti suggerisca di controllare le variabili globali in quanto possono aiutarti o persino risolvere questo tipo di problema!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

esempio:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2

5
Sono stato tentato di pubblicare una risposta simile: l'interrogatore originale potrebbe non aver saputo che ciò che voleva era in realtà utilizzare una variabile globale, condivisa tra le funzioni. Ecco il link che avrei condiviso: stackoverflow.com/questions/423379/… In risposta a @Tim, Stack Overflow non è solo un sito di domande e risposte, è un vasto archivio di conoscenze di riferimento che diventa solo più forte e più ricco di sfumature. come una wiki attiva con più input.
Max P Magee,

13

Molte intuizioni nelle risposte qui, ma penso che un punto aggiuntivo non sia chiaramente menzionato qui esplicitamente. Citando dalla documentazione di Python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"In Python, le variabili a cui viene fatto riferimento solo all'interno di una funzione sono implicitamente globali. Se a una variabile viene assegnato un nuovo valore in qualsiasi punto del corpo della funzione, si presume che sia locale. Se a una variabile viene mai assegnato un nuovo valore all'interno della funzione, la variabile è implicitamente locale e devi dichiararla esplicitamente come "globale". Sebbene all'inizio sia un po 'sorprendente, una considerazione da parte di un momento lo spiega. Da un lato, la richiesta globale di variabili assegnate fornisce una barra contro gli effetti collaterali non intenzionali. d'altra parte, se fosse necessario globale per tutti i riferimenti globali, useresti sempre globale. Dovresti dichiarare come globale ogni riferimento a una funzione integrata o a un componente di un modulo importato. vanificherebbe l'utilità della dichiarazione globale per identificare gli effetti collaterali ".

Anche quando si passa un oggetto mutevole a una funzione, ciò vale comunque. E per me spiega chiaramente la ragione della differenza di comportamento tra l'assegnazione all'oggetto e il funzionamento sull'oggetto nella funzione.

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

L'assegnazione a una variabile globale che non è dichiarata globale crea quindi un nuovo oggetto locale e interrompe il collegamento all'oggetto originale.


10

Ecco la semplice (spero) spiegazione del concetto pass by objectusato in Python.
Ogni volta che passi un oggetto alla funzione, l'oggetto stesso viene passato (l'oggetto in Python è in realtà quello che chiameresti un valore in altri linguaggi di programmazione) non il riferimento a questo oggetto. In altre parole, quando chiami:

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

my_list = [0, 1]
change_me(my_list)

L'oggetto reale - [0, 1] (che sarebbe chiamato un valore in altri linguaggi di programmazione) viene passato. Quindi in effetti la funzione change_meproverà a fare qualcosa del tipo:

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

che ovviamente non cambierà l'oggetto passato alla funzione. Se la funzione fosse simile a questa:

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

Quindi la chiamata comporterebbe:

[0, 1].append(2)

che ovviamente cambierà l'oggetto. Questa risposta lo spiega bene.


2
Il problema è che il compito fa qualcosa di diverso da quello che ti aspetti. Le list = [1, 2, 3]cause riutilizzano il listnome per qualcos'altro e dimenticano l'oggetto passato in origine. Tuttavia, puoi provare list[:] = [1, 2, 3](a proposito il listnome sbagliato per una variabile. Pensare [0, 1] = [1, 2, 3]è una totale assurdità. Comunque, cosa pensi che significhi che l'oggetto stesso viene passato ? Cosa viene copiato nella funzione secondo te?
pepr

Gli oggetti @pepr non sono letterali. Sono oggetti. L'unico modo per parlarne è dare loro dei nomi. Ecco perché è così semplice dopo averlo capito, ma enormemente complicato da spiegare. :-)
Veky,

@Veky: ne sono consapevole. In ogni caso, l'elenco letterale viene convertito nell'oggetto elenco. In realtà, qualsiasi oggetto in Python può esistere senza un nome e può essere utilizzato anche quando non viene assegnato alcun nome. E puoi pensare a loro come a oggetti anonimi. Pensa agli oggetti come elementi di un elenco. Non hanno bisogno di un nome. È possibile accedervi indicizzando o ripetendo l'elenco. Comunque, insisto, [0, 1] = [1, 2, 3]è semplicemente un cattivo esempio. Non c'è niente di simile in Python.
pepr

@pepr: non intendo necessariamente nomi con definizione Python, solo nomi ordinari. Naturalmente alist[2]conta come un nome di un terzo elemento di alist. Ma penso di aver frainteso quale fosse il tuo problema. :-)
Veky,

Argh. Il mio inglese è ovviamente molto peggio del mio Python. :-) Ci proverò ancora una volta. Ho appena detto che devi dare all'oggetto dei nomi solo per parlarne. Con questi "nomi" non intendevo "nomi come definiti da Python". Conosco i meccanismi di Python, non ti preoccupare.
Veky,

8

A parte tutte le grandi spiegazioni su come funziona questa roba in Python, non vedo un semplice suggerimento per il problema. Mentre sembri creare oggetti e istanze, il modo pitone di gestire le variabili di istanza e modificarle è il seguente:

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

    def Change(self):
        self.variable = 'Changed'

Nei metodi di istanza, di solito si fa riferimento agli selfattributi dell'istanza di accesso. È normale impostare attributi di istanza __init__e leggerli o modificarli nei metodi di istanza. Questo è anche il motivo per cui passi selfanche il primo argomento def Change.

Un'altra soluzione sarebbe quella di creare un metodo statico come questo:

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

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var

6

C'è un piccolo trucco per passare un oggetto per riferimento, anche se la lingua non lo rende possibile. Funziona anche in Java, è l'elenco con un elemento. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

È un brutto trucco, ma funziona. ;-P


pè riferimento a un oggetto elenco mutabile che a sua volta memorizza l'oggetto obj. Il riferimento 'p' viene passato in changeRef. All'interno changeRef, viene creato un nuovo riferimento (viene chiamato il nuovo riferimento ref) che punta allo stesso oggetto elenco a cui ppunta. Ma poiché gli elenchi sono modificabili, le modifiche all'elenco sono visibili da entrambi i riferimenti. In questo caso, è stato utilizzato il refriferimento per modificare l'oggetto all'indice 0 in modo che successivamente memorizzi l' PassByReference('Michael')oggetto. La modifica all'oggetto elenco è stata eseguita utilizzando refma questa modifica è visibile a p.
Minh Tran,

Così ora, i riferimenti pe refpuntare a un oggetto lista che contiene il singolo oggetto, PassByReference('Michael'). Quindi ne consegue che p[0].nameritorna Michael. Certo, refora è andato oltre il campo di applicazione e può essere spazzatura raccolta ma lo stesso.
Minh Tran,

Tuttavia, non è stata modificata la variabile dell'istanza privata dell'oggetto nameoriginale PassByReferenceassociato al riferimento obj. In effetti, obj.nametornerà Peter. I suddetti commenti presuppongono la definizione Mark Ransomfornita.
Minh Tran,

Ad ogni modo, non concordo sul fatto che si tratti di un hack (che intendo per riferirsi a qualcosa che funziona ma per ragioni sconosciute, non testate o non intenzionali dall'implementatore). Hai semplicemente sostituito un PassByReferenceoggetto con un altro PassByReferenceoggetto nel tuo elenco e fatto riferimento a quest'ultimo dei due oggetti.
Minh Tran,

5

Ho usato il seguente metodo per convertire rapidamente un paio di codici Fortran in Python. È vero, non è un riferimento dato che è stata posta la domanda originale, ma in alcuni casi è una semplice soluzione.

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

Sì, questo risolve anche il "passaggio per riferimento" nel mio caso d'uso. Ho una funzione che sostanzialmente pulisce i valori in a dicte quindi restituisce il dict. Tuttavia, durante la pulizia può sembrare evidente la necessità di ricostruire una parte del sistema. Pertanto, la funzione non deve solo restituire la pulizia, dictma anche essere in grado di segnalare la ricostruzione. Ho provato a passare un boolriferimento, ma spesso non funziona. Capendo come risolverlo, ho trovato la tua soluzione (fondamentalmente restituire una tupla) per funzionare al meglio, pur non essendo affatto un hack / soluzione alternativa (IMHO).
Kasimir

3

Mentre pass by reference non è niente che si adatta bene a Python e dovrebbe essere usato raramente, ci sono alcune soluzioni alternative che possono effettivamente funzionare per ottenere l'oggetto attualmente assegnato a una variabile locale o addirittura riassegnare una variabile locale dall'interno di una funzione chiamata.

L'idea di base è quella di avere una funzione che possa fare quell'accesso e che possa essere passata come oggetto in altre funzioni o memorizzata in una classe.

Un modo consiste nell'utilizzare global(per variabili globali) o nonlocal(per variabili locali in una funzione) in una funzione wrapper.

def change(wrapper):
    wrapper(7)

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

La stessa idea funziona per leggere ed deleting una variabile.

Per la sola lettura c'è anche un modo più breve di usare semplicemente lambda: xche restituisce un callable che quando chiamato restituisce il valore corrente di x. Questo è un po 'come la "chiamata per nome" utilizzata nelle lingue in un lontano passato.

Passare 3 wrapper per accedere a una variabile è un po 'ingombrante, quindi quelli possono essere raggruppati in una classe che ha un attributo 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

Il supporto "reflection" di Pythons consente di ottenere un oggetto in grado di riassegnare un nome / una variabile in un determinato ambito senza definire esplicitamente le funzioni in tale ambito:

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)

Qui la ByRefclasse include un accesso al dizionario. Pertanto, l'attributo access to wrappedviene tradotto in un accesso item nel dizionario passato. Passando il risultato del builtin localse il nome di una variabile locale, questo finisce per accedere a una variabile locale. La documentazione di Python al 3.5 indica che la modifica del dizionario potrebbe non funzionare ma sembra funzionare per me.


3

dato il modo in cui python gestisce valori e riferimenti ad essi, l'unico modo in cui è possibile fare riferimento a un attributo di istanza arbitrario è il nome:

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

nel codice reale, ovviamente, aggiungerei il controllo degli errori nella ricerca di dict.


3

Poiché i dizionari vengono passati per riferimento, è possibile utilizzare una variabile dict per memorizzare tutti i valori di riferimento al suo interno.

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value

3

Il pass-by-reference in Python è abbastanza diverso dal concetto di pass-by-reference in C ++ / Java.

  • Java & C #: tipi primitivi (include stringa) passano per valore (copia), Il tipo di riferimento viene passato per riferimento (copia indirizzo) in modo che tutte le modifiche apportate al parametro nella funzione chiamata siano visibili al chiamante.
  • C ++: sono consentiti sia il pass-by-reference che il pass-by-value. Se un parametro viene passato per riferimento, è possibile modificarlo o meno a seconda che il parametro sia stato passato come const oppure no. Tuttavia, const o no, il parametro mantiene il riferimento all'oggetto e il riferimento non può essere assegnato per puntare a un oggetto diverso all'interno della funzione chiamata.
  • Python: Python è "pass-by-object-reference", di cui si dice spesso: "I riferimenti agli oggetti vengono passati per valore". [Leggi qui] 1. Sia il chiamante che la funzione si riferiscono allo stesso oggetto, ma il parametro nella funzione è una nuova variabile che contiene solo una copia dell'oggetto nel chiamante. Come C ++, un parametro può essere modificato o non in funzione - Dipende dal tipo di oggetto passato. per esempio; Un tipo di oggetto immutabile non può essere modificato nella funzione chiamata mentre un oggetto mutabile può essere aggiornato o reinizializzato. Una differenza cruciale tra l'aggiornamento o la riassegnazione / reinizializzazione della variabile mutabile è che il valore aggiornato viene riflesso nella funzione chiamata mentre il valore reinizializzato no. L'ambito di qualsiasi assegnazione di nuovo oggetto a una variabile mutabile è locale alla funzione in Python. Gli esempi forniti da @ blair-conrad sono grandiosi per capirlo.

1
Vecchio ma mi sento obbligato a correggerlo. Le stringhe vengono passate per riferimento sia in Java che in C #, NON in base al valore
John,

2

Poiché l'esempio è orientato agli oggetti, è possibile apportare le seguenti modifiche per ottenere un risultato simile:

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'

1
Anche se funziona. Non è per riferimento. È "passa per riferimento oggetto".
Bishwas Mishra il

1

È possibile utilizzare semplicemente una classe vuota come istanza per archiviare oggetti di riferimento poiché gli attributi interni degli oggetti sono memorizzati in un dizionario di istanza. Vedi l'esempio

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)

1

Dal momento che sembra non essere menzionato da nessuna parte un approccio per simulare riferimenti noti ad esempio da C ++ consiste nell'utilizzare una funzione di "aggiornamento" e passarla al posto della variabile effettiva (o meglio, "nome"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Ciò è utile soprattutto per "riferimenti non disponibili" o in una situazione con più thread / processi (rendendo sicura la funzione di aggiornamento thread / multiprocessing).

Ovviamente quanto sopra non consente la lettura del valore, ma solo l'aggiornamento.

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.