Jak przekazać zmienną przez odniesienie?

2627

Dokumentacja Pythona wydaje się niejasna, czy parametry są przekazywane przez odwołanie czy wartość, a poniższy kod generuje niezmienioną wartość „Original”

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

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

Czy jest coś, co mogę zrobić, aby przekazać zmienną przez rzeczywiste odwołanie?

David Sykes
źródło
23
Krótkie wyjaśnienie / wyjaśnienie znajduje się w pierwszej odpowiedzi na to pytanie dotyczące przepełnienia stosu . Ponieważ ciągi są niezmienne, nie zostaną zmienione i zostanie utworzona nowa zmienna, dlatego zmienna „zewnętrzna” nadal ma tę samą wartość.
PhilS,
10
Kod w odpowiedzi BlairConrad jest dobry, ale wyjaśnienia dostarczone przez DavidCournapeau i DarenThomas są poprawne.
Ethan Furman,
70
Przed przeczytaniem wybranej odpowiedzi zastanów się nad przeczytaniem tego krótkiego tekstu Inne języki mają „zmienne”, Python ma „nazwy” . Pomyśl o „nazwach” i „obiektach” zamiast „zmiennych” i „referencjach” i powinieneś unikać wielu podobnych problemów.
lqc
24
Dlaczego niepotrzebnie korzysta z klasy? To nie jest Java: P
Peter R
2
innym obejściem jest utworzenie „referencji” opakowania w następujący sposób: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
Robert

Odpowiedzi:

2830

Argumenty są przekazywane przez przypisanie . Uzasadnienie tego jest dwojakie:

  1. przekazany parametr jest tak naprawdę referencją do obiektu (ale referencja jest przekazywana przez wartość)
  2. niektóre typy danych są zmienne, ale inne nie

Więc:

  • Jeśli przekażesz zmienny obiekt do metody, metoda otrzyma odwołanie do tego samego obiektu i możesz zmutować go ku uciesze twojego serca, ale jeśli ponownie powiążesz odniesienie w metodzie, zakres zewnętrzny nie będzie o tym nic wiedział, a po tym gdy skończysz, odniesienie zewnętrzne nadal będzie wskazywać na oryginalny obiekt.

  • Jeśli przekażesz niezmienny obiekt do metody, nadal nie możesz ponownie powiązać zewnętrznego odniesienia, a nawet nie możesz mutować obiektu.

Aby było jeszcze bardziej jasne, podajmy kilka przykładów.

Lista - typ zmienny

Spróbujmy zmodyfikować listę, która została przekazana do metody:

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)

Wynik:

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

Ponieważ przekazany parametr jest odniesieniem do outer_list, a nie jego kopią, możemy użyć metod listy mutacji, aby go zmienić, a zmiany odzwierciedlić w zewnętrznym zakresie.

Zobaczmy teraz, co się stanie, gdy spróbujemy zmienić odwołanie, które zostało przekazane jako parametr:

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)

Wynik:

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

Ponieważ the_listparametr został przekazany przez wartość, przypisanie mu nowej listy nie miało żadnego wpływu na to, że kod poza metodą mógł zobaczyć. the_listByła kopia outer_listodniesienia, i mieliśmy the_listpunkt do nowej listy, ale nie było sposobu, aby zmienić miejsce outer_listspiczasty.

String - niezmienny typ

Jest niezmienny, więc nie możemy nic zrobić, aby zmienić zawartość łańcucha

Teraz spróbujmy zmienić odniesienie

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)

Wynik:

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

Ponownie, ponieważ the_stringparametr został przekazany przez wartość, przypisanie do niego nowego ciągu nie wpłynęło na to, że kod poza metodą mógł zobaczyć. the_stringByła kopia outer_stringodniesienia, i mieliśmy the_stringpunkt do nowego łańcucha, ale nie było sposobu, aby zmienić miejsce outer_stringspiczasty.

Mam nadzieję, że to trochę wyjaśni.

EDYCJA: Zauważono, że to nie odpowiada na pytanie, które @David pierwotnie zadał: „Czy jest coś, co mogę zrobić, aby przekazać zmienną przez faktyczne odwołanie?” Popracujmy nad tym.

Jak sobie z tym poradzić?

Jak pokazuje odpowiedź @ Andrei, możesz zwrócić nową wartość. Nie zmienia to sposobu przekazywania rzeczy, ale pozwala uzyskać informacje, które chcesz odzyskać:

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)

Jeśli naprawdę chcesz uniknąć używania wartości zwracanej, możesz utworzyć klasę, która będzie przechowywać twoją wartość i przekaże ją do funkcji lub użyj istniejącej klasy, takiej jak 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])

Chociaż wydaje się to trochę kłopotliwe.

