eigene Rect Klasse

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Hab jetzt ne Rect Klasse am Start und sie... funktioniert noch nicht so richtig. Bin das Ganze am durchtesten aber irgendwo liegt der Hund begraben... sehr komisch finde ich, dass der print Befehl innerhalb der Rect Klasse nicht ausgeführt wird..? Normalerweise finde ich so den Fehler, aber wenns nix printed...

hier die Klasse:

Code: Alles auswählen

class My_Rect():

    def __init__(self, left, top, width, height, xyoffsets=(0, 0)):

        self.x_offset = int(xyoffsets[0])
        self.y_offset = int(xyoffsets[1])

        self._x = float(left) + self.x_offset
        self._y = float(top) + self.y_offset
        self._left = int(left)
        self._top = int(top)
        
        self.width = int(width)
        self.height = int(height)

    #### x y ####

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = float(value)
        self._left = int(self._x) - self.x_offset
        print "self._left set!", self._left

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y = float(value)
        self._top = int(self._y) - self.y_offset

    #### centerx centery ####

    @property
    def centerx(self):
        return self._left + self.width / 2

    @centerx.setter
    def centerx(self, value):
        self.left = int(value) - self.width / 2

    @property
    def centery(self):
        return self._top + self.height / 2

    @centery.setter
    def centery(self, value):
        self.top = int(value) - self.height / 2

    #### left right bottom top ####

    @property
    def left(self):
        return self._left

    @left.setter
    def left(self, value):
        self._left = int(value)
        self._x = float(self._left) + self.x_offset

    @property
    def top(self):
        return self._top

    @top.setter
    def top(self, value):
        self._top = int(value)
        self._y = float(self._top) + self.y_offset

    @property
    def right(self):
        return self._left + self.width

    @right.setter
    def right(self, value):
        self.left = int(value) - self.width

    @property
    def bottom(self):
        return self._top + self.height

    @bottom.setter
    def bottom(self, value):
        self.top = int(value) - self.height

    ### topleft topright bottomleft bottomright ###

    @property
    def topleft(self):
        return (self.left, self.top)

    @topleft.setter
    def topleft(self, pos):
        x = int(pos[0])
        y = int(pos[1])
        self.left = x
        self.top = y

    @property
    def topright(self):
        return (self.right, self.top)

    @topright.setter
    def topright(self, pos):
        x = int(pos[0])
        y = int(pos[1])
        self.left = x - self.width
        self.top = y

    @property
    def bottomleft(self):
        return (self.left, self.bottom)

    @bottomleft.setter
    def bottomleft(self, pos):
        x = int(pos[0])
        y = int(pos[1])
        self.left = x
        self.top = y - self.height

    @property
    def bottomright(self):
        return (self.right, self.bottom)

    @bottomright.setter
    def bottomright(self, value):
        x = int(pos[0])
        y = int(pos[1])
        self.left = x - self.width
        self.top = y - self.height
    
hier der Test:

Code: Alles auswählen

from my_rect import My_Rect

ni = My_Rect(0, 0, 16, 16, (8, 8))

print ni
print "x", ni.x
print "y", ni.y
print "left", ni.left
print "topleft", ni.topleft
print "topright", ni.topright
ni.x += 5
print "ni.x += 5 -->"
print "x", ni.x
print "y", ni.y
print "left", ni.left
print "topleft", ni.topleft
print "topright", ni.topright
..und das kommt raus:

Code: Alles auswählen

<my_rect.My_Rect instance at 0x02B861E8>
x 8.0
y 8.0
left 0
topleft (0, 0)
topright (16, 0)
ni.x += 5 -->
x 13.0
y 8.0
left 0
topleft (0, 0)
topright (16, 0)
topleft wird also offenbar nicht angepasst. Sieht jemand warum?
Ich code, also bin ich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Henry Jones Jr. hat geschrieben:topleft wird also offenbar nicht angepasst. Sieht jemand warum?
Ich habe es mir nicht angeschaut, aber deine Klass sieht nach einem kompletten Chaos aus. Ein Rechteck ist definiert durch vier Werte: x-Position, y-Position, Breite und Höhe; oder statt Breite und Höhe eben Rechts und Unten; oder über Winkel und Durchmesser, aber dann wirds gruselig. Du hast aber x, y, left, top, offset_x, offset_y, width und height. Dabei ist doch offensichtlich, dass du die Breite aus x-Position und Rechts herleiten kannst, oder die Höhe aus y-Position und Unten. Wenn du alle die Werte parallel hältst, dann kann das nur zu Problemen führen. Und das tut es bei dir auch. Schreibe deine Klasse neu und arbeite nur mit x, y, width und height. Oder x, y, right und bottem, was auch immer für dich praktischer ist (das macht auch semantisch einen Unterschied!).

