Mutowalność list w Pythonie za pomocą funkcji

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

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

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


c = [5,5,5]
funkcja(c)
print(c)
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:

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.

0

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

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.

0

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

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)

0

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

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

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

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.

2

Przeszedłem do Pythona z C.

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

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ś:


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

Gdzie tu sens wywoływania funkcji;)

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Ł...

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.

1 użytkowników online, w tym zalogowanych: 0, gości: 1