Blair Conrad
źródło
165
To samo dotyczy C, kiedy zdasz „przez referencję”, faktycznie przekazujesz wartość referencji ... Zdefiniuj „przez referencję”: P
Andrea Ambu
104
Nie jestem pewien, czy rozumiem twoje warunki. Od jakiegoś czasu nie grałem w C, ale kiedy tam byłem, nie było „pass by referencji” - można było przekazać rzeczy i zawsze było to przekazywanie według wartości, więc cokolwiek było na liście parametrów został skopiowany. Ale czasami chodziło o wskaźnik, który można było śledzić do fragmentu pamięci (prymitywny, tablicowy, strukturalny, cokolwiek), ale nie można było zmienić wskaźnika, który został skopiowany z zewnętrznego zakresu - kiedy skończysz z funkcją , oryginalny wskaźnik nadal wskazywał ten sam adres. C ++ wprowadził odwołania, które zachowywały się inaczej.
Blair Conrad
34
@Zac Bowling Naprawdę nie rozumiem, w jaki sposób to, co mówisz, ma znaczenie, w sensie praktycznym, dla tej odpowiedzi. Jeśli przybysz Python chcieliście wiedzieć o przepuszczenie przez ref / val, a następnie na wynos z tej odpowiedzi jest: 1- Ty może używać odniesienie że funkcja odbiera jako jego argumenty, aby zmodyfikować „na zewnątrz” wartość zmiennej, tak długo, ponieważ nie przypisujesz parametru do nowego obiektu. 2 - Przypisanie do niezmiennego typu zawsze tworzy nowy obiekt, który przerywa odniesienie do zmiennej zewnętrznej.
Cam Jackson
11
@CamJackson, potrzebujesz lepszego przykładu - liczby są również niezmiennymi obiektami w Pythonie. Poza tym, czy nie byłoby prawdą stwierdzenie, że każde przypisanie bez subskrybowania po lewej stronie równości spowoduje ponowne przypisanie nazwy do nowego obiektu, niezależnie od tego, czy jest niezmienny? def Foo(alist): alist = [1,2,3]będzie nie modyfikować zawartość listy z punktu widzenia rozmówców.
Mark Ransom,
59
-1. Pokazany kod jest dobry, wyjaśnienie, w jaki sposób jest całkowicie błędne. Zobacz odpowiedzi DavidCournapeau lub DarenThomas, aby uzyskać prawidłowe wyjaśnienie, dlaczego.
Ethan Furman
685

Problem wynika z niezrozumienia, jakie zmienne są w Pythonie. Jeśli jesteś przyzwyczajony do większości tradycyjnych języków, masz mentalny model tego, co dzieje się w następującej kolejności:

a = 1
a = 2

Uważasz, że ajest to miejsce w pamięci, które przechowuje wartość 1, a następnie jest aktualizowane w celu zapisania wartości 2. Nie tak to działa w Pythonie. Zamiast tego azaczyna się jako odwołanie do obiektu o wartości 1, a następnie zostaje ponownie przypisane jako odwołanie do obiektu o wartości 2. Te dwa obiekty mogą nadal współistnieć, mimo że anie odnoszą się już do pierwszego; w rzeczywistości mogą być one udostępniane przez dowolną liczbę innych odniesień w programie.

Kiedy wywołujesz funkcję z parametrem, tworzone jest nowe odwołanie, które odnosi się do przekazanego obiektu. Jest to oddzielne od odwołania, które zostało użyte w wywołaniu funkcji, więc nie ma sposobu, aby zaktualizować to odniesienie i sprawić, by odnosiło się do nowy obiekt W twoim przykładzie:

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

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

self.variablejest odwołaniem do obiektu ciągu 'Original'. Kiedy dzwonisz Change, tworzysz drugie odniesienie vardo obiektu. Wewnątrz funkcji ponownie przypisujesz odwołanie vardo innego obiektu łańcuchowego 'Changed', ale odwołanie self.variablejest osobne i nie zmienia się.

Jedynym sposobem na obejście tego jest ominięcie zmiennego obiektu. Ponieważ oba odwołania odnoszą się do tego samego obiektu, wszelkie zmiany obiektu są odzwierciedlane w obu miejscach.

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

def Change(self, var):
    var[0] = 'Changed'
Mark Ransom
źródło
106
Dobre zwięzłe wyjaśnienie. Twój akapit „Kiedy wywołujesz funkcję ...” jest jednym z najlepszych wyjaśnień, jakie słyszałem o dość tajemniczej frazie, że „parametry funkcji Pythona są referencjami przekazywanymi przez wartość”. Myślę, że jeśli rozumiesz sam akapit, wszystko inne ma sens i płynie z niego logiczny wniosek. Następnie musisz tylko wiedzieć, kiedy tworzysz nowy obiekt i kiedy modyfikujesz istniejący.
Cam Jackson
3
Ale jak zmienić przypisanie referencji? Myślałem, że nie możesz zmienić adresu „var”, ale twój ciąg „Changed” będzie teraz przechowywany w adresie pamięci „var”. Twój opis sprawia, że ​​„Zmieniony” i „Oryginalny” należą do różnych miejsc w pamięci, a ty po prostu przełączasz „var” na inny adres. Czy to jest poprawne?
Glassjawed
8
@Glassjawed, myślę, że rozumiesz. „Zmienione” i „Oryginalne” to dwa różne obiekty łańcuchowe o różnych adresach pamięci, a „var” zmienia się z jednego na drugi na drugi.
Mark Ransom,
użycie funkcji id () pomaga wyjaśnić sprawy, ponieważ wyjaśnia, kiedy Python tworzy nowy obiekt (tak myślę, tak czy inaczej).
Tim Richardson,
1
@MinhTran w najprostszych słowach odniesienie to coś, co „odnosi się” do obiektu. Fizyczna reprezentacja tego jest najprawdopodobniej wskaźnikiem, ale jest to po prostu szczegół implementacji. To naprawdę jest abstrakcyjne pojęcie w sercu.
Mark Ransom,
329

Znalazłem inne odpowiedzi raczej długie i skomplikowane, dlatego stworzyłem ten prosty schemat, aby wyjaśnić sposób, w jaki Python traktuje zmienne i parametry. wprowadź opis zdjęcia tutaj

Zenadix
źródło
2
cudownie, ułatwia dostrzeżenie subtelnej różnicy, że istnieje zadanie pośrednie, co nie jest oczywiste dla zwykłego obserwatora. +1
użytkownik22866
9
Nie ma znaczenia, czy A można modyfikować, czy nie. Jeśli przypiszesz coś innego do B, A się nie zmieni . Jeśli obiekt można modyfikować, możesz go mutować. Ale to nie ma nic wspólnego z przypisaniem bezpośrednio do nazwiska ...
Martijn Pieters
1
@Martijn Masz rację. Usunąłem część odpowiedzi, która wspomina o zmienności. Nie sądzę, że może być już łatwiej.
Zenadix,
4
Dzięki za aktualizację, znacznie lepiej! Tym, co dezorientuje większość ludzi, jest przypisanie do subskrypcji; na przykład B[0] = 2, w porównaniu z bezpośrednim przypisaniu B = 2.
Martijn Pieters
11
„A jest przypisane do B.” Czy to nie jest dwuznaczne? Myślę, że w zwykłym języku angielskim, które mogą oznaczać albo A=Balbo B=A.
Hatszepsut
240

