Sinn von @property bei gettern / settern

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
Benutzeravatar
Natrix natrix
User
Beiträge: 16
Registriert: Mittwoch 29. März 2017, 09:16

Montag 6. November 2017, 16:27

Hallo Forum,

bzgl. des Einsatzes von property bei gettern/settern fehlt mir aktuell das Verständnis, wozu dies gut sein soll, resp. wo ich einen Vorteil bekomme.
Klassisch würde ich eine Klasse z.B. wie folgt aufbauen & nutzen:

Code: Alles auswählen

class Cat():
    
    def __init__ (self, name = "Lucy", gewicht = "8", hoehe = "21"):
        self.__name = name
        self.__gewicht = gewicht
        self.__hoehe = hoehe
        
    def run(self):
        print("{} rennt los".format(self.__name))
     
    # Getter und Setter
    def setname(self, name):
        print("ok {} hört zukünftig auf den neuen Namen {} ".format(self.__name, name))
        self.__name = name
        
    def getname(self):
        return self.__name 

    # weitere Methoden sowie getter & setter analog

def main():
    Lissy = Cat("Lissy", 9, 22)
    Lissy.run()
    
    Lissy.setname("Jaqueline")
    Lissy.run()
    
main()
Mit der Property-Funktionalität sieht es dann so aus:

Code: Alles auswählen

class Dog():
    
    def __init__ (self, name = "Whiskey", gewicht = "12", hoehe = "21"):
        self.__name = name
        self.__gewicht = gewicht
        self.__hoehe = hoehe
        
    def run(self):
        print("{} rennt los".format(self.__name))
       
    # getter & setter
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        if value.isalpha():
            self.__name = value
        else:
            print("Error")

    # weitere Methoden sowie getter & setter analog
            
def main():
    Jack = Dog("Jack")
    Jack.run()

    Jack.name = "Jonas"
    Jack.run()

main()
Der Unterschied besteht so weit ich erkennen kann nur darin, dass ich mit property nun ein Attribut "direkt" aber geschützt anzusprechen kann und im klassischen Fall über die Setter/Getter-Methoden gehen muss. Kann es sein, dass ich was übersehe? Denn wenn das alles ist... wozu der Aufwand?

Danke und Grüße
Benutzeravatar
kbr
User
Beiträge: 853
Registriert: Mittwoch 15. Oktober 2008, 09:27

Montag 6. November 2017, 16:37

Python kennt keine privacy, daher brauchst Du auch keine getter und setter. Du kannst auf die Attribute direkt zugreifen. Properties sind berechnete Attribute.
__deets__
User
Beiträge: 2841
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 6. November 2017, 17:19

Natrix natrix hat geschrieben:Hallo Forum,
Kann es sein, dass ich was übersehe? Denn wenn das alles ist... wozu der Aufwand?
Du uebersiehst das getter und setter keine gottgegebene tolle Idee sind, sondern insbesondere bei Java (wo sie so richtig populaer wurden) ein trauriger Hack, bei dem um eine Beschraenkung der Sprache herumgearbeitet wurde. Naemlich die Unfaehigkeit, Attributzugriffe mit Code-Ausfuehrung zu verknuepfen, ohne das sich dabei die Schnittstelle der Komponente aendert.

Wenn du nur einen simplen Wert brauchst, benutzt du direkten Attributzugriff. Wenn du dann ploetzlich feststellst, das beim setzen eines Wertes zB eine Validierung losgetreten oder ein Meldereiter nach Usbekistan losgeschickt werden soll, dann wandelst du das Ding in eine Property um.

Der Aufwand ist also ueberhaupt getter/setter zu schreiben. Wozu?
Benutzeravatar
pillmuncher
User
Beiträge: 1097
Registriert: Samstag 21. März 2009, 22:59
Wohnort: München

Montag 6. November 2017, 18:05

Außerdem sind Namen, die mit zwei führenden Unterstrichen beginnen, nicht dazu da, Privatheit zu forcieren, sondern um gewisse Probleme bei Mehrfachvererbung zu umgehen. In den zehn Jahren, die ich jetzt in Python progremmiere, habe ich das noch nie geraucht.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
noisefloor
User
Beiträge: 2387
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: Görgeshausen
Kontaktdaten:

Montag 6. November 2017, 21:00

Hallo,

also normalerweise schreibst du das in Python so:

[codebox=pycon file=Unbenannt.txt]>>> class Cat():
... def __init__(self, name, weight, height):
... self.name = name
... self.weight = weight
... self.height = height
...
... def run(self):
... print('{} starts to run'.format(self.name))
...
>>> a_pussy_cat = Cat('Lisa', 10, 5)
>>> a_pussy_cat.name
'Lisa'
>>> a_pussy_cat.run()
Lisa starts to run
>>> a_pussy_cat.name = 'Jaqueline'
>>> a_pussy_cat.name
'Jaqueline'
>>>[/code]

Wenn du tatsächlich eine Validierung brauchst -> dazu hat __deets__ ja schon was gesagt.

Gruß, noisefloor
Benutzeravatar
Natrix natrix
User
Beiträge: 16
Registriert: Mittwoch 29. März 2017, 09:16

Dienstag 7. November 2017, 11:57

Hallo

Danke an alle für die Antworten.

Die Getter & Setter sind wahrscheinlich in der Tat reine Gewöhnung...
Ich fasse es für mich mal so zusammen:
- sofern Validierung / Folgeaktionen notwendig über property gehen
- "klassische" Getter / Setter wie in Java vergessen

Stellt sich die Frage, wie ich Zugriffe auf Attribute, wo ich Folgeaktionen nutzen will (z.B. Zugriffe in log files schreiben) wirksam beschränke. Das mache ich bisher über "__".
Ist das schlau oder gibt es was besseres.

Vielen Dank!
NN
Benutzeravatar
snafu
User
Beiträge: 5448
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dienstag 7. November 2017, 12:01

Interne Attribute kennzeichnet man in Python durch einen einfachen Unterstrich. Der doppelte Unterstrich ist dafür gedacht, Namenskonflikte bei Vererbung zu vermeiden. In den meisten Fällen braucht man ihn nicht. IMHO hätte er mit Python 3 auch gern verschwinden können, weil er Anfänger nur verwirrt.
shcol (Repo | Doc | PyPi)
Benutzeravatar
noisefloor
User
Beiträge: 2387
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: Görgeshausen
Kontaktdaten:

Dienstag 7. November 2017, 13:15

Hallo,
Das mache ich bisher über "__". Ist das schlau oder gibt es was besseres
Wie bereits gesagt: dunder schützt gar nichts. Man muss nur anders drauf zugreifen.
Stellt sich die Frage, wie ich Zugriffe auf Attribute, wo ich Folgeaktionen nutzen will (z.B. Zugriffe in log files schreiben)

Code: Alles auswählen

@name.setter
def name(self, value):
...
ist ja auch "nur" eine Funktion. Ob du da "nur" eine Valdierung einabaust oder direkt noch Logging oder was auch immer liegt bei dir.

Gruß, noisefloor
Benutzeravatar
Natrix natrix
User
Beiträge: 16
Registriert: Mittwoch 29. März 2017, 09:16

Sonntag 19. November 2017, 18:34

Hallo noch mal,

hat ein bisschen gedauert :)
Danke noisefloor, der Hinweis, dass dunder am Ende gar nichts bringt...und sich einfach mit ._Klassenname__Variablenname aushebeln läßt, hat dieser Schreibweise bei mir den Garaus gemacht.

VG und Danke!
Tholo
User
Beiträge: 50
Registriert: Sonntag 7. Januar 2018, 20:36

Montag 5. März 2018, 20:45

Ich möchte dieses Post mal aus der Versenkung holen. Da ich es überhaupt nicht verstanden habe.
Ich hab keine Getter/Setter provoziert, da mir andere Code Sprachen als Python zZ noch spanischer vorkommen.
Pycharm hat mir die Möglichkeit der Property vorgeschlagen. Aber ich habe gerade keine Ahnung wieso.
Da meine Variablen ja aus einer Json Datei kommen. Diese wiederum ließt Python aus einer Json Datei.
Wieso sollte ich diese Variablen also über Property ansprechen? Durch die Json sind sie doch "Öffentlich"
Oder hab ich einen Denkfehler drin?

Code: Alles auswählen

from kivy.storage.jsonstore import JsonStore
from time import localtime
import os
import ujson
from collections import OrderedDict

