Funktion verändert input-argument, ohne dass sie es soll

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

ich habe einmal eine Verständnisfrage zu Listen, wenn wir bereits beim Thema sind/waren:
interaktive Shell :

Code: Alles auswählen

>>> liste = [[1, 2], [3, 4]]
>>> liste2 = list(liste)
>>> liste3 = liste[:]
>>> id(liste)
2039415693256
>>> id(liste2)
2039415701640
>>> id(liste3)
2039415457608
was ist eigentlich der Unterschied zwischen liste2 und liste3, außer, dass sie unterschiedliche ids haben? Sind ja beide flache Kopien, die 'nur' einen Zeiger/Referenz auf die Items in *liste* haben, denn:

Code: Alles auswählen

>>> liste[0][0] = 100
>>> liste
[[100, 2], [3, 4]]
>>> liste2
[[100, 2], [3, 4]]
>>> liste3
[[100, 2], [3, 4]]
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: Ausser das es verschiedene aber gleiche Objekte sind, gibt es keinen Unterschied.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, es gibt verschiedene Arten, wie man Kopien von Listen machen kann. Das Ergebnis ist jeweils das selbe.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

danke für die schnelle(n) Antwort(en).
Das heißt, wenn die Originalliste nur unveränderbare Items (Tupel, Strings, Zahlen) beinhaltet, dann ist

Code: Alles auswählen

liste1 = list(liste)
liste2 = liste[:]
für eine Kopie aureichend, ansonsten

Code: Alles auswählen

import copy
liste3 = copy.deepcopy(liste)
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: Auch bei veränderbaren Objekten kann eine flache Kopie ausreichend sein. Das kommt halt immer darauf an wie man mit der Liste weiter verfahren will. `copy.deepcopy()` wird eher selten verwendet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Perlchamp: Genau. Bei mutablen Objekten in kopierten Datenstrukturen musst Du aufpassen. Gleiches gilt für Parameterübergaben. Bei nicht mutablen Objekten ist das unkritisch, da hier bei Änderungen stets ein neues Objekt als Resultat erzeugt wird. In Python wird alles per Referenz angesprochen, der Typ ist an das Objekt gebunden. Daher ist die Sprache sowohl dynamisch, als auch stark typisiert ("späte Typbindung"). Das ist für Umsteiger von statisch typisierten Sprachen oft etwas unheimlich. Ich empfinde dies mittlerweile als großen Vorteil und betrachte die type-annotations mit entsprechendem Argwohn. (Insbesondere die neuen Dataclasses, die type-annotations zwingend voraussetzen, gefallen mir überhaupt nicht. Aber da gehen die Meinungen sehr auseinander.)
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ _blackjack_ :
klar, wenn man weiß, was man will und vor allem, wenn man weiß, was das Programm tut und bei einer gewissenhaften Vorbereitung (OOA, OOE, OOD, OOP) muß man nicht mit Kanonen auif Spatzen schießen. Sooooo weit bin ich noch laaaaange nicht. Es ging mir bloß ums Verständnis und um's "sicher gehen" ...

@ kbr :
ja, dass der Typ an das Objekt gebunden ist, empfinde ich persönlich auch als sehr gutes "feature" und vor allen Dingen als sehr *entspannend* ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Perlchamp: auch das flache Kopieren einer Liste ist eher selten, denn man macht meist einfach eine neue veränderte Liste, statt eine bestehende zu verändern.
Häufiger ist, dass man irgend ein iterierbares Objekt hat und daraus eine Liste machen will, da funktioniert dann nur die Variante `list(iterable)`.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@sirius3 :
... um es dann *beispielsweise* manipulieren zu können ...

Code: Alles auswählen

>>> list (“Buchstaben“)
[‘B‘, ‘u‘, ‘c‘, ‘h‘, ‘s‘, ‘t‘, ‘a‘, ‘b‘, ‘e‘, ‘n‘]
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Okay, wir sind immer noch in den 80ern aber jetzt auf dem PC – das CBM BASIC-Programm nach GW-BASIC portiert das bis MS-DOS 4 als Programmiersprache dabei war. Die speziellen Kommentare für den Basic-Boss-Compiler sind weg, dafür sind alle Integer-Variablen jetzt entsprechend mit dem % benannt um sie zu Integer-Variablen zu machen. GW-BASIC kennt Befehle für Grafik und um einzelne Zeichen abzufragen, deshalb muss nicht mehr mit PEEK/POKE/WAIT direkt auf Speicher und Hardware zugegriffen werden. IF/THEN kennt nun auch ein ELSE. Und der SWAP-Befehl macht die entsprechende Subroutine überflüssig.

Code: Alles auswählen