Nie jest on ani przekazywany przez wartość, ani przekazywany przez referencję - jest to wywołanie obiektu. Zobacz to, autorstwa Fredrika Lundha:

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

Oto znaczący cytat:

„... zmienne [nazwy] nie są obiektami; nie mogą być oznaczane innymi zmiennymi ani wskazywane przez obiekty.”

W twoim przykładzie, gdy Changewywoływana jest metoda - tworzona jest dla niej przestrzeń nazw ; i varstaje się nazwą w obrębie przestrzeni nazw dla obiektu typu string 'Original'. Ten obiekt ma następnie nazwę w dwóch przestrzeniach nazw. Następnie var = 'Changed'wiąże varsię z nowym obiektem łańcuchowym, dlatego przestrzeń nazw metody zapomina 'Original'. Wreszcie przestrzeń nazw zostaje zapomniana, a ciąg 'Changed'wraz z nią.

David Cournapeau
źródło
21
Trudno mi kupić. Dla mnie jest tak jak Java, parametry są wskaźnikami do obiektów w pamięci, a wskaźniki te są przekazywane przez stos lub rejestry.
Luciano,
10
To nie jest jak Java. Jednym z przypadków, w których nie jest to to samo, są obiekty niezmienne. Pomyśl o trywialnej funkcji lambda x: x. Zastosuj to dla x = [1, 2, 3] i x = (1, 2, 3). W pierwszym przypadku zwrócona wartość będzie kopią danych wejściowych i identyczna w drugim przypadku.
David Cournapeau,
26
Nie, to dokładnie jak semantyka Java dla obiektów. Nie jestem pewien, co masz na myśli przez „W pierwszym przypadku zwrócona wartość będzie kopią danych wejściowych i będzie identyczna w drugim przypadku”. ale to stwierdzenie wydaje się po prostu błędne.
Mike Graham
23
Jest dokładnie taki sam jak w Javie. Odwołania do obiektów są przekazywane według wartości. Każdy, kto myśli inaczej, powinien dołączyć kod Pythona do swapfunkcji, która może zamienić dwie referencje, w ten sposób: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann
24
Jest dokładnie taki sam jak Java, gdy przekazujesz obiekty w Javie. Jednak Java ma również operacje podstawowe, które są przekazywane przez skopiowanie wartości operacji podstawowej. Różnią się więc w tym przypadku.
Claudiu
174

Pomyśl o przekazywaniu rzeczy przez przypisanie zamiast przez referencję / wartość. W ten sposób zawsze jest jasne, co się dzieje, dopóki rozumiesz, co dzieje się podczas normalnego zadania.

Tak więc, gdy przekazujemy listę do funkcji / metody, lista jest przypisywana do nazwy parametru. Dołączenie do listy spowoduje modyfikację listy. Ponowne przypisanie listy wewnątrz funkcji nie zmieni oryginalnej listy, ponieważ:

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

Ponieważ typy niezmienne nie mogą być modyfikowane, wydają się są przekazywane przez wartość - przekazanie int do funkcji oznacza przypisanie int do parametru funkcji. Możesz tylko to zmienić, ale nie zmieni to oryginalnej wartości zmiennych.

Daren Thomas
źródło
3
Na pierwszy rzut oka wydaje się, że ta odpowiedź omija pierwotne pytanie. Po drugim czytaniu zdałem sobie sprawę, że to wyjaśnia sprawę. Dobrą kontynuację tej koncepcji „przypisywania nazw” można znaleźć tutaj: Code Like a Pythonista: Idiomatic Python
Christian Groleau
65

Effbot (znany również jako Fredrik Lundh) opisał zmienny styl przekazywania Pythona jako call-by-object: http://effbot.org/zone/call-by-object.htm

Obiekty są przydzielane na stercie, a wskaźniki do nich można przekazywać w dowolnym miejscu.

  • Podczas wykonywania przypisania, takiego jak x = 1000, tworzony jest wpis słownika, który odwzorowuje ciąg „x” w bieżącej przestrzeni nazw na wskaźnik na obiekt liczb całkowitych zawierający tysiąc.

  • Po zaktualizowaniu „x” za pomocą x = 2000, tworzony jest nowy obiekt liczb całkowitych, a słownik jest aktualizowany, aby wskazywał na nowy obiekt. Stary tysiąc obiektów pozostaje niezmieniony (i może być żywy, w zależności od tego, czy coś jeszcze odnosi się do obiektu).

  • Kiedy wykonujesz nowe przypisanie, takie jak y = x, tworzony jest nowy wpis słownika „y”, który wskazuje ten sam obiekt co wpis dla „x”.

  • Obiekty takie jak łańcuchy i liczby całkowite są niezmienne . Oznacza to po prostu, że nie ma metod, które mogłyby zmienić obiekt po jego utworzeniu. Na przykład, gdy zostanie utworzony obiekt liczby całkowitej tysiąc, nigdy się nie zmieni. Matematyka odbywa się poprzez tworzenie nowych obiektów całkowitych.

  • Obiekty takie jak listy można modyfikować . Oznacza to, że zawartość obiektu można zmienić przez cokolwiek, co wskazuje na obiekt. Na przykład x = []; y = x; x.append(10); print ywydrukuje [10]. Pusta lista została utworzona. Zarówno „x”, jak i „y” wskazują na tę samą listę. Metoda Append mutuje (aktualizuje) obiekt listy (jak dodanie rekordu do bazy danych), a wynik jest widoczny zarówno dla „x”, jak i „y” (tak jak aktualizacja bazy danych byłaby widoczna dla każdego połączenia z tą bazą danych).

Mam nadzieję, że to wyjaśni ci problem.

Raymond Hettinger
źródło
2
Naprawdę doceniam naukę na ten temat od programisty. Czy to prawda, że id()funkcja zwraca wartość wskaźnika (odwołania do obiektu), jak sugeruje odpowiedź pepr?
Uczciwy Abe
4
@HonestAbe Tak, w CPython id () zwraca adres. Ale w innych pytonach, takich jak PyPy i Jython, id () jest tylko unikalnym identyfikatorem obiektu.
Raymond Hettinger
59

Technicznie, Python zawsze używa wartości referencyjnych pass by . Powtórzę moją drugą odpowiedź na poparcie mojego oświadczenia.

Python zawsze używa wartości przekazywanych przez referencje. Nie ma żadnego wyjątku. Każde przypisanie zmiennej oznacza skopiowanie wartości odniesienia. Bez wyjątku. Każda zmienna jest nazwą związaną z wartością odniesienia. Zawsze.

Możesz myśleć o wartości odniesienia jako adresie obiektu docelowego. Adres jest automatycznie usuwany z listy przy użyciu. W ten sposób, pracując z wartością odniesienia, wydaje się, że pracujesz bezpośrednio z obiektem docelowym. Ale pomiędzy nimi zawsze jest odniesienie, jeszcze jeden krok, aby przejść do celu.

Oto przykład, który dowodzi, że Python używa przekazywania przez referencję:

Ilustrowany przykład przekazania argumentu

Jeśli argument został przekazany przez wartość, lstnie można zmienić wartości zewnętrznej . Zielony to obiekty docelowe (czarny to wartość przechowywana w środku, czerwony to typ obiektu), żółty to pamięć z wartością odniesienia w środku - narysowaną jako strzałka. Niebieska stała strzałka to wartość odniesienia, która została przekazana do funkcji (za pomocą przerywanej ścieżki niebieskiej strzałki). Brzydka ciemnożółta to wewnętrzny słownik. (W rzeczywistości można go narysować również jako zieloną elipsę. Kolor i kształt mówi tylko, że jest wewnętrzny).

Za pomocą id()wbudowanej funkcji można dowiedzieć się, co to jest wartość odniesienia (to znaczy adres obiektu docelowego).

W skompilowanych językach zmienna to przestrzeń pamięci, która jest w stanie uchwycić wartość typu. W Pythonie zmienna jest nazwą (przechwyconą wewnętrznie jako ciąg) powiązaną ze zmienną referencyjną, która przechowuje wartość referencyjną do obiektu docelowego. Nazwa zmiennej jest kluczem w wewnętrznym słowniku, część wartości tego elementu słownika przechowuje wartość odniesienia do celu.

Wartości referencyjne są ukryte w Pythonie. Nie ma żadnego jawnego typu użytkownika do przechowywania wartości odniesienia. Można jednak użyć elementu listy (lub elementu w dowolnym innym odpowiednim typie kontenera) jako zmiennej referencyjnej, ponieważ wszystkie kontenery przechowują elementy również jako referencje do obiektów docelowych. Innymi słowy, elementy faktycznie nie są zawarte w kontenerze - są tylko odniesienia do elementów.

pepr
źródło
1
W rzeczywistości jest to potwierdzane przez podanie wartości odniesienia. +1 za tę odpowiedź, chociaż przykład nie był dobry.
BugShotGG
30
Opracowanie nowej terminologii (takiej jak „przekazanie przez wartość odniesienia” lub „wywołanie obiektu” nie jest pomocne). „Call by (wartość | referencja | nazwa)” to standardowe warunki. „odniesienie” jest terminem standardowym. Przekazywanie referencji przez wartość dokładnie opisuje zachowanie Pythona, Javy i wielu innych języków, przy użyciu standardowej terminologii.
cayhorstmann
4
@cayhorstmann: Problem polega na tym, że zmienna Python nie ma tego samego znaczenia terminologicznego jak w innych językach. W ten sposób wywołanie przez odniesienie nie pasuje tutaj dobrze. Jak dokładnie zdefiniujesz termin referencyjny ? Nieformalnie sposób w języku Python można łatwo opisać jako przekazywanie adresu obiektu. Ale to nie pasuje do potencjalnie rozproszonej implementacji Pythona.
pepr,
1
Podoba mi się ta odpowiedź, ale możesz zastanowić się, czy przykład naprawdę pomaga lub szkodzi przepływowi. Ponadto, jeśli zastąpisz „wartość odniesienia” „odniesieniem do obiektu”, użyłbyś terminologii, którą moglibyśmy uznać za „oficjalną”, jak widać tutaj: Definiowanie funkcji
Uczciwe Abe
2
Na końcu cytatu znajduje się przypis, który brzmi: „Właściwie wywołanie przez odwołanie do obiektu byłoby lepszym opisem, ponieważ jeśli przekazany zostanie obiekt zmienny, wywołujący zobaczy wszelkie zmiany, które wprowadzi w nim osoba odbierająca ... „ Zgadzam się z tobą, że zamieszanie jest spowodowane próbą dopasowania terminologii ustalonej w innych językach. Pomijając semantykę, należy zrozumieć: słowniki / przestrzenie nazw, operacje wiązania nazw i związek nazwy → wskaźnika → obiektu (jak już wiesz).
Uczciwy Abe
56

W Pythonie nie ma żadnych zmiennych

Kluczem do zrozumienia przekazywania parametrów jest zaprzestanie myślenia o „zmiennych”. W Pythonie znajdują się nazwy i obiekty, które razem wyglądają jak zmienne, ale zawsze warto rozróżnić trzy.

  1. Python ma nazwy i obiekty.
  2. Przypisanie wiąże nazwę z obiektem.
  3. Przekazanie argumentu do funkcji wiąże również nazwę (nazwę parametru funkcji) z obiektem.

To wszystko. Zmienność nie ma znaczenia dla tego pytania.

