Properties gleichzeitig als (statische) Methoden verwenden

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
nezzcarth
User
Beiträge: 574
Registriert: Samstag 16. April 2011, 12:47

Donnerstag 18. September 2014, 09:29

Hallo,

ich bin in Objektorientierter Programmierung leider nicht besonders bewandert. Ich habe eine Klasse, die (vereinfacht) so aussieht:

(python3)

Code: Alles auswählen

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

    @property
    def p1(self):
        return self.x*self.y*self.z
    
    @property
    def pn(self)
    ....
Ich habe mehrere solcher Properties, die teilweise voneinander abhängen und würde nun allerdings jedes @property auch gerne als Methode (bzw. Staticmethod?), verwenden können, wenn ich es mit Parametern aufrufe. Einerseits denke ich, dass das möglicherweise unsauber ist, andererseits würde ich so viel Code sparen. Gibt es da vielleicht eine Möglichkeit, das zu vereinfachen?

Vielen Dank :)
BlackJack

Donnerstag 18. September 2014, 09:42

@nezzcarth: Ich kann mir unter Beschreibung nichts vorstellen, beziehungsweise zu viel verschiedenes. Kannst Du mal ein bisschen konkreter werden?
nezzcarth
User
Beiträge: 574
Registriert: Samstag 16. April 2011, 12:47

Donnerstag 18. September 2014, 09:50

@BlackJack:

Die Klasse sieht (leicht gekürzt) so aus; es handelt sich um die Berechnung von Spielwerten für ein Schiff, abhängig von den Maßen und der Anzahl der Masten.

Code: Alles auswählen

class Ship:
    def __init__(self, length, width, depth, masts, name="Nameless Ship"):
        self.length=length
        self.width=width
        self.depth=depth
        self.masts=masts
        self.name=name

    @property
    def tonnage(self):
        return round((self.length*self.width*self.depth)/3)

    @property
    def capacity(self):
        return round(self.tonnage*2/3)

    @property
    def crew(self):
        return divmod(self.tonnage, 10)[0] + self.masts*10
    
    def print_stats(self):
        print('Name: {0}'.format(self.name))
        print('Länge: {0}'.format(self.length))
        print('Breite: {0}'.format(self.width))
        print('Tiefgang: {0}'.format(self.depth))
        print('Masten: {0}'.format(self.masts))
        print('Schiffsraum: {0}'.format(self.tonnage))
        print('Frachtraum: {0}'.format(self.capacity))
        print('Mannschaft: {0}'.format(self.crew))
Ich würde jetzt gerne ein Property wie z.B. 'crew' zusätzlich optional auch wie eine 'normale' Funktion mit Parametern aufrufen können. Ich könnte natürlich auch eine eigene Funktion dafür schreiben, hätte aber eine Codeverdoppelung.

EDIT:

Code: Alles auswählen

#So verwende ich es:
>>> ship = Ship(20,6,2,3)
>>> ship.crew
38
>>> ship.tonnage
80
#Und so würde ich es gerne optinal auch verwenden können:
>>> ship.crew(80,2)
Ist das möglicherweise unsauber, weil die Instanz an sich ja auf ein konkretes Schiff referiert, der Aufruf mit Parametern aber ja generalisiert ist?
Zuletzt geändert von nezzcarth am Donnerstag 18. September 2014, 10:01, insgesamt 2-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7472
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Donnerstag 18. September 2014, 09:56

nezzcarth hat geschrieben: Ich würde jetzt gerne ein Property wie z.B. 'crew' zusätzlich optional auch wie eine 'normale' Funktion mit Parametern aufrufen können. Ich könnte natürlich auch eine eigene Funktion dafür schreiben, hätte aber eine Codeverdoppelung.
Nö, Du kannst ja die Logik in eine *Funktion* schreiben, die Du in der Property lediglich aufrufst ;-)

Code: Alles auswählen