100 RANDOMIZE -TIMER:SL%=640:SC%=350:DP=.3:MV%=5:A%=1:CD=.35
105 DIM CP%(SL%),CV%(SL%)
110 PRINT"Create and place cars...":GOSUB 5000:SCREEN 9
200 FOR Y%=0 TO SC%-1:GOSUB 500:GOSUB 1000:NEXT
210 WHILE INKEY$="":WEND:GOTO 200
500 ' ******************************
510 ' One simulation step.
520 '
530 ' 1. Update velocities.
540 FOR I%=0 TO CC%:IF I%=CC% THEN J%=0 ELSE J%=I%+1
550 V%=CV%(I%)+A%:D%=CP%(J%)-CP%(I%)-1:IF D%<0 THEN D%=D%+SL%
560 IF V%>D% THEN V%=D%
570 IF V%>MV% THEN V%=MV%
580 IF V%>=1 THEN V%=V%+(RND(1)<DP)
590 CV%(I%)=V%:NEXT
700 ' 2. Update positions.
710 FOR I%=0 TO CC%:CP%(I%)=(CP%(I%)+CV%(I%)) MOD SL%:NEXT:RETURN
1000 ' ******************************
1010 ' Plot row.
1020 '
1030 LINE (0,Y%)-(SL%,Y%):FOR I%=0 TO CC%:PRESET (CP%(I%),Y%):NEXT:RETURN
5000 ' ******************************
5010 ' Create cars.
5020 '
5030 CC%=INT(SL%*CD)-1:FOR I%=0 TO SL%-1:CP%(I%)=I%:NEXT
5040 FOR I%=0 TO SL%-1:J%=INT(RND(1)*(SL%-I%))+I%:SWAP CP%(I%),CP%(J%):NEXT
5050 F%=0:FOR I%=0 TO CC%-1:J%=I%+1
5055 IF CP%(I%)>CP%(J%) THEN F%=-1:SWAP CP%(I%),CP%(J%)
5060 NEXT:IF F% THEN 5050
5070 RETURN
Eigentlich hätte man hier auch schon was mit den Variablennamen machen können, weil GW-BASIC mehr als zwei Zeichen erlaubt. Aber da GW-BASIC Programme oft von anderen Systemen portiert waren (wie in diesem Fall) oder dorthin portiert werden sollten, war es nicht unüblich die zwei Zeichen Grenze nicht zu überschreiten. Zudem sind nur Grossbuchstaben möglich was sich Variablennamen die aus mehr als einem Wort zusammengesetzt sind, unschön liest. Und es wird schwieriger Anweisungen und Funktionen von Variablen zu unterscheiden. Man hat bei GW-BASIC ja kein Syntaxhighlighting wie hier im Forum.

Edit: Doch mal mit längeren Namen:

Code: Alles auswählen

100 RANDOMIZE -TIMER:STREET.LEN%=640:STEPCOUNT%=350:CAR.DENSITY=.35:MAX.V%=5
105 ACCEL%=1:DECEL.PROB=.3:DIM CAR.POS%(STREET.LEN%-1),CAR.V%(STREET.LEN%-1)
110 PRINT"Create and place cars...":GOSUB 5000:SCREEN 9
200 FOR Y%=0 TO STEPCOUNT%-1:GOSUB 500:GOSUB 1000:NEXT
210 WHILE INKEY$="":WEND:GOTO 200
500 ' ******************************
510 ' One simulation step.
520 '
530 ' 1. Update velocities.
540 FOR I%=0 TO CC%:IF I%=CC% THEN J%=0 ELSE J%=I%+1
550 V%=CAR.V%(I%)+ACCEL%:D%=CAR.POS%(J%)-CAR.POS%(I%)-1
555 IF D%<0 THEN D%=D%+STREET.LEN%
560 IF V%>D% THEN V%=D%
570 IF V%>MAX.V% THEN V%=MAX.V%
580 IF V%>=1 THEN V%=V%+(RND(1)<DECEL.PROB)
590 CAR.V%(I%)=V%:NEXT
700 ' 2. Update positions.
710 FOR I%=0 TO CC%
715 CAR.POS%(I%)=(CAR.POS%(I%)+CAR.V%(I%)) MOD STREET.LEN%:NEXT:RETURN
1000 ' ******************************
1010 ' Plot row.
1020 '
1030 LINE (0,Y%)-(STREET.LEN%,Y%):FOR I%=0 TO CC%
1035 PRESET (CAR.POS%(I%),Y%):NEXT:RETURN
5000 ' ******************************
5010 ' Create cars.
5020 '
5030 CC%=INT(STREET.LEN%*CAR.DENSITY)-1
5035 FOR I%=0 TO STREET.LEN%-1:CAR.POS%(I%)=I%:NEXT
5040 FOR I%=0 TO STREET.LEN%-1:J%=INT(RND(1)*(STREET.LEN%-I%))+I%
5045 SWAP CAR.POS%(I%),CAR.POS%(J%):NEXT
5050 F%=0:FOR I%=0 TO CC%-1:J%=I%+1
5055 IF CAR.POS%(I%)>CAR.POS%(J%) THEN F%=-1:SWAP CAR.POS%(I%),CAR.POS%(J%)
5060 NEXT:IF F% THEN 5050
5070 RETURN
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dann kam mit MS-DOS 5 QBasic – Keine Zeilennummern mehr, und selbst die Sprungmarken durch die man sie ersetzen kann, braucht man nicht unbedingt, weil es Unterroutinen und Funktionen mit lokalen Variablen gibt. Variablennamen können Gross- und Kleinbuchstaben enthalten, auch wenn die nicht unterschieden werden. Aber man kann MixedCase schreiben und muss keine Punkte mehr verwenden um Worte zu trennen. Strukturierte Schleifen und IF/ELSE-Zweige die über mehr als eine Zeile gehen können. Und benutzerdefinierte zusammengesetzte Datentypen. Keine zwei Arrays mehr um die Autos zu beschreiben sondern nur noch eines bei dem jeder Eintrag ein Auto mit seinen zwei Eigenschaften beschreibt.

