unterschied von x+= und x=x+

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.
Antworten
Bobby-John-Joe
User
Beiträge: 7
Registriert: Freitag 23. September 2011, 19:50

Wie der Titel schon sagt, würde mich aus aktuellem Anlass der unterschied zwischen x+= und x=x+ interessieren. Ich habe das als ich mit Python angefangen habe für einfache Beispiele irgendwo gelesen, dass es sich dabei um äquivalente Schreibweisen handlet und seit dem nicht mehr hinterfragt. Jetzt musste ich feststellen, dass dem nicht so ist. Finde aber über google nichts...

Gruß,
Bobby
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Wie kommst Du darauf, dass dem nicht so ist...? Hast Du ein Beispiel?

Laut Doku zur '__iadd__'-Methode wird auf ein Klassenobjekt mit '__iadd__'-Methode (+=) eben diese aufgerufen, wenn selbige nicht definiert ist, wird auf '__add__' (+) zurückgegriffen. Vereinfacht ausgedrückt.

Demnach sollte zwischen 'x += 5' und 'x = x + 5' kein vordergründiger Unterschied bestehen...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Wenn x eine Instanz einer eigenen definierten Klasse ist, kann zwischen x.__add__ und x.__iadd__ natürlich ein Unterschied wie zwischen Tag und Nacht bestehen. Es ist sogar möglich (wenn auch davon abgeraten wird), dass beide Methoden Seiteneffekte hervorrufen, also zum Beispiel Dateien bearbeiten, Datenbanken manipulieren etc.. Was bei x+=5 und x=x+5 passiert, hängt also davon ab, wie x.__iadd__ bzw. x__add__ definiert sind. Wenn x ein int-Objekt ist, macht es keinen offensichtlichen Unterschied (intern passiert im Interpreter etwas anderes, Optimierungen unterschieden sich und so weiter, aber das ist für die meisten uninteressant).
Bobby-John-Joe
User
Beiträge: 7
Registriert: Freitag 23. September 2011, 19:50

Danke erst mal für die antworten, evtl schau ich später noch ob ich ein minibeispiel zusammen bekomme, welches den selben 'Fehler hat'.

Aufgefallen ist es mir in folgender Situation:
Ich bin gerade dabei ein Programm zum Kartenspielen zu schreiben. Dabei hat die Klasse Spieler das Attribut 'handkarten', also eine liste der Eigenen Karten. Das Hauptprogramm erzeugt dann 4 Spieler, mischt die Karten und Verteilt diese (übergibt also eine Liste mit 8 Karten an Jeden Spieler).
Nachdem es später die Möglichkeit geben soll auch 2 mal 4 Karten zu geben muss ich die Liste der neuen übergeben Karten an 'handkarten' anhängen (könnten ja schon Karten auf der Hand sein). Das habe ich zunächst so gemacht:

Code: Alles auswählen

def aufnehmen(self,karten):
    self.__handkarten+=karten
Das hat dann dazu geführt, dass nach dem Austeilen ALLE Spieler 32 Karten hatten. Keine Ahnung wieso, aber mit dem Aufruf

Code: Alles auswählen

Spieler1.aufnehmen(karten)
wurde zwar nicht die Methode 'aufenehmen' der anderen Spieler aufgerufen (soweit so gut), aber das jeweilige (private) Attribut __handkarten wurde gesetzt... (wie auch immer)

Schreibe ich hingegen:

Code: Alles auswählen

def aufnehmen(self,karten):
    self.__handkarten=self.__handkarten+karten
dann läuft alles wie gewollt und jeder bekommt 8 individuelle Karten.

Ich schau mal ob ich ein minibeispiel hinbekomme...

Ging recht fix:

Code: Alles auswählen

class Spieler(object):
   __handkarten=[]
   def __init__(self):
      pass

   def aufnehmen(self,karten):
      self.__handkarten+=karten

   def zeige(self):
      print self.__handkarten

class Tisch(object):
    deck=['OE','OG','OH','OS','UE','UG','UH','US','AE','ZE','KE','9E','8E','7E','AG','ZG', 'KG','9G','8G','7G','AH','ZH','KH','9H','8H','7H','AS','ZS','KS','9S','8S','7S']
   def __init__(self):
      pass

   def einladen(self):
      self.mitspieler=[]
      for i in range(0,4):
         neu=Spieler()
         self.mitspieler.append(neu)

   def verteilen(self):
      for i in range(0,4):
         self.mitspieler[i].aufnehmen(self.deck[i*8-1:i*8+7])

if __name__=='__main__':
   t=Tisch()
   t.einladen()
   t.verteilen()
   for i in t.mitspieler:
      i.zeige()

mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Deine Klasse 'Spieler()' verwendet ein Klassenattribut '__handkarten'. Klassenattribute sind im Gegensatz zu Instanzattributen für alle Exemplare (Instanzen) dieser Klasse gültig.

Wenn Du nun dieses Attribut per '__iadd__' veränderst, geschieht folgendes:
'karten' wird an das Spieler-Exemplar übergeben und dort in-place (daher das i bei iadd) dem Klassenattribut '__handkarten' (das für alle anderen Exemplare auch gültig ist) aufaddiert.

Wenn Du per '__add__' addierst, geschieht das:
'karten' wird an das Spieler-Exemplar übergeben. Durch

Code: Alles auswählen

self.__handkarten = ...
wird eine (neue) Zuweisung an das dadurch (neu) angelegte Instanzattribut '__handkarten' eingeleitet. Diesem neuen Attribut wird dann durch

Code: Alles auswählen

... self.__handkarten + karten
der Additionswert vom Klassenattribut '__handkarten' und dem Übergabewert 'karten' zugewiesen.