:class Ship:
:    def __init__(self, length, width, depth, masts, name="Nameless Ship"):
:        self.length=length
:        self.width=width
:        self.depth=depth
:        self.masts=masts
:        self.name=name
:
:    @property
:    def tonnage(self):
:        return tonnage(self.length, self.width, self.depth)
:
:def tonnage(length, width, depth):
:    return (length * width * depth) / 3
:
:--

In [21]: s = Ship(60, 20, 10, 3)

In [22]: s.tonnage
Out[22]: 4000.0

In [23]: tonnage(60, 20, 10)
Out[23]: 4000.0
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3))
assert encoding_kapiert
Benutzeravatar
/me
User
Beiträge: 3205
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Donnerstag 18. September 2014, 09:57

nezzcarth hat geschrieben:Ich würde jetzt gerne ein Property wie z.B. 'crew' zusätzlich optional auch wie eine 'normale' Funktion mit Parametern aufrufen können.
Was sollen diese Parameter denn machen? Möchtest du unabhängig von einer konkreten Instanz Werte für tonnage und masts übergeben können?

Edit: Falls das gewünscht ist, dann ist BlackJacks Vorschlag die gewünschte Lösung.

Edit2: *gnarf* Hyperion war es natürlich ...
Zuletzt geändert von /me am Donnerstag 18. September 2014, 10:17, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7472
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Donnerstag 18. September 2014, 10:03

/me hat geschrieben: Edit: Falls das gewünscht ist, dann ist BlackJacks Vorschlag die gewünschte Lösung.
Ui... das ist mal ein starkes Kompliment, danke dafür 8)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3))
assert encoding_kapiert
BlackJack

Donnerstag 18. September 2014, 10:05

@nezzcarth: Bei `crew()` ist das `divmod()` irgendwie fehl am Platz. Wenn man nur die Hälfte von dem Ergebnis braucht, sollte man auch nur diese Hälfte ausrechnen. ;-)

Code: Alles auswählen

    @property
    def crew(self):
        return self.tonnage // 10 + self.masts * 10
nezzcarth
User
Beiträge: 574
Registriert: Samstag 16. April 2011, 12:47

Donnerstag 18. September 2014, 10:11

/me hat geschrieben:
nezzcarth hat geschrieben:Ich würde jetzt gerne ein Property wie z.B. 'crew' zusätzlich optional auch wie eine 'normale' Funktion mit Parametern aufrufen können.
Was sollen diese Parameter denn machen? Möchtest du unabhängig von einer konkreten Instanz Werte für tonnage und masts übergeben können?

Edit: Falls das gewünscht ist, dann ist BlackJacks Vorschlag die gewünschte Lösung.
Ja, das war gemeint. Mir war erst nach dem abschicken klar, dass da ein Beispiel nötig wäre, das ich dann noch reineditiert hatte.

Danke für eure Antworten :) Ich hatte ja irgendwie gehofft, dass das vielleicht noch leichter geht, also, dass man das irgendwie über
die Signatur regeln könnte und tatsächlich nur eine einzige Definition hat, oder so (aber wie das gehen soll, weiß ich auch nicht ;) )

Wie sieht es hiermit aus

Code: Alles auswählen

class Ship:
    def __init__(self, length, width, depth, masts, name="Nameless Ship"):
        self.length=length
        self.width=width
        self.depth=depth
        self.masts=masts
        self.name=name
    @staticmethod
    def _tonnage(length, width, depth):
        return (length * width * depth) / 3
    @property
    def tonnage(self):
        return self._tonnage(self.length, self.width, self.depth)
Kann/sollte man das so machen, oder eher nicht?


EDIT:
@Blackjack: Oh je, natürlich; manchmal fallen mir leider nur die kompliziertesten Lösungen ein... :oops:
Benutzeravatar
/me
User
Beiträge: 3205
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Donnerstag 18. September 2014, 10:19

Eine statische Methode mit einem Unterstrich als nur für interne Benutzung zu kennzeichnen kommt mir irgendwie merkwürdig vor. Irgendwie will mir dafür spontan kein Use Case einfallen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7472
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Donnerstag 18. September 2014, 10:21