Code: Alles auswählen

CONST StreetLength = 640, StepCount = 480
CONST CarDensity = .35
CONST Acceleration = 1, MaxVelocity = 5, DecelerationProbability = .3

TYPE TCar
  Position AS INTEGER
  Velocity AS INTEGER
END TYPE

DIM i AS INTEGER
REDIM Car(0) AS TCar

RANDOMIZE -TIMER
PRINT "Create and place cars..."
CreateCars Car()
SCREEN 11
DO
  FOR i = 0 TO StepCount - 1
    SimulationStep Car()
    PlotRow i, Car()
    IF INKEY$ = CHR$(27) THEN EXIT DO
  NEXT
  SLEEP
LOOP UNTIL INKEY$ = CHR$(27)

SUB CreateCars (Car() AS TCar)
  DIM i AS INTEGER, NewCar(StreetLength - 1) AS TCar
  InitCars NewCar()
  ShuffleCars NewCar()
  REDIM Car(INT(StreetLength * CarDensity) - 1)
  FOR i = 0 TO UBOUND(Car)
    Car(i) = NewCar(i)
  NEXT
  SortCars Car()
END SUB

SUB InitCars (Car() AS TCar)
  DIM i AS INTEGER
  FOR i = 0 TO UBOUND(Car)
    Car(i).Position = i
    Car(i).Velocity = 0
  NEXT
END SUB

FUNCTION Min% (a AS INTEGER, b AS INTEGER)
  IF a < b THEN Min% = a ELSE Min% = b
END FUNCTION

SUB PlotRow (y AS INTEGER, Car() AS TCar) STATIC
  DIM i AS INTEGER
  LINE (0, y)-(StreetLength, y)
  FOR i = 0 TO UBOUND(Car)
    PRESET (Car(i).Position, y)
  NEXT
END SUB

SUB ShuffleCars (Car() AS TCar)
  DIM i AS INTEGER, j AS INTEGER
  FOR i = 0 TO UBOUND(Car)
    j = INT(RND(1) * (UBOUND(Car) + 1 - i)) + i
    SWAP Car(i), Car(j)
  NEXT
END SUB

SUB SimulationStep (Car() AS TCar) STATIC
  DIM i AS INTEGER, j AS INTEGER
  FOR i = 0 TO UBOUND(Car)
    IF i = UBOUND(Car) THEN j = 0 ELSE j = i + 1
    UpdateVelocity Car(i), Car(j)
  NEXT
  FOR i = 0 TO UBOUND(Car)
    Car(i).Position = (Car(i).Position + Car(i).Velocity) MOD StreetLength
  NEXT
END SUB

SUB SortCars (Car() AS TCar)
  DIM i AS INTEGER, j AS INTEGER, swapped AS INTEGER
  DO
    swapped = 0
    FOR i = 0 TO UBOUND(Car) - 1
      j = i + 1
      IF Car(i).Position > Car(j).Position THEN
        swapped = -1
        SWAP Car(i), Car(j)
      END IF
    NEXT
  LOOP WHILE swapped
END SUB

SUB UpdateVelocity (Car AS TCar, NextCar AS TCar) STATIC
  DIM Velocity AS INTEGER, Distance AS INTEGER
  Velocity = Car.Velocity + Acceleration
  Distance = NextCar.Position - Car.Position - 1
  IF Distance < 0 THEN Distance = Distance + StreetLength
  Velocity = Min(Min(Velocity, Distance), MaxVelocity)
  IF Velocity >= 1 THEN
    Velocity = Velocity + (RND(1) < DecelerationProbability)
  END IF
  Car.Velocity = Velocity
END SUB
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten