Przekazywanie liczby całkowitej przez odwołanie w Pythonie

99

Jak mogę przekazać liczbę całkowitą przez odwołanie w Pythonie?

Chcę zmodyfikować wartość zmiennej, którą przekazuję do funkcji. Czytałem, że wszystko w Pythonie jest przekazywane przez wartość, ale musi być prosta sztuczka. Na przykład w Javie można przekazać typów referencyjnych Integer, Longitp

  1. Jak mogę przekazać liczbę całkowitą do funkcji przez odwołanie?
  2. Jakie są najlepsze praktyki?
CodeKingPlusPlus
źródło
zobacz to dla przyjemnego, choć nieco zawiłego sposobu na umieszczenie twoich int w anonimowej klasie (która jest zmienna), która będzie zachowywać się jak 'referencja': stackoverflow.com/a/1123054/409638 ie ref = type ('', () , {'n': 1})
robert

Odpowiedzi:

111

W Pythonie nie działa to w ten sposób. Python przekazuje odniesienia do obiektów. Wewnątrz swojej funkcji masz obiekt - możesz dowolnie modyfikować ten obiekt (jeśli to możliwe). Jednak liczby całkowite są niezmienne . Jedynym sposobem obejścia tego problemu jest przekazanie liczby całkowitej w kontenerze, który można zmodyfikować:

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

x = [1]
change(x)
print x

W najlepszym razie jest to brzydkie / niezdarne, ale w Pythonie nic nie da się osiągnąć. Powodem jest to, że w Pythonie przypisanie ( =) pobiera dowolny obiekt będący wynikiem prawej strony i wiąże go z tym, co znajduje się po lewej stronie * (lub przekazuje go do odpowiedniej funkcji).

Rozumiejąc to, możemy zobaczyć, dlaczego nie ma sposobu, aby zmienić wartość niezmiennego obiektu wewnątrz funkcji - nie możesz zmienić żadnego z jego atrybutów, ponieważ jest niezmienny i nie możesz po prostu przypisać „zmiennej” nowego wartość, ponieważ wtedy faktycznie tworzysz nowy obiekt (który różni się od starego) i nadajesz mu nazwę, jaką stary obiekt miał w lokalnej przestrzeni nazw.

Zazwyczaj sposobem obejścia tego problemu jest po prostu zwrócenie żądanego obiektu:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* W pierwszym przykładzie powyżej, 3faktycznie zostaje przekazany do x.__setitem__.

mgilson
źródło
11
„Python przekazuje obiekty”. Nie. Python przekazuje referencje (wskaźniki do obiektów).
user102008
6
@ user102008 - Słuszna uwaga. Wydaje mi się, że semantyka w tym miejscu jest bardzo mętna. Ostatecznie też nie są „wskazówkami”. Przynajmniej na pewno nie w tym samym sensie, w jakim się znajdujesz C. (Na przykład nie trzeba ich wyłuskiwać). W moim modelu myślowym łatwiej jest myśleć o rzeczach jako o „nazwach” i „przedmiotach”. Przypisanie wiąże „Nazwę” (y) po lewej stronie z „Obiektem” (-ami) po prawej stronie. Kiedy wywołujesz funkcję, przekazujesz „Obiekt” (skutecznie wiążąc go z nową nazwą lokalną w funkcji).
mgilson
Jest to oczywiście opis odwołań do Pythona , ale nie jest łatwo znaleźć sposób, aby zwięźle je opisać tym, którzy nie są zaznajomieni z terminologią.
mgilson
2
Nic dziwnego, że mylimy się z terminologią. Tutaj mamy to opisane jako call-by-object, a tutaj jest opisane jako pass-by-value . Gdzie indziej to się nazywa „pass-by-reference” z gwiazdką na co to faktycznie oznacza ... W zasadzie, problemem jest to, że społeczność nie zorientowali się, co to nazwać
mgilson
1
Referencje w Pythonie są dokładnie takie same, jak odwołania do języka Java. A odwołania do języka Java są zgodne ze wskaźnikami JLS do obiektów. I zasadniczo istnieje kompletna bijekcja między odwołaniami do Java / Python a wskaźnikami C ++ do obiektów w semantyce, i nic innego nie opisuje również tej semantyki. „Nie trzeba ich wyłuskiwać” Cóż, .operator po prostu wyłuskuje lewą stronę. Operatory mogą być różne w różnych językach.
user102008
31

W większości przypadków, w których musisz przekazać przez odwołanie, musisz zwrócić więcej niż jedną wartość z powrotem do wywołującego. „Najlepszą praktyką” jest używanie wielu zwracanych wartości, co jest znacznie łatwiejsze w Pythonie niż w językach takich jak Java.

Oto prosty przykład:

def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once
Gabe
źródło
9

Nie jest to dokładne przekazywanie wartości bezpośrednio, ale używanie jej tak, jakby została przekazana.

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

Ostrzeżenia:

  • nonlocal został wprowadzony w Pythonie 3
  • Jeśli otaczający zakres jest zasięgiem globalnym, użyj globalzamiast nonlocal.
Uri
źródło
7

Naprawdę, najlepszą praktyką jest cofnięcie się i pytanie, czy naprawdę musisz to zrobić. Dlaczego chcesz zmodyfikować wartość zmiennej, którą przekazujesz do funkcji?

Jeśli chcesz to zrobić, aby szybko się zhakować, najszybszym sposobem jest przekazanie listliczby całkowitej trzymającej i przyklejenie[0] wokół każdego jej użycia, jak pokazuje odpowiedź mgilsona.

Jeśli trzeba zrobić coś bardziej znaczącego, napisać class, że ma intjako atrybut, więc można po prostu ustawić go. Oczywiście zmusza cię to do wymyślenia dobrej nazwy dla klasy i dla atrybutu - jeśli nie możesz nic wymyślić, wróć i przeczytaj zdanie jeszcze raz kilka razy, a następnie użyj list.

Mówiąc bardziej ogólnie, jeśli próbujesz przenieść jakiś idiom Java bezpośrednio do Pythona, robisz to źle. Nawet jeśli istnieje coś bezpośrednio odpowiadającego (jak w przypadku static/ @staticmethod), nadal nie chcesz go używać w większości programów w Pythonie tylko dlatego, że używałbyś go w Javie.

abarnert
źródło
JAVA nie przekazuje liczb całkowitych przez odniesienie (liczby całkowite lub dowolny obiekt. Obiekty w ramkach są również zastępowane podczas przypisywania).
Luis Masuelli
@LuisMasuelli Cóż, prymitywy w pudełkach są traktowane jak obiekty, jedyną rzeczą uniemożliwiającą ich użycie zgodnie z życzeniem OP jest fakt, że pudełka są niezmienne (a ponieważ same prymitywy są również niezmienne, całość jest niezmienna i można ją zmienić tylko w poziom zmienny)
Kroltan
Dobrym przykładem zastosowania tego jest zliczanie liczby wywołań (łącznie, a nie głębokości) wewnątrz funkcji rekurencyjnej. Potrzebujesz numeru, który można zwiększać we wszystkich połączeniach rozgałęzionych. Standardowy int po prostu tego nie wystarcza
z33 tys.
4

W Pythonie każda wartość jest odniesieniem (wskaźnikiem do obiektu), podobnie jak nieprymitywy w Javie. Podobnie jak Java, Python ma tylko przekazywanie wartości. Więc semantycznie są prawie takie same.

Ponieważ w swoim pytaniu wspominasz o Javie, chciałbym zobaczyć, w jaki sposób osiągasz to, co chcesz w Javie. Jeśli możesz to pokazać w Javie, mogę ci pokazać, jak to zrobić dokładnie równoważnie w Pythonie.

newacct
źródło
4

Jednoelementowa tablica numpy jest zmienna, a jednak w większości zastosowań może być oceniana tak, jakby była numeryczną zmienną Pythona. Dlatego jest to wygodniejszy kontener na numery referencyjne niż lista jednoelementowa.

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

wynik:

3
Trisoloriansunscreen
źródło
4

Może nie jest to sposób pythonowy, ale możesz to zrobić

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref
Sergey Kovalev
źródło
Człowieku, który jest mądry.
xilpex
3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     
shruti
źródło
2

Być może nieco bardziej samodokumentująca się sztuczka niż trik z listą długości-1 jest stara sztuczka z pustym typem:

def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)
mheyman
źródło
Lub zawiń numer w klasie: github.com/markrages/python_mutable_number
markrages
0

W Pythonie wszystko jest przekazywane przez wartość, ale jeśli chcesz zmodyfikować jakiś stan, możesz zmienić wartość liczby całkowitej wewnątrz listy lub obiektu, który jest przekazywany do metody.

rekurencyjny
źródło
3
Myślę, bo everything is passed by valueto nieprawda. Cytuj dokumenty:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Astery
1
Listy, obiekty i słowniki są przekazywane przez odniesienie
AN88
2
Gdyby tak było, przypisanie do parametru w funkcji byłoby odzwierciedlone w miejscu wywołania. Jak cytuje Astery, przekazywanie jest według wartości, a te wartości są odniesieniami do obiektów.
rekurencyjne
0

Prawidłową odpowiedzią jest użycie klasy i umieszczenie wartości wewnątrz klasy, co pozwala na przekazywanie przez odwołanie dokładnie tak, jak chcesz.

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)

Chris Pugmire
źródło