property: Möglichkeit, an "raw fset-value" zu kommen...?

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
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

ich (nicht allein, wie ich inzwischen nach einigem, was ich gelesen habe, weiß...) stehe vor folgendem Problem:

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self._x = 0

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

    @x.setter
    def x(self, value):
        self._x = value

Code: Alles auswählen

In [71]: f = Foo()

In [72]: f._x
Out[72]: 0

In [73]: f.x
Out[73]: 0

In [74]: f.x = 2

In [75]: f._x
Out[75]: 2

In [76]: f.x
Out[76]: 4

In [77]: f.x += 1

In [78]: f._x
Out[78]: 5

In [79]: f.x
Out[79]: 10
Mir ist klar, weshalb das so ist. Als Lösung fällt mir nur ein:
  • Direkt '_x' verändern und 'x' lesen
  • Das was im 'x.getter' geschieht im 'x.setter' erstmal wieder umkehren (funktioniert aber nur dann, wenn im getter lediglich etwas dazu- oder abgezogen wird)
  • 'property' vergessen und mit 'f.set_x()' und 'f.get_x()' arbeiten
Gefällt mir alles nicht wirklich... Ich suche eine Lösung, bei der ich im 'setter' mit einem 'raw-value' arbeite.
In meinem Beispiel ist es ja so, dass 'property()' mit 'x' (das vom 'x.getter' stammt) und dem Summand bzw. Subtrahend einen Wert ermitteln und diesen dann zum 'x.setter' schickt. Und mit 'raw-value' meine ich eben den Summand oder Subtrahend, den ich gerne hätte...

Während ich das hier so schreibe werd' ich das Gefühl nicht los, dass ich mir mal wieder meine eigene Grube schaufel... ;-) Ist das so?

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

Ja, du schaufelst mal wieder. Nenn _x wegen mir foo, und manipulier es, wenn du musst. Mal ehrlich - fuer wieviel promille deines Codes greift diese Problem? Insgesamt, in deinem Python-Programmierer-Leben - nicht fuer die 20 Zeilen, ueber die du jetzt bruetest?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Finde ich ebenfalls arg konstruiert. Wenn ich `f.x` unter Nutzung des (hoffentlich dokumentierten) Setter-Propertys gesetzt habe, dann muss ich mir auch im Klaren sein darüber, das mein Wert an der Stelle modifiziert wurde. Will ich eine Addition auf dem Ursprungswert durchführen, dann tu ich gut daran, mir diesen vorher irgendwo abzulegen oder einfach die Addition *vor* der Attributzuweisung zu machen. Umgekehrt, sofern die Klasse einen mir vorher unbekannten Wert ausspuckt, so habe ich eigentlich kein Interesse/Wissen von dem Ursprungswert. Für welches konkrete Problem soll die Frage also relevant sein?

Natürlich könnte man ein von `int` abgeleitetes Attribut setzen, welches `raw` kennt und dies für den Benutzer dokumentieren, aber wie gesagt: Was soll der Sinn davon sein? Etwa nur der Theorie wegen?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

snafu hat geschrieben:Was soll der Sinn davon sein?
Ich habe eine Klasse 'Frame()', deren Exemplare einen Bildschirmbereich darstellen. Die Berechnung der y-Koordinate sieht so aus:

Code: Alles auswählen

    @property
    def y(self):
        y = self._y % self.cols or self.cols
        y += (self.init_y - 1)
        return y
Somit ist es eben nicht möglich, die y-Koordinate direkt über das y-Attribut zu beeinflussen. Ich wollte z. B. einen Cursorschritt nach links mit 'self.y += 1' anstatt 'self._y += 1'.

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

Also auch noch zustandsveraenderung *innerhalb* der Klasse? Dann ist das doch voellig irrelevant. Biete von aussen wegen mir eine move(delta_x, delta_y)-methode an - aber innen in der Klasse arbeitest du natuerlich auf den "rohen" Attributen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich weiß zwar nicht, was du da genau machst, aber wenn du ein Element bewegen willst, bei dem du eine Position vorgaukeln musst (warum auch immer) und es bei der Bewegung auf die tatsächliche Position ankommt, dann würde ich doch eher etwas wie `.move(x=0, y=0)` anbieten. Damit wäre eine flexible, aber dennoch einfach gehaltene Schnittstelle geschaffen, weil für den Benutzer auch so Dinge wie `move(y=-1)` möglich sind. Das ist IMHO fast genau so intuitiv wie ein `f.y -= 1`, muss man halt nur in der Doku klarstellen. `.move` arbeitet dabei intern natürlich auf den "privaten" Attributen.

Abgesehen davon riecht aber die Tatsache, dass man solche Verrenkungen überhaupt machen muss, für mich arg nach nem Designfehler. Reicht es nicht, statt dem direkten Zugriff auf die Postionsattribute lieber etwas wie `self.position = (self._x, self._y)` und für die Veränderung eben `.move` zu haben?
deets

Alternativ kannst du auch saemtliche berechnungen in den settern machen. Ist im Zweifel auch noch performanter - allerdings natuerlich risikoreicher.