Przykład:

a = 1

Wiąże to nazwę az obiektem typu liczba całkowita, który zawiera wartość 1.

b = x

To wiąże nazwę bz tym samym obiektem, z którym nazwa xjest obecnie związana. Potem nazwa bnie ma już z nią nic wspólnego x.

Zobacz sekcje 3.1 i 4.2 w dokumentacji języka Python 3.

Jak przeczytać przykład w pytaniu

W kodzie pokazanym w pytaniu instrukcja self.Change(self.variable)wiąże nazwę var(w zakresie funkcji Change) z obiektem, który przechowuje wartość 'Original'i przypisanie var = 'Changed'(w treści funkcjiChange ) ponownie przypisuje tę samą nazwę: do innego obiektu (co się dzieje do trzymania sznurka, ale mogło to być coś zupełnie innego).

Jak przekazać przez odniesienie

Jeśli więc rzeczą, którą chcesz zmienić, jest obiekt zmienny, nie ma problemu, ponieważ wszystko jest skutecznie przekazywane przez referencję.

Jeśli jest to obiekt niezmienny (np. Bool, liczba, łańcuch), sposobem jest zawinięcie go w obiekt zmienny.
Szybkim i brudnym rozwiązaniem tego jest lista jednoelementowa (zamiast self.variablepass [self.variable]i w funkcji modyfikacji var[0]).
Bardziej pythonowym podejściem byłoby wprowadzenie trywialnej klasy z jednym atrybutem. Funkcja odbiera instancję klasy i manipuluje atrybutem.

Lutz Prechelt
źródło
20
„Python nie ma zmiennych” to głupie i mylące hasło, i naprawdę chciałbym, żeby ludzie przestali to mówić… :( Reszta tej odpowiedzi jest dobra!
Ned Batchelder
9
Może to być szokujące, ale nie jest głupie. I nie sądzę, że jest to również mylące: mam nadzieję, że otwiera umysł odbiorcy na nadchodzące wyjaśnienia i stawia ją w użytecznym podejściu „Zastanawiam się, co mają zamiast zmiennych”. (Tak, przebieg może się różnić.)
Lutz Prechelt
13
czy powiedziałbyś również, że JavaScript nie ma zmiennych? Działają tak samo jak Pythona. Ponadto Java, Ruby, PHP, .... Myślę, że lepszą techniką nauczania jest: „Zmienne Pythona działają inaczej niż C.”
Ned Batchelder
9
Tak, Java ma zmienne. Podobnie jak Python, JavaScript, Ruby, PHP itp. Nie powiedziałbyś w Javie, która intdeklaruje zmienną, ale Integernie. Obie deklarują zmienne. IntegerZmienna jest przedmiotem, intzmienna jest prymitywne. Jako przykład zademonstrowałeś działanie swoich zmiennych, pokazując a = 1; b = a; a++ # doesn't modify b. Dotyczy to również Pythona (używając, += 1ponieważ nie ma go ++w Pythonie)!
Ned Batchelder
1
Pojęcie „zmiennej” jest złożone i często niejasne: zmienna jest kontenerem dla wartości identyfikowanej nazwą. W Pythonie wartości są obiektami, kontenery są obiektami (widzisz problem?), A nazwy są w rzeczywistości osobnymi rzeczami. Uważam, że znacznie trudniej jest dokładnie zrozumieć zmienne w ten sposób. Wyjaśnienie nazw i obiektów wydaje się trudniejsze, ale w rzeczywistości jest prostsze.
Lutz Prechelt,
43

Prostą sztuczką, której zwykle używam, jest po prostu zawinięcie jej w listę:

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

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

(Tak, wiem, że może to być niewygodne, ale czasami wystarczy to zrobić.)

AmanicA
źródło
7
+1 za niewielką ilość tekstu, dającą zasadnicze obejście problemu braku Pythona przez odniesienie. (Jako kontynuacja komentarza / pytania, które pasuje tutaj i gdziekolwiek na tej stronie: nie jest jasne, dlaczego python nie może zapewnić słowa kluczowego „ref”, jak C #, co po prostu umieszcza argument dzwoniącego na liście takiej jak i traktuj odniesienia do argumentu w funkcji jako 0 element listy.)
M Katz
5
Miły. Aby przejść przez ref, zawiń [].
Justas
38

(edytuj - Blair zaktualizował swoją niezwykle popularną odpowiedź, aby była teraz dokładna)

Myślę, że należy zauważyć, że obecny post z największą liczbą głosów (autor: Blair Conrad), choć jest poprawny pod względem wyniku, wprowadza w błąd i jest niepoprawny na podstawie jego definicji. Chociaż istnieje wiele języków (takich jak C), które pozwalają użytkownikowi przejść przez referencję lub przekazać wartość, Python nie jest jednym z nich.

Odpowiedź Davida Cournapeau wskazuje na prawdziwą odpowiedź i wyjaśnia, dlaczego zachowanie w poście Blaira Conrada wydaje się poprawne, a definicje nie.

W zakresie, w jakim Python jest przekazywany według wartości, wszystkie języki są przekazywane według wartości, ponieważ należy wysłać część danych (czy to „wartość”, czy „referencję”). Nie oznacza to jednak, że Python przekazuje wartość w tym sensie, że programista C by o tym pomyślał.

Jeśli chcesz tego zachowania, odpowiedź Blair Conrad jest w porządku. Ale jeśli chcesz wiedzieć, dlaczego Python nie jest ani przekazywany przez wartość, ani przekazywany przez odniesienie, przeczytaj odpowiedź Davida Cournapeau.

KobeJohn
źródło
4
Po prostu nie jest prawdą, że wszystkie języki są wywoływane według wartości. W C ++ lub Pascal (i na pewno wielu innych, których nie znam), masz połączenie przez referencję. Na przykład w C ++ void swap(int& x, int& y) { int temp = x; x = y; y = temp; }zamieni przekazane do niego zmienne. W Pascal używasz varzamiast &.
cayhorstmann,
2
Myślałem, że odpowiedziałem na to dawno temu, ale nie widzę tego. Dla kompletności - cayhorstmann źle zrozumiał moją odpowiedź. Nie mówiłem, że wszystko nazywa się wartością w terminach, których większość ludzi uczy się o C / C ++ . Po prostu przekazano pewną wartość (wartość, nazwa, wskaźnik itp.), A terminy użyte w pierwotnej odpowiedzi Blair były niedokładne.
KobeJohn,
24

Masz tutaj naprawdę dobre odpowiedzi.

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' )
Bobobobo
źródło
2
tak, jednak jeśli wykonasz x = [2, 4, 4, 5, 5], y = x, X [0] = 1, wydrukuj x # [1, 4, 4, 5, 5] wydrukuj y # [1 , 4, 4, 5, 5]
laycat
19

W tym przypadku zmienna nazwana varw metodzie Changejest przypisana do odwołania self.variablei natychmiast przypisujesz ciąg var. To już nie wskazuje self.variable. Poniżej przedstawiono fragment kodu, co by się stało, gdyby zmodyfikować strukturę danych wskazywanego przez vari self.variable, w tym przypadku listy:

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

Jestem pewien, że ktoś inny mógłby to wyjaśnić.

Mike Mazur
źródło
18

Schemat przekazywania przez Pythona nie jest dokładnie taki sam jak opcja parametrów referencyjnych C ++, ale okazuje się być bardzo podobny do modelu przekazywania argumentów języka C (i innych) w praktyce:

  • Niezmienne argumenty są skutecznie przekazywane „ przez wartość ”. Obiekty takie jak liczby całkowite i łańcuchy są przekazywane przez odniesienie do obiektu zamiast przez kopiowanie, ale ponieważ nie można w żaden sposób zmienić niezmiennych obiektów na miejscu, efekt przypomina tworzenie kopii.
  • Argumenty zmienne są skutecznie przekazywane „ wskaźnikiem ”. Obiekty takie jak listy i słowniki są również przekazywane przez odniesienie do obiektu, co jest podobne do sposobu, w jaki C przekazuje tablice jako wskaźniki - zmienne obiekty można zmieniać w funkcji, podobnie jak tablice C.
ajknzhol
źródło
16

Jak możesz powiedzieć, musisz mieć zmienny obiekt, ale pozwól, że zasugeruję ci sprawdzenie zmiennych globalnych, ponieważ mogą ci pomóc, a nawet rozwiązać ten problem!

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

przykład:

>>> 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
Nuno Aniceto
źródło
5
Kusiło mnie, aby opublikować podobną odpowiedź - pierwotny pytający mógł nie wiedzieć, że tak naprawdę chciał użyć zmiennej globalnej dzielonej między funkcjami. Oto link, który bym udostępnił: stackoverflow.com/questions/423379/... W odpowiedzi na @Tim, przepełnienie stosu to nie tylko strona pytań i odpowiedzi, to ogromne repozytorium wiedzy referencyjnej, które staje się silniejsze i bardziej niuansowe - dużo jak aktywna wiki - z większym wkładem.
Max P Magee
13

Wiele spostrzeżeń w odpowiedziach tutaj, ale myślę, że dodatkowa kwestia nie jest tutaj wyraźnie wymieniona. Cytowanie z dokumentacji Pythona https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

„W Pythonie zmienne, do których istnieją odwołania tylko w obrębie funkcji, są domyślnie globalne. Jeśli zmiennej przypisano nową wartość w dowolnym miejscu w ciele funkcji, zakłada się, że jest ona lokalna. Jeśli zmienna kiedykolwiek zostanie przypisana nowej wartości w funkcji, zmienna jest domyślnie lokalna i musisz jawnie zadeklarować ją jako „globalną”. Choć na początku jest to nieco zaskakujące, wyjaśnienie to wyjaśnia. Z jednej strony wymaganie globalne dla przypisanych zmiennych zapewnia ochronę przed niezamierzonymi skutkami ubocznymi. z drugiej strony, jeśli globalny byłby wymagany dla wszystkich globalnych odniesień, cały czas używałbyś globalnego. Musiałbyś zadeklarować jako globalny każde odniesienie do wbudowanej funkcji lub komponentu importowanego modułu. Ten bałagan pokonałby przydatność globalnej deklaracji do identyfikowania skutków ubocznych. ”

Nawet w przypadku przekazania zmiennego obiektu do funkcji nadal obowiązuje. I dla mnie jasno wyjaśnia przyczynę różnicy w zachowaniu między przypisaniem do obiektu a działaniem na obiekcie w funkcji.

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)

daje:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

Przypisanie do zmiennej globalnej, która nie jest zadeklarowana jako globalna, tworzy zatem nowy obiekt lokalny i przerywa połączenie z pierwotnym obiektem.

Joop
źródło
10

Oto proste (mam nadzieję) wyjaśnienie pojęcia pass by objectzastosowanego w Pythonie.
Ilekroć przekazujesz obiekt do funkcji, sam obiekt jest przekazywany (obiekt w Pythonie jest tak naprawdę, jak nazwałbyś wartość w innych językach programowania), a nie odniesienie do tego obiektu. Innymi słowy, kiedy dzwonisz:

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

my_list = [0, 1]
change_me(my_list)

Rzeczywisty obiekt - [0, 1] (który byłby nazywany wartością w innych językach programowania) jest przekazywany. W rzeczywistości funkcja change_mebędzie próbowała zrobić coś takiego:

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

co oczywiście nie zmieni obiektu przekazanego do funkcji. Jeśli funkcja wyglądała tak:

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

Wówczas połączenie spowodowałoby:

[0, 1].append(2)

co oczywiście zmieni obiekt. Ta odpowiedź dobrze to wyjaśnia.

matino
źródło
2
Problem polega na tym, że zadanie wykonuje coś innego niż się spodziewasz. Te list = [1, 2, 3]przyczyny ponownego wykorzystania listnazwy do czegoś innego i forgeting pierwotnie przekazany obiekt. Możesz jednak spróbować list[:] = [1, 2, 3](nawiasem mówiąc, listjest to niepoprawna nazwa zmiennej. Myślenie o tym [0, 1] = [1, 2, 3]jest kompletnym nonsensem. W każdym razie, co według ciebie oznacza, że obiekt jest przekazywany ? Co Twoim zdaniem jest kopiowane do funkcji?
Pepr,
Obiekty @pepr nie są literałami. Oni są przedmiotami. Jedynym sposobem na rozmowę o nich jest nadanie im nazwisk. Dlatego zrozumienie tego jest tak proste, ale wyjaśnienie jest niezwykle skomplikowane. :-)
Veky
@Veky: Jestem tego świadomy. W każdym razie literał listy jest konwertowany na obiekt listy. W rzeczywistości każdy obiekt w Pythonie może istnieć bez nazwy i może być używany, nawet jeśli nie ma żadnej nazwy. I możesz myśleć o nich jak o anonimowych przedmiotach. Pomyśl o obiektach będących elementami list. Nie potrzebują imienia. Możesz uzyskać do nich dostęp poprzez indeksowanie lub iterację listy. W każdym razie, nalegam, [0, 1] = [1, 2, 3]to po prostu zły przykład. W Pythonie nie ma nic takiego.
pepr
@pepr: Niekoniecznie mam na myśli nazwy w języku Python, tylko zwykłe nazwy. Oczywiście alist[2]liczy się jako nazwa trzeciego elementu alist. Ale myślę, że źle zrozumiałem na czym polegał twój problem. :-)
Veky
Argh. Mój angielski jest oczywiście znacznie gorszy niż mój Python. :-) Spróbuję jeszcze raz. Powiedziałem tylko, że musisz nadać obiektowi kilka nazw, aby o nich mówić. Przez te „imiona” nie miałem na myśli „imion zdefiniowanych przez Pythona”. Znam mechanizmy Python, nie martw się.
Veky
8

Oprócz wszystkich świetnych wyjaśnień dotyczących tego, jak działają te rzeczy w Pythonie, nie widzę prostej sugestii dotyczącej problemu. Gdy wydaje się, że tworzysz obiekty i instancje, pythoniczny sposób obsługi zmiennych instancji i ich zmiany jest następujący:

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

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

W metodach instancji zwykle odnosi się selfdo atrybutów instancji dostępu. Normalne jest ustawianie atrybutów instancji __init__i odczytywanie lub zmienianie ich w metodach instancji. Dlatego przekazujesz selfrównież pierwszy argument def Change.

Innym rozwiązaniem byłoby stworzenie metody statycznej takiej jak ta:

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
Dolf Andringa
źródło
6

Istnieje pewna sztuczka, aby przekazać obiekt przez referencję, nawet jeśli język nie pozwala. Działa również w Javie, jest to lista z jednym elementem. ;-)

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

To brzydki hack, ale działa. ;-P

itmuckel
źródło
pjest odwołaniem do zmiennego obiektu listy, który z kolei przechowuje obiekt obj. Zostanie przekazane odniesienie „p” changeRef. Wewnątrz changeReftworzone jest nowe odwołanie (nazywane jest nowym odwołaniem ref), które wskazuje ten sam obiekt listy, który pwskazuje. Ale ponieważ listy można modyfikować, zmiany na liście są widoczne w obu odnośnikach. W takim przypadku refodwołanie zostało użyte do zmiany obiektu o indeksie 0, aby następnie przechował PassByReference('Michael')obiekt. Zmiana obiektu listy została wykonana za pomocą, refale ta zmiana jest widoczna dla p.
Minh Tran,
Więc teraz, odniesienia pi refpunkt do obiektu listy, która przechowuje jeden obiekt, PassByReference('Michael'). Wynika z tego, że p[0].namepowraca Michael. Oczywiście, refteraz wyszedł poza zakres i może być zbierany śmieci, ale mimo wszystko.
Minh Tran,
Ty nie zmienił zmiennej instancji prywatnych, name, oryginalnego PassByReferenceobiektu związanego z odniesieniem obj, choć. W rzeczywistości obj.namewróci Peter. Powyższe komentarze zakładają, że podana definicja Mark Ransom.
Minh Tran,
Chodzi mi o to, że nie zgadzam się, że jest to hack (co mam na myśli, mówiąc o czymś, co działa, ale z powodów nieznanych, nie przetestowanych lub niezamierzonych przez osobę wdrażającą). Po prostu zastąpiłeś jeden PassByReferenceobiekt innym PassByReferenceobiektem na liście i odniosłeś się do drugiego z dwóch obiektów.
Minh Tran,
5

Zastosowałem następującą metodę, aby szybko przekonwertować kilka kodów Fortran na Python. To prawda, że ​​nie jest przekazywane przez odniesienie, jak postawiono oryginalne pytanie, ale w niektórych przypadkach jest to proste obejście.

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
Brad Porter
źródło
Tak, rozwiązuje to również „przekazanie przez odniesienie” w moim przypadku użycia. Mam funkcję, która w zasadzie czyści wartości w a, dicta następnie zwraca dict. Jednak podczas czyszczenia może się okazać, że konieczna jest przebudowa części systemu. Dlatego funkcja musi nie tylko zwrócić wyczyszczone, dictale także być w stanie zasygnalizować odbudowę. Próbowałem przekazać boolreferencję, ale ofc to nie działa. Zastanawiając się, jak to rozwiązać, znalazłem twoje rozwiązanie (w zasadzie zwracanie krotki), które działa najlepiej, a jednocześnie nie jest hack / obejściem w ogóle (IMHO).
kasimir
3