Den Offset solltest du gar nicht als Parameter übergeben können. Implementiere die __add__-Methode, so dass du ``rect + (3, 4)`` schreiben kannst, um so eine Verschiebung zu erreichen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

EyDu hat geschrieben:Oder x, y, right und bottem, was auch immer für dich praktischer ist (das macht auch semantisch einen Unterschied!).
Warum das Rad neu erfinden? Dein erster Tipp war schon richtig, x, y, width, height sind konform. Für topleft, topright, bottomright, bottomleft usw. stellt man nur eine Funktion, die Differenzen zurückgibt, zur Verfügung. Da ist kein property notwendig.
EyDu hat geschrieben:Implementiere die __add__-Methode, so dass du ``rect + (3, 4)`` schreiben kannst, um so eine Verschiebung zu erreichen.
Sehr gute Empfehlung!
Zudem relevant wäre eventuell eine adjusted() - Methode, wie sie beispielsweise Qt für Rects anbietet. Das erhöht den Komfort im Umgang mit deiner Rect-Klasse erheblich.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Paraxiale Rechtecke, also solche, deren Seiten parallel zu den Achsen des Koordinatensystems liegen, bilden eine Algebra. Genauer gesagt, einen vollständigen Verband.

Hier ist meine Version: http://www.python-forum.de/pastebin.php?mode=view&s=385
und die Tests dazu: http://www.python-forum.de/pastebin.php?mode=view&s=386

Eine __add__() Methode fehlt noch.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@Madmartigan: Da hier `pygame.Rect` nachgebaut werden soll müssen `topleft` etc. Properties sein. Man kann da auch Werte zuweisen, die dann auf die interne Darstellung umgerechnet werden müssen.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

BlackJack hat geschrieben:@Madmartigan: Da hier `pygame.Rect` nachgebaut werden soll....
Ist für mich nicht ersichtlich. Wo liest du das?
BlackJack hat geschrieben:Man kann da auch Werte zuweisen, die dann auf die interne Darstellung umgerechnet werden müssen.
bottomright als Property zu implementieren und Werte direkt darauf zu setzen ist redundant, da bereits x, y, width, height hinreichend sind. Ich kenne zumindest keinen notwendigen Grund für komplementäre Properties wie bottomright etc. Dennoch lasse ich mich gern umstimmen, wenn dir ein Anwendungsfall einfällt.
BlackJack

@Madmartigan: Das lese ich aus dem Unterforum und in dem anderen Thema wo dem OP geraten wurde nicht `pygame.Rect` abzuleiten, sondern es selber zu implementieren. :-)

Und wie gesagt, `pygame.Rect` implementiert das so. Natürlich fällt mir auch ein Anwendungsbeispiel ein, denn in `pygame` verwendet man so etwas andauernd. Zum Beispiel um ein Sprite mittig auf dem Spielfeld zu platzieren: ``self.rect.center = screen.get_rect().center``. Oder wenn man eine Grafik mit einer Sprechblase links oben neben einem beliebigen Sprite platzieren möchte: ``bubble.rect.bottomleft = character.rect.topright``. Oder irgendwas mittig über einem Sprite: ``bulb.rect.midbottom = einstein.rect.move(0, -5).midtop``.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Hey ihr seid ja gut... krass! Danke für die vielen Tipps!

Ich habe heute leider zu tun, bin erst spät zuhause. Aber ich gehe das noch an in den nächsten Tagen, das zu fixen. Und ja, BlackJack hat recht: Wenn ich eine property des Rects anpasse (egal welche), müssen x und y (floats) angepasst werden. Wenn ich x / y ändere, müssen left / top geändert werden. Deshalb brauche ich zwingend _x, _y, _top und _left, damit ich diese Variablen ändern kann, ohne eine Endlosschleife zu erzeugen...!

Ich hatte wirklich gedacht ich hätte das richtig gecodet! Kann mir jemand trotzdem sagen, warum das print in der Klasse nicht ausgeführt wird? Darauf kann ich mir wirklich keinen Reim machen; ich komme so nicht weiter. Wenn ich das x property anpasse, sollte es doch was printen? Sollte doch glasklar sein? Da möchte ich mal ansetzen weil ich das nicht verstehe.
Ich code, also bin ich.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Da Du Python 2.x verwendest, sollte Deine Klasse von object erben.

@pillmuncher: Wo ist die Lila Pille des lambda-Kalküls erhältlich? Ich in meinem mittleren Alter mit (lediglich) FOR muss mich schon schwer anstrengen, um Deinen Beispielen noch folgen zu können ;)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

BlackJack hat geschrieben:@Madmartigan: Das lese ich aus dem Unterforum und in dem anderen Thema wo dem OP geraten wurde nicht `pygame.Rect` abzuleiten, sondern es selber zu implementieren. :-)
Nun gut, ich lese nun nicht wirklich jeden Thread, die Fähigkeit zum Desinteresse hinsichtlich bestimmter Themennamen erlaubt es mir. :wink:

Der Anwendungsfall ist durchaus plausibel, erzwingt aber nicht das Vorhandensein dieser zusätzlichen Properties. Ich zentriere Rects bzw. Quads z.B. für unser hausinternes UI-Framework, insofern es manuell notwendig ist, so

Code: Alles auswählen

self.center(self.parent().center())
Da ist kein zusätzliches Property erforderlich, center() greift auf x, y, width, height zu. Das gleiche gilt für das ganze Layouting/Snapping. Alle Border/Corner-Features kommunizieren über Funktionsaufrufe, setzen aber keine Properties anderer Klassen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Madmartigan hat geschrieben:Warum das Rad neu erfinden? Dein erster Tipp war schon richtig, x, y, width, height sind konform.
Wie ich schon schrieb: Es kommt auf den Kontext an, welche Implementierung sinnvoller ist. Beide Varianten sind üblich.
Madmartigan hat geschrieben:Für topleft, topright, bottomright, bottomleft usw. stellt man nur eine Funktion, die Differenzen zurückgibt, zur Verfügung. Da ist kein property notwendig.
Properties sind Funktionen die für genau solche Zugriffe gedacht sind. ``bottomright`` leitet aus x, y, width und height die untere rechte Position her.
Henry Jones Jr. hat geschrieben:Kann mir jemand trotzdem sagen, warum das print in der Klasse nicht ausgeführt wird? Darauf kann ich mir wirklich keinen Reim machen; ich komme so nicht weiter. Wenn ich das x property anpasse, sollte es doch was printen? Sollte doch glasklar sein? Da möchte ich mal ansetzen weil ich das nicht verstehe.
Erstelle eine Kopie des Moduls und streiche so lange überflüssigen Code, bis nur noch der Fehler übrig bleibt. Oder baue einfach jede Menge print-Aufrufe ein, um die genauen Aufrufe zu verfolgen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

EyDu hat geschrieben:Wie ich schon schrieb: Es kommt auf den Kontext an, welche Implementierung sinnvoller ist. Beide Varianten sind üblich.
Properties sind Funktionen die für genau solche Zugriffe gedacht sind. ``bottomright`` leitet aus x, y, width und height die untere rechte Position her.
Sinnvoll ist, einer Klasse keine anderen Variablen als die notwendigen zu spendieren. Die Diskussion ist sicher müßig...
Eine simple Funktion bottomright(*args) oder eben center(*args) aus meinem Beispiel leistet bereits alles, was via property Decorator, zusätzlicher Setter-Funktion und Variablen für _left, _x usw verfügbar gemacht wird. Der Mehraufwand an Code und Verwaltung ist offensichtlich.
BlackJack

@Madmartigan: Man kann genau so andersherum argumentieren das keine Funktionen nötig sind die auch noch so eine blöde API haben das sie zwei Operationen vermischen und man durch die API schon festgelegt ist was als tatsächliche Werte gespeichert ist und was als Funktion abgebildet wird. Der Mehraufwand ist IMHO auch nicht offensichtlich, Du machst im Grunde das gleiche nur IMHO deutlich unschöner. Und die Klasse hat auch so ziemlich die gleiche Anzahl von Attributen. Ob `bottomright` nun an eine Funktion oder an ein Property-Objekt gebunden wird, ändert an der Anzahl ja nichts.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@Madmartigan: Es gibt überhaupt keine zusätzlichen Attribute, lediglich zusätzliche Zugriffsmethoden/Properties. Und wenn man Zugriff über Properties haben möchte, was in diesem Kontext durchaus verständlich ist, dann muss man diesen Weg eben gehen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ich verwende für Attribute, welche sich wie Werte verhalten Properties (x, y, center, topleft, left, etc). Wenn diese etwas tun (wie zum Beispiel move, resize, etc.) Methoden.

Wobei ich im konkreten Fall x, y, width und height einfach öffentlich in der __init__ definiere.

Wobei tatsächlich nur die notwendigsten Daten vorgehalten werden sollen -> Redundanz vermeiden -> Fehler vermeiden.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Madmarigan: welche der beiden Varianten ist weniger Code?

Code: Alles auswählen

def center(self,*args):
    if args:
        self.set_center(*args)
    else:
        return self.get_center()
und

Code: Alles auswählen

center = property(get_center, set_center)
Je nach Vorlieben können "get_center" und "set_center" auch gleich inline definiert werden, der Mehraufwand wird hier aber meiner Meinung nach deutlicher.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Um meiner obigen Aussage noch etwas mehr Gewicht zu verleihen, erkennt man bei Properties eine hat-Beziehung sofort.