class JsonHandling(object):
    def __init__(self, jsonpath):
        self.jpathhandle = JsonStore(jsonpath, indent=4, sort_keys=False)

    def read_json_keys(self):
        return self.jpathhandle.keys()

    def read_json(self, key):
        return self.jpathhandle.get(key)

    def change_json_value(self, key, val):
        if val == str("1"):
            valbool = bool(True)
            self.jpathhandle.store_put(key, valbool)
            self.write_json()

        elif val == str("0"):
            valbool = bool(False)
            self.jpathhandle.store_put(key, valbool)
            self.write_json()
        else:
            self.jpathhandle.store_put(key, val)
            self.write_json()

    def write_json(self):  # speichert json mit unixtime
        try:
            self.time = localtime(seconds=None)
            self.jpathhandle.put("SBGuifileversion", lastwritetime=self.time)
        except:
            print("Könnte nicht schreiben")

    def len_dict(self):
        return self.jpathhandle.count()

    def search(self):
        return self.jpathhandle.find()

    def drop(self):
        # todo del item
        pass

class SBPanelConfigJson(JsonHandling):
    def __init__(self, paneljson, jsonpath, section):
        JsonHandling.__init__(self, paneljson)
        self.paneljson = paneljson
        self.jsonpath = jsonpath
        self.paneljs = JsonStore(self.paneljson)
        self.changes = []
        self.section = section
        self.read_panel_file 
        self.dict_compare()

    def update_json_keys(self):
        self.sbconfigjson = JsonHandling(self.jsonpath)
        self.json_keys = self.sbconfigjson.read_json_keys()
        return self.json_keys

    @property  # Property von Pycharm vorgeschlagen
    def read_panel_file(self):
        self.keylist = OrderedDict()
        try:
            self.reload_keys_in_panel_file()
        except:
            print("Kann Json nicht finden, schreibe Datei")
        self.update_json_keys()
        self.configpaneljson = self.keylist.keys()
        return self.configpaneljson

    def dict_compare(self):
        d1 = self.json_keys
        d2 = self.configpaneljson
        self.result = set(d1) - set(d2)
        if len(self.result) > 0:
            self.create_panel_value()

    def reload_keys_in_panel_file(self):
        for item in self.paneljs[0:]:
            keys = dict.get(item, "key")
            self.keylist[keys] = item

    def write_panel_file(self, key, val):
        return self.change_json_value(key, val)

    def isfloat(self, value):
        try:
            res = int(eval(str(value)))
            if type(res) == int:
                return res
        except:
            return False

    def create_panel_value(self):
        count = len(self.result)
        for keylistitem in self.keylist:
            reload_file_items = self.keylist.get(keylistitem)
            self.changes.append(dict(reload_file_items))
        while count is not 0:
            createrkey = self.result.pop()
            count = count - 1
            checkbool = self.sbconfigjson.read_json(createrkey)
            self.isfloat(checkbool)
            if checkbool is bool(True):
                self.change_panel_boolitem(createrkey)
                continue
            elif checkbool is bool(False):
                self.change_panel_boolitem(createrkey)
                continue
            elif checkbool is float():
                self.change_panel_floatitem(createrkey)
            else:
                self.change_panel_item(createrkey)
                continue
        self.write_configpanel_json()

    def change_panel_floatitem(self, key):
        list.append(self.changes, dict(type="numeric", section=self.section, title=key, key=key))

    def change_panel_item(self, key):
        list.append(self.changes, dict(type="string", section=self.section, title=key, key=key))

    def change_panel_boolitem(self, key):
        list.append(self.changes, dict(type="bool", section=self.section, title=key, key=key))

    def write_configpanel_json(self):
        # todo als OrderedDict() ordnen
        print("Inhalt von changes" + str(self.changes))
        with open(self.paneljs.filename, "w") as f:
            ujson.dump(self.changes, f, indent=4)
Tante Edit mag euch noch die Json zeigen:
diese Datei wird generiert oder ergänzt
[codebox=javascript file=settingspanel.json][
{
"type":"string",
"section":"SBConfig",
"title":"polo-key",
"key":"polo-key"
},
{
"type":"bool",
"section":"SBConfig",
"title":"strategy-arbitrage",
"key":"strategy-arbitrage"
},
{
"type":"string",
"section":"SBConfig",
"title":"Tholo-publickey",
"key":"Tholo-publickey"
},
{
"type":"string",
"section":"SBConfig",
"title":"spread-trading-stake",
"key":"spread-trading-stake"
}
][/code]

