Klasse verhält sich merkwürdig

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
lalelu169
User
Beiträge: 12
Registriert: Dienstag 7. April 2020, 10:56

Code: Alles auswählen

class Vektor2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        if not isinstance(other, Vektor2D):
            raise ValueError(f'{other} ist kein Vektor: Keine Addition möglich')
        return Vektor2D(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f" {self.x:.2f}, {self.y:.2f}"

    def __repr__(self):
        return f"Vektor2D({self.x}, {self.y})"


class Ball:
    def __init__(self, idx, position=Vektor2D(0,30), geschwindigkeit=Vektor2D(0, 0),
                 beschleunigung=Vektor2D(0, 3)):
        self.id = idx
        self.position = position  # Position des Balls
        self.geschwindigkeit = geschwindigkeit  # Ausgangsgeschwindigkeit in beide Richtungen
        self.beschleunigung = beschleunigung  # Beschleunigung in beide Richtungen

    def __str__(self):
        return f'Ball Nr {self.id} mit Geschwindigkeit {self.geschwindigkeit} auf pos {self.position}'

    def update(self):
        self.geschwindigkeit.y += self.beschleunigung.y
        self.position.y += self.geschwindigkeit.y

class Game:
    def __init__(self):
        self.ball = Ball(idx=1, position=Vektor2D(0, 20))
        self.ball2 = Ball(idx=2, position=Vektor2D(0, 20))

    def run(self):
        for _ in range(3):
            self.ball.update()
            self.ball2.update()
            # Eigentlich sollten die Bälle nun gleich sein!!
            print(f'Ball 1 nach Update: {self.ball}')
            print(f'Ball 2 nach Update: {self.ball2}')
            print('--------------------------------')


game = Game()
game.run()
Ich komme bei diesem Problem nicht weiter. Eigentlich sollten Ball1 und Ball2 nach dem beiderseitigen Update identisch sein. Sind sie leider nicht. Nach dem ersten Durchlauf der Schleife hat Ball 1 die Position 0,23 und Ball 2 die Position 0,26. Irgendwie wird bei Ball2 die Geschwindigkeit zweimal addiert. Das bleibt auch so nach den anderen Schleifendurchläufen.

Ich vermute, dass es etwas mit der update Methode in der Ball Klasse zu tun hat, da es funktioniert, wenn ich das .y weglasse. Ich kann mir allerdings nicht erklären, warum mein Code zu einem falschen Ergebnis führt.

Kann mir da bitte jemand weiterhelfen?
Benutzeravatar
/me
User
Beiträge: 3558
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Als Parameter für __init__ hast du u.a. geschwindigkeit=Vektor2D(0, 0). Vektor2D(0, 0) wird bereits während des Kompilierens in Bytecode ausgewertet, nicht erst zur Laufzeit. Da du bei der Erstellung der Ball-Instanzen diesen Parameter nicht mitgibst enthält er somit in beiden Instanzen das selbe Objekt.

Hier ein simples Besipiel mit einer Liste als Default-Parameter:

Code: Alles auswählen

class Foo:
    def __init__(self, x=[]):
        self.x = x

bar = Foo()
bar.x.append("Monty")
baz = Foo()
print(baz.x)
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@lalelu169: Fehlt noch die Lösung dieses Problems: Entweder man nimmt für Defaultwerte Objekte die man nicht verändert, was ja in `Ball.update()` passiert. Oder man nimmt als Defaultwert `None`, testet darauf in der `__init__()` und erstellt gegebenfalls dort einen immer neuen Wert um sicherzustellen, dass sich die Objekte keinen ungewollten Zustand teilen.

Der `isinstance()`-Test ist unpythonisch. Man kann einfach alles addieren was `x` und `y` als Attribute hat. Wenn man auf solche Einschränkungen steht, sollte man das trotzdem nicht zur Laufzeit prüfen, sondern Typannonationen schreiben und ein Werkzeug wie MyPy verwenden um die statisch zu prüfen.

In der `__repr__()` sollte man den Klassennamen nicht hart kodieren, sondern über das Objekt selbst dynamisch ermitteln. Ausserdem die Werte von den Attributen in der `repr()`-Darstellung formatieren.

Das Argument `idx` an das Attribut `id` zu binden ist irreführend. `idx` wird oft als Abkürzung für `index` benutzt, ich habe das noch nie für eine `id` gesehen. Es ist auch eher ungewöhnlich wenn ein Objekt ein `id`-Attribut hat, sofern das nicht von irgendeinem externen System benutzt wird, zum Beispiel bei Datenbanken. Eine Identität haben Objekte ja schon dadurch das sie Objekte sind. Das kann man als ganze Zahl ausgedrückt mit der `id()`-Funktion abfragen. Von einer ID erwartet man auch das die Eindeutig ist, und wenn man das selbst vergibt, muss man diese Eigenschaft auch selbst sicherstellen.

Bei ``self.position = position # Position des Balls`` in der `Ball.__init__()` macht der Kommentar keinen Sinn. Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Die `Game`-Klasse macht so keinen Sinn. Das ist im Moment einfach nur eine zu kompliziert geschriebene Funktion. Der Code würde hier (noch) in die `main()` gehören.

Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste.

Code: Alles auswählen

#!/usr/bin/env python3


class Vektor2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"{self.__class__.__name__}({self.x!r}, {self.y!r})"

    def __str__(self):
        return f"{self.x:.2f}, {self.y:.2f}"

    def __add__(self, anderer):
        return Vektor2D(self.x + anderer.x, self.y + anderer.y)


class Ball:
    def __init__(
        self, position, beschleunigung, geschwindigkeit=Vektor2D(0, 0)
    ):
        self.position = position
        self.geschwindigkeit = geschwindigkeit
        self.beschleunigung = beschleunigung

    def __str__(self):
        return (
            f"Ball mit Geschwindigkeit {self.geschwindigkeit}"
            f" auf pos {self.position}"
        )

    def aktualisieren(self):
        self.geschwindigkeit += self.beschleunigung
        self.position += self.geschwindigkeit


def main():
    baelle = [Ball(Vektor2D(0, 20), Vektor2D(0, 3)) for _ in range(2)]
    for _ in range(3):
        for nummer, ball in enumerate(baelle, 1):
            ball.aktualisieren()
            print(f"Ball {nummer} nach Update: {ball}")
        print("--------------------------------")


if __name__ == "__main__":
    main()
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Alternativ/unterstützend mit `attrs` um die `Vektor2D`-Klasse ”einzufrieren” und sich die `__init__()` und `__repr__()`-Methode zu sparen. `__hash__()` und `__eq__()`/`__neq__()` gibt's auch noch dazu.

Code: Alles auswählen

#!/usr/bin/env python3
from attrs import define, field, frozen


@frozen
class Vektor2D:
    x = field(default=0)
    y = field(default=0)

    def __str__(self):
        return f"{self.x:.2f}, {self.y:.2f}"

    def __add__(self, anderer):
        return Vektor2D(self.x + anderer.x, self.y + anderer.y)


@define
class Ball:
    position = field()
    beschleunigung = field()
    geschwindigkeit = field(factory=Vektor2D)

    def __str__(self):
        return (
            f"Ball mit Geschwindigkeit {self.geschwindigkeit}"
            f" auf pos {self.position}"
        )

    def aktualisieren(self):
        self.geschwindigkeit += self.beschleunigung
        self.position += self.geschwindigkeit


def main():
    baelle = [Ball(Vektor2D(0, 20), Vektor2D(0, 3)) for _ in range(2)]
    for _ in range(3):
        for nummer, ball in enumerate(baelle, 1):
            ball.aktualisieren()
            print(f"Ball {nummer} nach Update: {ball}")
        print("--------------------------------")


if __name__ == "__main__":
    main()
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
lalelu169
User
Beiträge: 12
Registriert: Dienstag 7. April 2020, 10:56

Vielen Dank für die Hinweise zur pythonic Programmierung.

Auf das mit der Parameterübergabe wäre ich selbst nicht gekommen. Das hjat ungemein geholfen
Antworten