Als Methode wird das erst durch die Dokumentation deutlich. Es gibt das Verb to center. Richtig genommen müsste center(*args) in center_at(*args) umbenannt werden.

Mein Messer für Haarspaltereien wird langsam stumpf ;)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

bwbg hat geschrieben:Wobei tatsächlich nur die notwendigsten Daten vorgehalten werden sollen -> Redundanz vermeiden -> Fehler vermeiden.
Exakt. Aber vielleicht ist dieses Bestreben auch nur ein LowLevel-Relikt...

Code: Alles auswählen

class UIGenericRect(object):
    ''''''
    def __init__(self, (x, y, w, h)=(0.0, 0.0, 0.0, 0.0)):
        ''''''
        self.x = x
        self.y = y
        self.width = w
        self.height = h

    def center(self, target_pos=None):
        ''''''
        if not target_pos:
            return (0.5*self.width + self.x,
                    0.5*self.height + self.y)
        self.x = target_pos[0] - 0.5*self.width
        self.y = target_pos[1] - 0.5*self.height
Ganz ohne Decorator, property Deklarationen und zusätzliche Setter.
BlackJack hat geschrieben:@Madmartigan: Man kann genau so andersherum argumentieren das keine Funktionen nötig sind die auch noch so eine blöde API haben das sie zwei Operationen vermischen und man durch die API schon festgelegt ist was als tatsächliche Werte gespeichert ist und was als Funktion abgebildet wird. Der Mehraufwand ist IMHO auch nicht offensichtlich, Du machst im Grunde das gleiche nur IMHO deutlich unschöner. Und die Klasse hat auch so ziemlich die gleiche Anzahl von Attributen. Ob `bottomright` nun an eine Funktion oder an ein Property-Objekt gebunden wird, ändert an der Anzahl ja nichts.
Seit wann besitzen APIs ein Attribut "blöde". Was ist am Verzicht auf getter/setter im OOP-Kontext bitte unschön?
Die Dualität einer Methode muss niemandem Angst machen. Ich vermische nichts im Sinne von:

Code: Alles auswählen

def center(*args):
    if args:
        make_it_nice()
    else:
        launch_the_bomb()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Madmartigan hat geschrieben:
bwbg hat geschrieben:Wobei tatsächlich nur die notwendigsten Daten vorgehalten werden sollen -> Redundanz vermeiden -> Fehler vermeiden.
Exakt. Aber vielleicht ist dieses Bestreben auch nur ein LowLevel-Relikt...
Bei keinem der Ansätze wurde das Speichern von unnötigen Daten vorgeschlagen.
Madmartigan hat geschrieben:Ganz ohne Decorator, property Deklarationen und zusätzliche Setter.
Vielleicht möchte man aber gerade ``rect.center = 23, 42`` schreiben, weil es lesbarer ist als ``rect.center(23, 42)``. Oder eben ``rect.center + (2, 3)`` statt ``rect.center() + (2,3)``. Spannend wird es dann noch bei ``rect.center += (2,3)``, das wird mit Funktionen ein wenig unschöner.
Madmartigan hat geschrieben:Seit wann besitzen APIs ein Attribut "blöde". Was ist am Verzicht auf getter/setter im OOP-Kontext bitte unschön?
Die Dualität einer Methode muss niemandem Angst machen.
Angst machen nicht, trotzdem ist es nicht unbedingt schön.
Das Leben ist wie ein Tennisball.
BlackJack

@Madmartigan: Du verzichtest ja gar nicht auf `getter`/`setter` sondern vermischst das bloss in einer Methode. Die dadurch zwei engegengesetzte Operationen durchführt. Mal einen Rückgabwert hat und mal mal nicht. Und einen Namen hat der nicht verrät das es sich um einen get/set handelt. `center()` hat als Namen noch das zusätzliche Problem das es eine Tätigkeit sein kann, also die Vermutung nahelegt, dass ein Aufruf ohne Argumente das Objekt auf irgendeine Weise zentriert.

Und ich sehe jetzt den Mehraufwand nicht wirklich. Klar das sind drei Zeilen mehr, aber fallen die wirklich ins Gewicht? Falls man sie überhaupt tippen muss und der Editor/die IDE das nicht durch ein Template für Properties wieder wett macht.

Code: Alles auswählen

class UIGenericRect(object):
    """"""
    def __init__(self, (x, y, w, h)=(0.0, 0.0, 0.0, 0.0)):
        ''''''
        self.x = x
        self.y = y
        self.width = w
        self.height = h
 
    @property
    def center(self):
        """"""
        return (0.5 * self.width + self.x, 0.5 * self.height + self.y)
    
    @center.setter
    def center(self, (x, y)):
        self.x = x - 0.5 * self.width
        self.y = x - 0.5 * self.height
Antworten