Mit anderen Worten: Mit '__iadd__' veränderst Du das Klassenattribut, das für alle Exemplare gültig ist. Mit '__add__' legst Du ein neues Instanzattribut mit selben Namen an und weist diesem Attribut den Additionswert zu.

Folgendes verdeutlicht es vielleicht noch ein wenig (ich habe den doppelten Unterstrich entfernt, macht an dieser Stelle (und an kaum einer anderen ;-) ) keinen Sinn:

Code: Alles auswählen

class Spieler(object):
    handkarten = []

    def aufnehmen(self, karten):
        self.handkarten = self.handkarten + karten

>>> s = Spieler()
>>> id(s.handkarten)
45167464
>>> s.aufnehmen([1, 2])
>>> id(s.handkarten)
45058528


class Spieler(object):
    handkarten = []

    def aufnehmen(self, karten):
        self.handkarten += karten

>>> s = Spieler()
>>> id(s.handkarten)
45058240
>>> s.aufnehmen([1, 2])
>>> id(s.handkarten)
45058240
Gruß
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Bobby-John-Joe
User
Beiträge: 7
Registriert: Freitag 23. September 2011, 19:50

Alles klar, vielen Dank! Der Unterschied zwischen Klassen- und Instanzattributen war mir nicht bewusst. Das mit den Unterstrichen war nur ein versuch bei der Fehlersuche gewesen, die kommen jetzt wieder weg :)

Gruß,
Bobby
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Noch ein paar Kleinigkeiten, die mir aufgefallen sind:
  • Wie schon gesagt, doppelte Unterstriche würde ich vermeiden. Attribute oder Methoden mit einem Unterstrich markieren Namen, die nur innerhalb der Klasse/Methode verwendet werden und für den äußeren Code nicht von Bedeutung sind. 'handkarten' gehört nicht dazu.
  • Konstanten wie 'deck' schreibt man in Großbuchstaben. Dadurch sehe ich im Code, dass es sich um einen festen Wert, wie z. B. Konfigurationsparameter, handelt.
  • Bei einer Zuweisung gehören zwischen Name und Wert ein Leerschritt.
  • Ebenso zwischen Rechenoperatoren, sofern sie bei der Übergabe an eine Funktion/Methode (also zwischen den Klammern) verwendet werden.
  • Setze auch einen Leerschritt zwischen Listen-/Tuple-Elemente oder Parameter bei der Übergabe
  • Wenn Du '__init__' nicht verwendest, könntest Du es auch gleich weglassen. Das ist aber jetzt meine persönliche Ansicht.
  • Die Definition von Attributen würde ich immer in der '__init__' vornehmen, auch wenn eigentliche Werte erst woanderst zugewiesen werden. In der Regel geht man davon aus, dass verwendete Attribute dort erzeugt werden.
  • Verwende zur Einrückung immer 4 Leerzeichen und hier im Forum das Code-Tag.
  • Lass Zeilen nicht länger als 79 Zeichen sein.
Dein Code sollte demnach so ausschauen (nur auf die Formatierung bezogen, siehe dazu auch PEP8):

Code: Alles auswählen

class Spieler(object):
    def __init__(self):
        self.handkarten = []

    def aufnehmen(self, karten):
        self.handkarten += karten

    def zeige(self):
        print self.handkarten

class Tisch(object):
    DECK = ['OE', 'OG', 'OH', 'OS', 'UE', 'UG', 'UH', 'US', 'AE', 'ZE', 'KE',
            '9E', '8E', '7E', 'AG', 'ZG', 'KG', '9G', '8G', '7G', 'AH', 'ZH',
            'KH', '9H', '8H', '7H', 'AS', 'ZS', 'KS', '9S', '8S', '7S']

    def __init__(self):
        self.mitspieler = []

    def einladen(self):
        self.mitspieler = []
        for i in range(0, 4):
            self.mitspieler.append(Spieler())

    def verteilen(self):
        for i in range(0, 4):
            self.mitspieler[i].aufnehmen(self.deck[i*8-1:i*8+7])

if __name__ == '__main__':
    t = Tisch()
    t.einladen()
    t.verteilen()
    for mitspieler in t.mitspieler:
        mitspieler.zeige()
Das mag jetzt erstmal nach Korinthenkackerei ausschauen, aber mit gut lesbarem Code, der sich an gängige Konventionen hält, hast Du (und auch andere, die Deine Programme lesen möchten) mehr Freude...

Deine for-Schleifen sind so IMHO nicht ganz glücklich...

Gruß
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Bobby-John-Joe
User
Beiträge: 7
Registriert: Freitag 23. September 2011, 19:50

darauf hab ich jetzt irgendwie auch gewartet ;) bin neulich mal über PEP8 gestolpert, da is mir aufgefallen, dass ich da einiges nicht so ganz sauber mache... Aber werde mich in Zukunft bemühen, aber speziell die vielen 'überflüssigen' Leerzeichen werden einige zeit brauchen bis sie sich eingeschliffen habe.

Andere Frage noch zwecks Code im Forum: wie bekomme ich Syntax highlighting? Sonst siehts immer so unübersichtlich aus...

Gruß,
Bobby
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

Die relevante Stelle aus der Doku ist der letzte Satz von
An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@Bobby-John-Joe
Indem Du an den Anfang Deines Codes den Codetag python oder code=python (muss dann in eckigen Klammern stehen!) setzt und diesen am Ende mit /python oder /code (ebenfalls in eckigen Klammern!) abschließt.
Oder Du markierst den Codeabschnitt und klickst dann auf 'python', direkt über dem Texteingabefeld, unter dem Wort 'Schriftgröße'...

Gruß
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten