Mutowalność list w Pythonie za pomocą funkcji

Mutowalność list w Pythonie za pomocą funkcji
PJ
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:24
0

Przede wszystkim pozdrawiam wszystkich.
Jestem początkującym "pythonerem". Myślałem, że ogarniam funkcję, ale ostatnio coś mnie zadziwiło w moim programie.
Mianowicie mam funkcję:
https://pastebin.com/raw/De0NFdBL

Kopiuj
def funkcja(b, c):
    b = 10
    c[0] = 10
    print("b lokalnie", b)

b = 5
c = [5,5,5]
funkcja(b, c)
print(b)
print(c)

No i funkcja działa tak jak powinna. Lista jest mutowalna i ją zmieni, zmienna b cały czas pozostaje niezmieniona poza funkcją. No i OK.

Ale potrzebowałem zmienić w inny sposób listę za pomocą funkcji. Więc ją skopiowałem do listy "pomocniczej". Zmieniłem pomocniczą. I potem z powrotem skopiowałem listę główną, ale ona nie została zmieniona. Dlaczego?
https://pastebin.com/raw/A0DJMzbZ

Kopiuj
def funkcja(c):
    copy = c[:]
    copy[0] = 10
    print("Kopia: ", copy)
    c = copy[:]


c = [5,5,5]
funkcja(c)
print(c)
edytowany 1x, ostatnio: pjanu
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
4

Bo nie możes zmieć samej referencji, bo ona jest kopiowana!
Jest różnica między zmianą "bebechów" obiektu a zmianą obiektu na inny obiekt.

Argumenty funkcji są kopiami parametrów, ale że to wszystko są obiekty, to nie przekazujesz do funkcji listy tylko powiedzmy coś co nazwiemy chwilowo adresem listy w pamięci. Więc co prawda zmienna którą przekazałeś jako argument do funkcji oraz argument w funkcji pokazują na ten sam obszar pamięci, gdzie jest fizycznie twoja lista, to są to dwie zupełnie osobne zmienne! Zmieniając obiekt pod tym adresem, wprowadzasz zmiany widoczne na zewnątrz, bo adres tej pamięci wewnątrz i na zewnątrz funkcji jest taki sam.
Ale jeśli przypiszesz sobie nowy adres do zmiennej która początkowo przechowywała adres który dostałeś jako argument, to nijak nie będzie to widoczne na zewnątrz funkcji.

Popatrz sobie moze na taki przykład:

Kopiuj
def funkcja(c):
    print('id c',id(c))
    copy = c[:]
    print('id copy',id(copy))
    copy[0] = 10
    print("Kopia: ", copy)
    c = copy[:]
    print('id c',id(c))
    print('id copy',id(copy))

c = [5,5,5]
print('id c',id(c))
funkcja(c)
print('id c',id(c))
print(c)

Moze być trochę mylący bo python jest sprytny i nie robi kopii dopóki nie jest potrzebna, więc np. copy = c[:] w praktyce zwraca to samo id, dopóki nie spróbujesz zmienić listy, dopiero wtedy realnie robi sie twoja kopia. Niemniej widać tutaj w czym problem.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 5x, ostatnio: Shalom
Zobacz pozostały 1 komentarz
Shalom
python3 = rak
IK
przecież w 2 jest tak samo
Shalom
Nie, nie jest. W 2 te nawiasy oznaczają tuple a nie argumenty funkcji, więc nie ma zadnych błędów.
IK
nie zrozumieliśmy się ;) wypisujesz id(c) w miejscu, gdzie chcesz wypisać id(copy), przez co błędnie dochodzisz do wniosku, że copy = c[:] nie tworzy kopii, a jest ona tworzona dopiero w momencie dokonania jakiejś operacji na copy. zobacz: https://ideone.com/6zvGs5
Shalom
A jasne, ale to jest zupełnie nie istotne w tym miejscu.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Musisz odróżnić listę w sensie obiektu w pamięci komputera a referencje która na taki obiekt pokazuje.

Kopiuj
x = [1,2,3,4]
y = x

W pamięci jest teraz tylko jedna lista, ale masz dwie różne referencje do niej -> x oraz y. Jeśli teraz zrobisz y = [5,6,7,8] to czy oczekujesz ze nagle x będzie też pokazywać na [5,6,7,8] czy że nadal będzie pokazywać na listę [1,2,3,4]? To jest dokładnie ta sama sytuacja, bo przekazując argument do funkcji tworzysz sobie taką właśnie zmienną y
O ile w chwili y = x lista jest jedna, o tyle masz dwie niezależne zmienne które na tą listę pokazują! Zmiana jednej z tych zmiennych nie powoduje zmiany drugiej! Ale zmiana listy pod spodem oczywiście będzie widoczna "z obu", bo lista fizycznie jest tylko jedna.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
LO
  • Rejestracja:ponad 9 lat
  • Ostatnio:2 dni
  • Postów:30
0

Jest to bardziej problem z zasięgiem zmiennych niż ich mutowalnością. Zerknij na to.

Kopiuj
def funkcja(c):
    copy = c[:]
    copy[0] = 10
    print("Kopia: ", copy)
    print("Adres c:", hex(id(c)))
    c = copy[:]
    print("Adres c:", hex(id(c)))
    print("Wartosc c lokalnie:", c)

c = [5,5,5]
print("Adres c:", hex(id(c)))
funkcja(c)
print(c)

lion137
  • Rejestracja:około 8 lat
  • Ostatnio:około 9 godzin
  • Postów:4935
0

Obejrzyj: , to Ci się trochę rozjaśni.


cmd
  • Rejestracja:około 10 lat
  • Ostatnio:4 dni
  • Lokalizacja:Warszawa
  • Postów:443
0

Obaj wyżej dobrze mówią. Pierwsze musisz zagłębić się czym jest pointer, referencja i obiekt w pythonie żeby to lepiej zrozumieć. W skrócie robiąc coś takiego copy = c[:] tworzysz nowy obiekt listy, jest też scope funkcji i ten nowy obiekt jest widoczny wlasnie tylko z poziomu tej funkcji. Więc kiedy wskazujesz wewnątrz funkcji c = copy[:] ciągle operujesz na obiektcie którego scope tyczy się tylko tej funkcji. Czyli "c" wewnątrz funkcji wskazuje na inny obiekt niż "c" poza funkcją (zmodyfikowales utworzyłeś nową tylko nową referencje o tej samej nazwie co globalnie (ale istniejącą tylko lokalnie), a nie zmodyfikowałeś oryginalny obiekt). Nie rozumiem też koncepcji kopiowania obiektu, po co? Przekaż obiekt do funkcji normalnie zmodyfikuj i go zwróć. Duplikowanie tego samego obiektu w pamięci tylko po to by go zmodyfikować nie jest w tym wypadku uzasadnione ( i to w sumie 3 razy, chyba że to jakieś celowe ćwiczenie). Ogólnie polecam http://pythontutor.com/ do zabawy w wyizualizacje takich rzeczy, pomocne narzędzie na start które dobrze Ci pokaże co gdzie i jak ;)

edytowany 5x, ostatnio: cmd
PJ
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:24
0

Dziękuję wszystkim za natychmiastową pomoc. Mam nadzieję, że już wiem o co chodzi. Przeszedłem do Pythona z C. Miało być łatwiej;) ale logika Pythona mnie poraża:)

PJ
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:24
0
cmd napisał(a):

Nie rozumiem też koncepcji kopiowania obiektu, po co? Przekaż obiekt do funkcji normalnie zmodyfikuj i go zwróć. Duplikowanie tego samego obiektu w pamięci tylko po to by go zmodyfikować nie jest w tym wypadku uzasadnione ( i to w sumie 3 razy, chyba że to jakieś celowe ćwiczenie). Ogólnie polecam http://pythontutor.com/ do zabawy w wyizualizacje takich rzeczy, pomocne narzędzie na start które dobrze Ci pokaże co gdzie i jak ;)

To jest część większego programu. Zostawiłem tylko co było potrzebne by pokazać o co mi chodzi.

Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
2

Przeszedłem do Pythona z C.

Ale w C przecież zachowa się to dokładnie tak samo...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Guaz
To samo pomyślałem czytając :D
PJ
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:24
0
Shalom napisał(a):

Przeszedłem do Pythona z C.

Ale w C przecież zachowa się to dokładnie tak samo...

A np. takie coś:

Kopiuj

def funkcja(element, lista = []):
    lista.append(element)
    print(lista)
		
funkcja(1)
funkcja(2)
funkcja(3)

Gdzie tu sens wywoływania funkcji;)

edytowany 1x, ostatnio: pjanu
TR
testujesz to jakoś czy czekasz, aż @Shalom użyje swojego wbudowanego w głowie kompilatora? :)
PJ
To nie jest do testowania, to tylko pokazuje jak "nielogicznie" działa mutowalność za pomocą funkcji. Fajnie działa ten http://pythontutor.com/ . Nie znałem;)
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

@pjanu nie rozumiem pytania. Zresztą normalne IDE (jak Pycharm) powie ci ze używanie MUTOWALNEGO obiektu jako domyślnego argumentu to ZŁY POMYSŁ...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
PJ
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:24
0
Shalom napisał(a):

@pjanu nie rozumiem pytania. Zresztą normalne IDE (jak Pycharm) powie ci ze używanie MUTOWALNEGO obiektu jako domyślnego argumentu to ZŁY POMYSŁ...

Tu nie ma pytania. Dzięki tym przykładom zobaczyłem zupełnie przypadkiem niebezpieczeństwo. Jeszcze raz dzięki.

edytowany 1x, ostatnio: Shalom

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.