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:około 21 lat
  • Ostatnio:prawie 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:około 21 lat
  • Ostatnio:prawie 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:około godziny
  • 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:2 minuty
  • Postów:4896
0

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


cmd
  • Rejestracja:około 10 lat
  • Ostatnio:około 4 godziny
  • 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:około 21 lat
  • Ostatnio:prawie 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:około 21 lat
  • Ostatnio:prawie 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
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)