Vraag Hoe geef ik een variabele door te verwijzen door?


De documentatie van Python lijkt onduidelijk of parameters worden doorgegeven door verwijzing of waarde, en de volgende code produceert de ongewijzigde waarde 'Origineel'

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

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

Kan ik iets doen door de werkelijke referentie door te geven?


2053
2018-06-12 10:23


oorsprong


antwoorden:


Argumenten zijn geslaagd voor opdracht. De reden hiervoor is tweeledig:

  1. de doorgegeven parameter is eigenlijk een referentie naar een object (maar de referentie wordt door een waarde doorgegeven)
  2. sommige gegevenstypen kunnen worden gewijzigd, maar andere zijn dat niet

Zo:

  • Als u een passeert veranderlijk object in een methode, de methode krijgt een verwijzing naar datzelfde object en je kunt het naar hartenlust van mutatie maken, maar als je de referentie in de methode opnieuw verbindt, weet de buitenste scope niets van het, en nadat je klaar bent, de buitenste referentie zal nog steeds naar het oorspronkelijke object wijzen.

  • Als je een onveranderlijk object tegen een methode, je kunt de outer reference nog steeds niet opnieuw binden, en je kunt het object zelfs niet muteren.

Om het nog duidelijker te maken, laten we enkele voorbeelden geven.

Lijst - een veranderlijk type

Laten we proberen de lijst aan te passen die is doorgegeven aan een methode:

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)

Output:

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

Omdat de opgegeven parameter een verwijzing is naar outer_list, niet een kopie ervan, we kunnen de muterende lijstmethoden gebruiken om het te veranderen en de wijzigingen weerspiegeld te krijgen in de buitenste scope.

Laten we nu eens kijken wat er gebeurt als we de referentie die als parameter is doorgegeven proberen te wijzigen:

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)

Output:

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

Sinds de the_list parameter werd doorgegeven door waarde, een nieuwe lijst toewijzen daaraan had geen effect dat de code buiten de methode kon zien. De the_list was een kopie van de outer_list referentie, en dat hadden we the_list wijzen naar een nieuwe lijst, maar er was geen manier om te veranderen waar outer_list wees.

String - een onveranderlijk type

Het is onveranderlijk, dus er is niets dat we kunnen doen om de inhoud van de string te veranderen

Laten we nu proberen de referentie te veranderen

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)

Output:

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

Nogmaals, sinds de the_string parameter werd doorgegeven door waarde, het toewijzen van een nieuwe string eraan had geen effect dat de code buiten de methode kon zien. De the_string was een kopie van de outer_string referentie, en dat hadden we the_string naar een nieuwe tekenreeks, maar er was geen manier om te wijzigen waar outer_string wees.

Ik hoop dat dit de zaken een beetje oplost.

BEWERK: Er is opgemerkt dat dit de vraag niet beantwoordt die @David oorspronkelijk vroeg: "Is er iets dat ik kan doen om de variabele door te geven door daadwerkelijke referentie?". Laten we daaraan werken.

Hoe komen we hier omheen?

Zoals uit het antwoord van @ Andrea blijkt, kun je de nieuwe waarde retourneren. Dit verandert niets aan de manier waarop dingen worden doorgegeven, maar geeft u wel de informatie die u wilt terughalen:

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)

Als u echt een retourwaarde wilt vermijden, kunt u een klasse maken om uw waarde vast te houden en deze door te geven aan de functie of een bestaande klasse te gebruiken, zoals een lijst:

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

Hoewel dit een beetje omslachtig lijkt.


2221
2018-06-12 11:18



Het probleem komt van een verkeerd begrip van de variabelen in Python. Als je gewend bent aan de meeste traditionele talen, heb je een mentaal model van wat er gebeurt in de volgende volgorde:

a = 1
a = 2

Je gelooft dat a is een geheugenlocatie waarin de waarde wordt opgeslagen 1, wordt vervolgens bijgewerkt om de waarde op te slaan 2. Dat is niet hoe dingen werken in Python. Liever, a begint als een verwijzing naar een object met de waarde 1, wordt dan opnieuw toegewezen als een verwijzing naar een object met de waarde 2. Die twee objecten kunnen zelfs blijven bestaan ​​naast elkaar a verwijst niet meer naar de eerste; in feite kunnen ze worden gedeeld door een aantal andere referenties binnen het programma.

Wanneer u een functie met een parameter aanroept, wordt een nieuwe verwijzing gemaakt die verwijst naar het doorgegeven object. Dit staat los van de referentie die werd gebruikt in de functieaanroep, dus er is geen manier om die verwijzing bij te werken en te verwijzen naar een verwijzing nieuw object. In jouw voorbeeld:

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

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

self.variable is een verwijzing naar het stringobject 'Original'. Wanneer je belt Change u maakt een tweede referentie var naar het object. Binnen de functie wijst u de referentie opnieuw toe var naar een ander string-object 'Changed', maar de referentie self.variable is gescheiden en verandert niet.

De enige manier om dit te omzeilen is om een ​​veranderlijk object door te geven. Omdat beide verwijzingen naar hetzelfde object verwijzen, worden alle wijzigingen in het object op beide plaatsen weergegeven.

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

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

529
2017-11-15 17:45



Ik vond de andere antwoorden vrij lang en ingewikkeld, dus ik heb dit eenvoudige diagram gemaakt om uit te leggen hoe Python variabelen en parameters behandelt. enter image description here


242
2017-09-04 16:05



Het is geen pass-by-value of pass-by-reference - het is call-by-object. Zie dit, door Fredrik Lundh:

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

Hier is een belangrijk citaat:

"... variabelen [namen] zijn niet voorwerpen; ze kunnen niet worden aangegeven met andere variabelen of worden aangeduid door objecten. "

In uw voorbeeld, wanneer de Change methode wordt genoemd - a namespace is ervoor gemaakt; en var wordt een naam, binnen die naamruimte, voor het tekenreeksobject 'Original'. Dat object heeft dan een naam in twee naamruimten. next, var = 'Changed' bindt var naar een nieuw stringobject, en dus vergeet de naamruimte van de methode 'Original'. Ten slotte wordt die naamruimte vergeten en de tekenreeks 'Changed' samen met het.


219
2018-06-12 12:55



Denk aan dingen die worden doorgegeven opgedragen in plaats van door verwijzing / naar waarde. Op die manier is het altijd duidelijk, wat er gebeurt, zolang je begrijpt wat er gebeurt tijdens de normale toewijzing.

Dus, bij het doorgeven van een lijst aan een functie / methode, wordt de lijst toegewezen aan de parameternaam. Als u de lijst toevoegt, wordt de lijst gewijzigd. De lijst opnieuw toewijzen binnen de functie zal de oorspronkelijke lijst niet veranderen, omdat:

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

Omdat onveranderlijke typen niet kunnen worden gewijzigd, zij lijken zoals doorgegeven worden door waarde - het doorgeven van een int in een functie betekent het toewijzen van de int aan de parameter functions. Je kunt dat alleen maar opnieuw toewijzen, maar het verandert de waarde van de oorspronkelijke variabelen niet.


142
2018-06-12 12:17



Effbot (ook bekend als Fredrik Lundh) heeft de variabele passingstijl van Python omschreven als call-by-object: http://effbot.org/zone/call-by-object.htm

Objecten worden op de hoop toegewezen en verwijzingen ernaar kunnen overal worden doorgegeven.

  • Wanneer u een opdracht maakt zoals x = 1000, er wordt een woordenboekgegeven gemaakt dat de tekenreeks "x" in de huidige naamruimte toewijst aan een aanwijzer naar het geheel getal-object dat duizend bevat.

  • Wanneer u "x" bijwerkt x = 2000, een nieuw object met een geheel getal wordt gemaakt en het woordenboek wordt bijgewerkt om naar het nieuwe object te verwijzen. Het oude duizend object is ongewijzigd (en kan al dan niet levend zijn afhankelijk van of iets anders naar het object verwijst).

  • Wanneer je een nieuwe opdracht doet zoals y = x, er wordt een nieuw woordenboekitem "y" gemaakt dat naar hetzelfde object verwijst als het item voor "x".

  • Objecten zoals strings en gehele getallen zijn onveranderlijk. Dit betekent eenvoudigweg dat er geen methoden zijn die het object kunnen wijzigen nadat het is gemaakt. Zodra het geheel-getal-object bijvoorbeeld duizend is gemaakt, zal het nooit veranderen. Wiskunde wordt gedaan door nieuwe integere objecten te maken.

  • Objecten zoals lijsten zijn veranderlijk. Dit betekent dat de inhoud van het object kan worden gewijzigd door alles dat naar het object verwijst. Bijvoorbeeld, x = []; y = x; x.append(10); print y zal afdrukken [10]. De lege lijst is gemaakt. Zowel "x" en "y" wijzen naar dezelfde lijst. De toevoegen methode muteert (update) het lijstobject (zoals het toevoegen van een record aan een database) en het resultaat is zichtbaar voor zowel "x" als "y" (net zoals een database-update zichtbaar zou zijn voor elke verbinding met die database).

Ik hoop dat het probleem voor u duidelijk wordt.


53
2018-03-29 04:41



Technisch gezien, Python gebruikt altijd pass-by-referentiewaarden. Ik ga herhalen mijn andere antwoord om mijn verklaring te ondersteunen.

Python gebruikt altijd pass-by-reference-waarden. Er is geen uitzondering. Elke variabele toewijzing betekent het kopiëren van de referentiewaarde. Geen uitzondering. Elke variabele is de naam die aan de referentiewaarde is gebonden. Altijd.