Aus dieser wird gelesen

Code: Alles auswählen

{ 
    "own-smtp-mail": true,
    "limit-trading": "0.02800000",
    "SBGuifileversion": {
        "lastwritetime": 1519464664.5043662
    }
}
Sirius3
User
Beiträge: 7766
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 6. März 2018, 08:30

@Tholo: warum PyCharm da ein Property vorschlägt, wird wohl sein Geheimnis bleiben. Sobald man irgendwelchen komplizierten Sachen macht, z.B. Dateien liest, sollte man immer eine Methode schreiben.

Deine Namen sind etwas seltsam: `read_json_keys` liest ja nichts, sondern holt sich nur aus dem vorhandenen Store die Schlüssel. Nenne das so, wie die Funktion die Du aufrufst: `keys`. Ebenso `read_json` -> `get`. `change_json_value` ist üblicherweise `set`. `len_dict` -> `len`, `search` -> `find`, obwohl, was soll da gefunden werden, wenn weder search noch find ein Argument hat. `drop` -> `remove`.

Es ist unsinnig, eine Konstante von selben Typ in diesen Typ zu verwandlen: "0" ist schon ein String, ebenso True schon ein `bool`. Warum sollte jede "1" in True umgewandelt werden? Wenn man True meint, sollte man der Funktion auch True übergeben.
`self.time` wird bei write_json benutzt und sonst nicht. Das sollte kein Attribut sein. Außerdem sollten alle Attribute schon in `__init__` angelegt werden.

Eine Methode sollte am besten entweder den Internen Zustand ändern, oder etwas zurückgeben, nicht beides: `update_json_keys` sollte daher keinen Rückgabewert haben. Die Vermischung von JsonHandling und JsonStore in SBPanelConfigJson ist verwirrend und mit verwirrend ist beim Programmieren immer gemeint, dass es einen Designfehler hat, der korrigiert werden sollte: `jpathhandle` ist ein JsonStore, und gleichzeitig ist `paneljs` auch ein JsonStore. `sbconfigjson` ist dan nwieder ein JsonHandling, das wiederum einen JsonStore enthält. Da sind also mindestens zwei JsonStores zu viel.
Nakte Excepts sollte man nicht benutzen, weil die wirklich jeden Fehler (auch manche Programmierfehler) abfangen. Benutze Konkrete Exceptions.
`eval` nicht benutzen. hier willst Du `float` verwenden. und wenn Du res nach `int` verwandelst, ist der Typ auf jeden Fall `int`. `isfloat` erwarte ich als Rückgabewert ein bool und nicht ein int. Zahlen niemals mit `is` vergleichen. Statt der while-Schleife möchtest Du auch eine for-Schleife benutzen. Der Aufruf von `isfloat` ist unsinnig, weil mit dem Rückgabewert nichts gemacht wird. Die continue sind alle überflüssig.
Tholo
User
Beiträge: 50
Registriert: Sonntag 7. Januar 2018, 20:36

Dienstag 6. März 2018, 20:16