nezzcarth hat geschrieben: Kann/sollte man das so machen, oder eher nicht?
Da spricht prinzipiell nichts dagegen (außer, dass man den Underscore nach *hinten* setzen sollte. Denn vorne haben sie per Konvention die Bedeutung einer *internen* Methode, die nicht öffentlich verwendet werden sollte), aber dann hast Du natürlich einen eher hässlichen Methodenaufruf ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3))
assert encoding_kapiert
nezzcarth
User
Beiträge: 574
Registriert: Samstag 16. April 2011, 12:47

Donnerstag 18. September 2014, 12:17

Danke euch allen für die Hinweise. Den Unterstrich lasse ich bleiben; da hatte ich was falsch verstanden, bzw. interpretiert (z.B. bei entsprechenden Namen in collections.namedtuple).

Aus Interesse hatte ich mal versucht, das Verhalten, das mir vorschwebt zu basteln. Das sähe dann vielleicht so aus:

Code: Alles auswählen

class TestShip:
    def __init__(self, length, width, depth, masts, name="Nameless Ship"):
        self.length=length
        self.width=width
        self.depth=depth
        self.masts=masts
        self.name=name

    @staticmethod
    def tonnage(length, width, depth):
        return round((length*width*depth)/3)

    @staticmethod
    def capacity(tonnage):
        return round(tonnage*2/3)

    @staticmethod
    def crew(tonnage, masts):
        return tonnage//10 + masts*10
        
    def __getattr__(self, name):
        if name.startswith('get_'):
            _, method_name = name.split('_', maxsplit=1)
            method = getattr(self, method_name)
            parameters = []
            if not method:
                raise AttributeError
            for var in method.__code__.co_varnames:
                try:
                    parameters.append(self.__dict__[var])
                except KeyError:
                    t = getattr(self, 'get_'+ var)
                    parameters.append(t)
            return method(*parameters)

Code: Alles auswählen

>>> ship = TestShip(20,5,3,2)
>>> ship.print_stats()
Name: Nameless Ship
Länge: 20
Breite: 5
Tiefgang: 3
Masten: 2
Schiffsraum: 100
Frachtraum: 67
Mannschaft: 30
>>> ship.get_tonnage
100
>>> ship.tonnage(20,5,3)
100
Aber das ist vmtl. etwas sehr holperig und zusammengewürfelt... ;) Könnte man sowas auch 'in schön' machen, oder ist das an sich 'ne schlechte Idee? (u.a. wegen der komischen, konvetionalisierten Benennung).
Benutzeravatar
Hyperion
Moderator
Beiträge: 7472
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Donnerstag 18. September 2014, 12:26

Wenn schon suchst Du ``@classmethod`` - denn welchen Sinn sollte hier eine static method haben? :? Du willst ja offenbar Berechnungen *ohne* Exemplar einer Klasse durchführen, oder nicht?

Generell halte ich nichts von so viel unnötiger Magie - zumal der eigentliche Vorteil von Properties hierdurch ad absurdum geführt werden ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3))
assert encoding_kapiert
BlackJack

Donnerstag 18. September 2014, 13:12

@Hyperion: Ich schliesse mich da Hyperion (fast) an. Das sollte weder eine statische noch eine Klassenmethode sein, sondern einfach eine Funktion. Und vielleicht auch nicht `tonnage()` heissen sondern besser `volume()`, denn das ist es ja letztendlich was die Funktion berechnet. Ist ja auch nicht wirklich abhängig von einem Schiff.
nezzcarth
User
Beiträge: 574
Registriert: Samstag 16. April 2011, 12:47

Donnerstag 18. September 2014, 13:33

Ja, das ist schon richtig; wahrscheinlich sollte man auch erst einmal den " normalen" Kram richtig verstanden haben, bevor man mit solchen Spielereien beginnt. Ich war halt nur neugierig, wie man das wohl umsetzen könnte ;)

@BlackJack: Tonnage kommt nicht von mir (bzw. nur insofern, als dass ich es übersetzt habe) sondern von dem Spiel; dort wird der Wert in einer Gewichtseinheit (dem Äquivalent zu Tonnen) angegeben, nicht in einem Volumenmaß.
Antworten