Chociaż przekazywanie przez odniesienie nie jest niczym, co dobrze pasuje do pytona i powinno być rzadko stosowane, istnieją pewne obejścia, które faktycznie mogą działać, aby obiekt został aktualnie przypisany do zmiennej lokalnej lub nawet przypisać zmienną lokalną z wnętrza wywoływanej funkcji.

Podstawową ideą jest posiadanie funkcji, która może uzyskać dostęp i może być przekazywana jako obiekt do innych funkcji lub przechowywana w klasie.

Jednym ze sposobów jest użycie global(dla zmiennych globalnych) lub nonlocal(dla zmiennych lokalnych w funkcji) w funkcji otoki.

def change(wrapper):
    wrapper(7)

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

Ten sam pomysł działa na odczytywanie i deletowanie zmiennej.

Do samego odczytu istnieje jeszcze krótszy sposób użycia, lambda: xktóry zwraca wywołanie, które po wywołaniu zwraca bieżącą wartość x. Jest to trochę jak „zadzwoń po imieniu” używane w odległej przeszłości w językach.

Przekazywanie 3 opakowań w celu uzyskania dostępu do zmiennej jest nieco niewygodne, więc można je zawinąć w klasę, która ma atrybut 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

Obsługa „refleksji” Pythona umożliwia uzyskanie obiektu, który jest w stanie ponownie przypisać nazwę / zmienną w danym zakresie bez wyraźnego zdefiniowania funkcji w tym zakresie:

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)

Tutaj ByRefklasa otacza dostęp do słownika. Tak więc dostęp do atrybutu wrappedjest tłumaczony na dostęp do elementu w przekazywanym słowniku. Po przekazaniu wyniku wbudowanej localsi nazwy zmiennej lokalnej uzyskuje się dostęp do zmiennej lokalnej. Dokumentacja Pythona od 3.5 radzi, że zmiana słownika może nie działać, ale wydaje mi się, że działa.

Skorupa
źródło
3

biorąc pod uwagę sposób, w jaki Python obsługuje wartości i odniesienia do nich, jedynym sposobem na odwołanie się do atrybutu dowolnej instancji jest nazwa:

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

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

w prawdziwym kodzie dodawalibyśmy oczywiście sprawdzanie błędów podczas wyszukiwania słownika.

MARKOWY BLOORE
źródło
3

Ponieważ słowniki są przekazywane przez referencję, można użyć zmiennej dict do przechowywania w niej dowolnych wartości referencyjnych.

# 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
Liakos
źródło
3

Przekazywanie przez referencję w Pythonie różni się znacznie od koncepcji przekazywania przez referencję w C ++ / Java.

  • Java & C #: typy pierwotne (zawierają łańcuch) przekazują wartość (kopia), typ referencji jest przekazywany przez referencję (kopia adresu), więc wszystkie zmiany dokonane w parametrze w wywoływanej funkcji są widoczne dla dzwoniącego.
  • C ++: Dozwolone jest zarówno przekazywanie przez referencję, jak i przekazywanie przez wartość. Jeśli parametr jest przekazywany przez odwołanie, możesz go zmodyfikować lub nie, w zależności od tego, czy parametr został przekazany jako const, czy nie. Jednak const, czy nie, parametr zachowuje odwołanie do obiektu i nie można przypisać odwołania do wskazania innego obiektu w ramach wywoływanej funkcji.
  • Python: Python to „pass-by-object-reference”, o którym często mówi się: „Referencje obiektów są przekazywane przez wartość”. [Czytaj tutaj] 1. Zarówno obiekt wywołujący, jak i funkcja odnoszą się do tego samego obiektu, ale parametr w funkcji jest nową zmienną, która po prostu przechowuje kopię obiektu w obiekcie wywołującym. Podobnie jak C ++, parametr może być modyfikowany lub nie w funkcji - Zależy to od typu przekazywanego obiektu. na przykład; Niezmiennego typu obiektu nie można zmodyfikować w wywoływanej funkcji, natomiast obiekt zmienny można zaktualizować lub ponownie zainicjować. Zasadniczą różnicą między aktualizacją lub ponownym przypisaniem / ponownym zainicjowaniem zmiennej zmiennej jest to, że zaktualizowana wartość jest ponownie odzwierciedlana w wywoływanej funkcji, podczas gdy wartość ponownie inicjowana nie. Zakres dowolnego przypisania nowego obiektu do zmiennej podlegającej mutacji jest lokalny dla funkcji w pythonie. Przykłady podane przez @ blair-conrad świetnie to rozumieją.
Alok Garg
źródło
1
Stare, ale czuję się zobowiązany to poprawić. Ciągi znaków są przekazywane przez odwołanie zarówno w Javie, jak i C #, NIE przez wartość
Jan
2

Ponieważ twój przykład jest zorientowany obiektowo, możesz wprowadzić następującą zmianę, aby osiągnąć podobny wynik:

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'
Jesse Hogan
źródło
1
Chociaż to działa. To nie jest przekazywane przez odniesienie. Jest to „odniesienie do obiektu”.
Bishwas Mishra
1

Możesz użyć pustej klasy jako instancji do przechowywania obiektów referencyjnych, ponieważ wewnętrzne atrybuty obiektów są przechowywane w słowniku instancji. Zobacz przykład.

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)
sergzach
źródło
1

Ponieważ wydaje się, że nigdzie nie wspomniano, podejście do symulacji referencji znanej np. Z C ++ polega na użyciu funkcji „aktualizacji” i przekazania jej zamiast faktycznej zmiennej (a raczej „nazwy”):

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

Jest to szczególnie przydatne w przypadku „odwołań tylko do wyjścia” lub w sytuacji, gdy istnieje wiele wątków / procesów (dzięki temu, że funkcja aktualizacji wątek / wieloprocesowość jest bezpieczna).

Oczywiście powyższe nie pozwala na odczyt wartości, a jedynie na jej aktualizację.

Daniel Jour
źródło