Sirius3 hat geschrieben:@Tholo: warum PyCharm da ein Property vorschlägt, wird wohl sein Geheimnis bleiben. Sobald man irgendwelchen komplizierten Sachen macht, z.B. Dateien liest, sollte man immer eine Methode schreiben.
Das bedeutet meine erste Herangehensweise war soweit korrekt und ich kann den Pycharm Vorschlag ignorieren?
Sirius3 hat geschrieben: `read_json_keys` liest ja nichts, sondern holt sich nur aus dem vorhandenen Store die Schlüssel. Nenne das so, wie die Funktion die Du aufrufst: `keys`. Ebenso `read_json` -> `get`. `change_json_value` ist üblicherweise `set`. `len_dict` -> `len`, `search` -> `find`, obwohl, was soll da gefunden werden, wenn weder search noch find ein Argument hat. `drop` -> `remove`.
Es ist unsinnig, eine Konstante von selben Typ in diesen Typ zu verwandlen: "0" ist schon ein String, ebenso True schon ein `bool`. Warum sollte jede "1" in True umgewandelt werden?
Sirius3 hat geschrieben:`self.time` wird bei write_json benutzt und sonst nicht. Das sollte kein Attribut sein. Außerdem sollten alle Attribute schon in `__init__` angelegt werden.
set_json_value (vormals change_jsnon_value) bekommt den inhalt vom Configparser der "app.ini" Der KivyConfigparser speichert als 0 & 1 in der ini Datei. Der PHP Bot welcher dieses JSon braucht und nutzt benötigt die Angaben in bool mit true & false. Daher kann ich sicher sein das alle 0&1 Strings eigentlich Bool sein sollen. Was wäre die Alternative? Den KivyConfigparser dazu zu bringen Bool zu schreiben?
Sirius3 hat geschrieben: Eine Methode sollte am besten entweder den Internen Zustand ändern, oder etwas zurückgeben, nicht beides: `update_json_keys` sollte daher keinen Rückgabewert haben. Die Vermischung von JsonHandling und JsonStore in SBPanelConfigJson ist verwirrend und mit verwirrend ist beim Programmieren immer gemeint, dass es einen Designfehler hat, der korrigiert werden sollte: `jpathhandle` ist ein JsonStore, und gleichzeitig ist `paneljs` auch ein JsonStore. `sbconfigjson` ist dan nwieder ein JsonHandling, das wiederum einen JsonStore enthält. Da sind also mindestens zwei JsonStores zu viel.
Meine Idee zur Zeit ist folgende. Die Klasse SBPAnelConfig bekommt ja paneljson (Die Json Datei welche gelesen,geschrieben und überarbeitet wird) übergeben. Die Informationen kommen aus einer anderen Json Datei ( die jsonpath, das zweite Json Beispiel im oberen Post).Wenn ich dich recht verstehe, müsste ich also eher die Superklasse JsonHandling mit der jsonpath aufrufen oder?
Wenn ich SBConfigpanel(paneljson) aufrufe, muss ich dann JsonHandling(jsonpath) in der Funktion update_json_keys machen...
Aber wie vermittel ich aus der Vererbung in die Superklasse mit anderer Attribut (Jsondatei). Könnest du mir ein Begriff dazu nennen?
Das Thema hat mir am Wochenende scho Stunden Knoten im Kopf gemacht.
Sirius3 hat geschrieben: Nakte Excepts sollte man nicht benutzen, weil die wirklich jeden Fehler (auch manche Programmierfehler) abfangen. Benutze Konkrete Exceptions.
`eval` nicht benutzen. hier willst Du `float` verwenden. und wenn Du res nach `int` verwandelst, ist der Typ auf jeden Fall `int`. `isfloat` erwarte ich als Rückgabewert ein bool und nicht ein int. Zahlen niemals mit `is` vergleichen. Statt der while-Schleife möchtest Du auch eine for-Schleife benutzen. Der Aufruf von `isfloat` ist unsinnig, weil mit dem Rückgabewert nichts gemacht wird. Die continue sind alle überflüssig.
Ohh man..Da kopiert man mal code von google und fällt so auf den Hintern...
Die isfloat Geschichte ist noch nicht ausgereift. Ich bin da gerade noch dran eine Möglichkeit zu finden wie ich Bool, Float und String aus der Json Datei (key: "value ist meinßt string") zuordnen kann.

Code: Alles auswählen

     def create_panel_value(self):
        count = len(self.result)
        for keylistitem in self.keylist:
            reload_file_items = self.keylist.get(keylistitem)
            self.changes.append(dict(reload_file_items))
        while count is not 0:
            createrkey = self.result.pop()
            count = count - 1
Jetzt erwischt du mich voll! Wieso sollte dort eine For Schleife besser sein? Ich arbeite gerade von Oben nach Unten. Deute ich dich richtig, das eine bearbeitung von Unten nach Oben eher besser wäre?

Code: Alles auswählen

        for createrkey in self.result.pop():
            #createrkey = self.result.pop() 
            checkbool = self.sbconfigjson.get_json_value(createrkey)
Ohh jetzt Pfeift der Kopf. Sorry, ich glaub ich verstehe was du meinst aber komme gerade nicht auf die Lösung. Das muss ich mir in Ruhe anschauen.

Ich danke dir für deine Einschätzung! Mir raucht gehörig der Kopf aber ich denke ich habe weitgehend verstanden was du meinst.
Ausnahme die Geschichte mit den 2 Datei innerhalb der Vererbung und die While zur For Schleife. Aber ich denke das müssten wir in einem andern Post diskutieren, um nicht vom Thema abzukommen. Da ich die @property nun wieder gelöscht habe

Danke!
Antworten