U kunt nadenken over een referentiewaarde als het adres van het doelobject. Het adres wordt bij gebruik automatisch afgeleid. Op deze manier lijkt het alsof u met de referentiewaarde werkt, direct met het doelobject. Maar er zit altijd een verwijzing tussen, één stap meer om naar het doel te springen.

Hier is het voorbeeld dat bewijst dat Python doorgeven door verwijzing gebruikt:

Illustrated example of passing the argument

Als het argument door waarde werd doorgegeven, het uiterlijke lst kan niet worden gewijzigd. De groen zijn de doelobjecten (het zwart is de waarde die erin is opgeslagen, het rood is het objecttype), het gele is het geheugen met de referentiewaarde binnen - getekend als de pijl. De blauwe vaste pijl is de referentiewaarde die is doorgegeven aan de functie (via het gestreepte blauwe pijlpad). Het lelijke donkergele is het interne woordenboek. (Het kan ook als een groene ellips getekend worden, de kleur en de vorm zeggen alleen dat het intern is.)

U kunt de id() ingebouwde functie om te leren wat de referentiewaarde is (dat wil zeggen, het adres van het doelobject).

In gecompileerde talen is een variabele een geheugenruimte die de waarde van het type kan vastleggen. In Python is een variabele een naam (intern gevangen als een tekenreeks) gebonden aan de referentievariabele die de referentiewaarde aan het doelobject bevat. De naam van de variabele is de sleutel in het interne woordenboek, het waardegedeelte van dat woordenboekitem slaat de referentiewaarde op naar het doel.

Referentiewaarden zijn verborgen in Python. Er is geen expliciet gebruikerstype voor het opslaan van de referentiewaarde. U kunt echter een lijstelement (of element in een ander geschikt containertype) gebruiken als de referentievariabele, omdat alle containers de elementen ook opslaan als verwijzingen naar de doelobjecten. Met andere woorden, elementen zijn eigenlijk niet opgenomen in de container - alleen de verwijzingen naar elementen zijn.


53
2017-09-15 18:53



Een eenvoudige truc die ik normaal gebruik is om het gewoon in een lijst te verpakken:

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

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

(Ja, ik weet dat dit onhandig kan zijn, maar soms is het eenvoudig genoeg om dit te doen.)


36
2017-08-05 22:52



(edit - Blair heeft zijn enorm populaire antwoord bijgewerkt, zodat het nu correct is)

Ik denk dat het belangrijk is om op te merken dat het huidige bericht met de meeste stemmen (door Blair Conrad), terwijl het correct is met betrekking tot het resultaat, misleidend is en op basis van de definities niet correct is. Hoewel er veel talen (zoals C) zijn waarmee de gebruiker kan doorgeven door te verwijzen of door te geven op waarde, is Python niet een van hen.

Het antwoord van David Cournapeau wijst op het echte antwoord en legt uit waarom het gedrag in Blair Conrad's post correct lijkt te zijn, terwijl de definities dat niet zijn.

In de mate dat Python de waarde passeert, hebben alle talen een waardeclassificatie, omdat een deel van de gegevens (of het nu een "waarde" of een "referentie" is) moet worden verzonden. Dat betekent echter niet dat Python waarde heeft, in die zin dat een C-programmeur erover zou denken.

Als je het gedrag wilt, is het antwoord van Blair Conrad prima. Maar als je de redenen wilt weten waarom Python niet waardevol is of door te geven, lees dan het antwoord van David Cournapeau.


34
2018-06-27 12:33



Er zijn geen variabelen in Python

De sleutel tot het begrijpen van het doorgeven van parameters is om te stoppen met denken aan "variabelen". Er zijn namen en objecten in Python en samen zijn ze verschijnen als variabelen, maar het is handig om altijd de drie te onderscheiden.

  1. Python heeft namen en objecten.
  2. Toewijzing bindt een naam aan een object.
  3. Het doorgeven van een argument aan een functie bindt ook een naam (de parameternaam van de functie) aan een object.

Dat is alles wat er is. Veranderbaarheid is niet relevant voor deze vraag.

Voorbeeld:

a = 1

Dit bindt de naam a naar een object van het type integer dat de waarde 1 bevat.

b = x

Dit bindt de naam b naar hetzelfde object als de naam x is momenteel gebonden aan. Daarna de naam b heeft niets met de naam te maken x meer.

Zie secties 3.1 en 4.2 in de taalreferentie van Python 3.


Dus in de code die in de vraag wordt getoond, de verklaring self.Change(self.variable) bindt de naam var (in het kader van de functie Change) naar het object dat de waarde bevat 'Original' en de opdracht var = 'Changed' (in het lichaam van functie Change) wijst diezelfde naam opnieuw toe: aan een ander object (dat toevallig ook een string vasthoudt, maar mogelijk ook iets anders was).


25
2018-02-11 11:29



Je hebt hier echt heel goede antwoorden.

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