Ich mache bei sowas dann gerne eine art recaclulate-methode, die alles neu berechnet, und die constraints beruecksichtigt.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu & deets:
Ja, habe ich inzwischen auch so gemacht, insofern war meine Frage dann doch wieder theoretischer Natur. Ich tu' mir einfach schwer damit, etwas nicht zu machen, 'nur' weil es zu verwegen, kompliziert oder auf den ersten Blick nicht möglich scheint.
Im Falle der Koordinaten denke ich mittlerweile auch, dass es sinnvoller und auch klarer ist, die Beeinflussung von außen über Methodenaufrufe zu machen.

Trotzdem hätte mich interessiert, ob oder noch besser wie es machbar wäre. Denn ich kann mir nicht vorstellen, dass es nicht geht... Hab' schon ein klein wenig mit einem eigenen descriptor statt property() gespielt, bin aber bisher immer in die 'recursion-Falle' getappt... Und alle Fragen, die ich bisher zu diesem Thema auf stackoverflow gelesen hab' konnten auch nicht gelöst werden...

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

@mutetella: Vermischst Du an der Stelle nicht auf verwirrende Weise Cursor- und Pixelkoordinaten miteinander?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@mutetella: Das wird nicht zufällig eine Art Terminalemulation, oder allgeimeiner eine Art Cursorhandling über darstellbare Zeichen? Falls ja, geb ich Dir mal den Hinweis der überbreiten Unicodezeichen, die belegen mehr als eine Standardcharakterbreite in Monospace, da klappt das mit einer Modulooperation nicht mehr so einfach.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Ich meine Cursorpositionen in einem Terminal. Wobei ich nicht wüsste, welchen Unterschied das zu Pixelkoordinaten machen würde...

@jerch:
Ich arbeite gerade an einem Modul, das über eine erweiterte print()- und input()-Funktion verfügt. Das Problem der >1 breiten Unicodezeichen ist mir bekannt. Allerdings sind diese Sequenzen bereits übersetzt, bevor ich damit Positionsberechnungen durchführe.

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

@mutetella: Dann verstehe ich das Problem wohl nicht. Beziehungsweise ist die API irgend wie kaputt. `x` scheint beim Zuweisen etwas anderes darzustellen als beim Abfragen. Dann sollte es aber auch nicht den gleichen Namen haben. Nach ``o.x = v`` erwarte ich in der Regel das ``o.x == v`` gilt.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, wie gesagt. Dein Wert kann eine von `int` abgeleitete Klasse sein. Keine Ahnung, ob's perfekt ist, aber vielleicht so in der Art:

Code: Alles auswählen

>>> class MyInt(int):
...     def __init__(self, value=0, base=0):
...         int.__init__(self, value, base)
...         self._raw = 42
...     def __iadd__(self, value):
...         return self.__class__(self._raw + value)
... 
>>> v = MyInt(5)
>>> v
5
>>> v += 5
>>> v
47
>>> type(v)
<class '__main__.MyInt'>
Ich bin aber nach wie vor der Meinung, dass eine Änderung am Klassendesign hier die bessere Lösung ist.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Jepp, damit lässt sich das machen, wovon ich inzwischen schon wieder abgerückt bin... ;-)

Code: Alles auswählen

class MyInt(int):
    def __init__(self, value=0, base=0):
        int.__init__(self, value, base)
        self._raw_value = 0

    def __iadd__(self, value):
        old_raw_value = self._raw_value
        new_value = old_raw_value + value + 10
        new_instance = self.__class__(new_value)
        new_instance._raw_value = old_raw_value + value
        return new_instance

Danke für Eure Hilfe!

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht noch kurz zur "`__iadd__`-Lösung": Bedenke, dass du in dem Fall theoretisch *alle* Inplace-Operationen implementieren müsstest, die es so gibt, um wirklich konsequent zu sein. Und das sind viele, sehr viele (vgl. alles mit `i` am Anfang aus http://docs.python.org/library/operator.html).

Es gibt in Python AFAIK keine Abstraktion, um schnell zu sehen, ob eine "normale" oder ob eine Inplace-Operation angefordert wird. Und diese Erkennung kann IMHO auch nur auf einem abgeleiteten Datentyp stattfinden. Ich wüsste nicht, wie man das sonst machen sollte.

Ok, du hast Deskriptoren angesprochen. Ehrlich gesagt hab ich mich in das Thema bisher nicht eingearbeitet. Ich würde mich aber schon fragen, wie ein Deskriptor erkennen soll, wann er welchen Wert zurückgeben soll. Nach meinem Verständnis kann der irgendwie bei einem Zugriff auf ihn automatisch eine Art Getter-Methode benutzen (wie gesagt: keine Ahnung im Detail). Jedoch wird es wohl keine Möglichkeit geben, eine *Rechenoperation* zu erkennen. Das können meines Wissens einzig und allein die bereits beschriebenen magischen Methoden, die das ja im Grunde auch nicht von selbst erkennen können, sondern halt nur bei Bedarf aufgerufen werden (eben wenn ein bestimmter Operator als Symbol auftritt).
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hab' noch ein wenig versucht, das selbe Ergebnis mit einem Deskriptor zu basteln. Soweit ich das überblicke (ich stocher' noch arg im Nebel...) beißt sich die Katze damit aber in den Schwanz...

Werd' mich da jetzt aber nicht mehr reinknien, da ich zwischenzeitlich auch der Ansicht bin, dass diese Art der wundersamen Automatik letztlich für Schöpfer und Geschöpf nur Probleme mit sich bringt... :wink:

Wie BlackJack ja schon sagte: Wo 'x = 2' draufsteht sollte auch 'x = 2' drinsein...

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