Aufbau von Klassen

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.
BlackJack

@zikzak: Also diese ganzen ``if``/``else`` sind Dir nicht nervig vorgekommen beim Schreiben? *Dann* kannst Du statt `None` auch leere Zeichenketten als Defaultwert setzen wenn Du das *so* machst. m)

Zeichenketten sollte man nicht wiederholt mit ``+`` zusammensetzen. Das ist ineffizient weil jedes mal eine neue Zeichenkette erstellt wird in die die am ``+`` beteiligten kopiert werden. Effizienter und idiomatischer ist es die Teilzeichenketten in einer Liste zu sammeln und dann mit der `join()`-Methode zusammenzusetzen. Wobei so etwas wie ``"".join(["Tel: ", self.tel, '\n'])`` allerdings wieder komisch aussieht, denn *dafür* gibt es die `format()`-Methode:
``'Tel: {}\n'.format(self.tel)``.

Und bei Adresse: Eine Adresse *ist* ein Adresszusatz? Eine Adresse *ist* Standortdaten? Du verwendest Vererbung schon wieder falsch. Eine Adresse *hat* einen Adresszusatz und Standortdaten, das ist Komposition und nicht Vererbung.

`isinstance()` ist ein „code smell”. Das ist ein Warnzeichen dass man etwas macht was nicht OOP ist und was in Python zudem das „duck typing” unterläuft. Benenne `daten` in `adressen` um und versuch nicht auf Teufel komm raus alles zu erlauben beziehungsweise einen Sinn zu geben. Dort muss man ein iterierbares Objekt mit Adressen übergeben können. Wenn jemand eine *einzelne* Adresse übergibt, dann ist das halt ein Fehler. Sieht als API sowieso komisch aus wenn man ein Adressen-Objekt aus einer einzelnen Adresse erstellen könnte. Wenn man das möchte braucht man ja nur ``[]`` um das Objekt zu setzen und schon hat man eine Liste mit einer Adresse. Die `Adressen.__init__()` wird dann zu einem Zweizeiler:

Code: Alles auswählen

    def __init__(self, adressen=()):
        self.adressen = list(adressen)
Die `__iter__()` geht auch kürzer: ``return iter(self.adressen)``

In der `str` ist das ``if`` unnötig. Wenn `self.adressen` leer ist, dann wird die Schleife ja auch ohne das ``if`` nicht durchlaufen.

`d` ist eine Adresse. Statt also ``Adresse.__str__(d)`` aufzurufen geht natürlich ``str(d)`` — genau dafür hat man die `__str__()`-Methode doch implementiert. `d` und `s` sind ausserhalb von Argumentlisten von ``lambda``-Funktionen, „list comprehensions”, und Generatorausdrücken keine akzeptablen Namen.

Und der `Kontakt` erbt auch schon wieder oder immer noch von `Adressen` statt ein Attribut davon zu haben.

Die Docstrings sind an der falschen Stelle. Die gehören unter die ``class``-Zeilen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

zikzak hat geschrieben:Ich rufe die interne Funktion __str__ der Klasse Adresse in einer Schleife auf, um die Ausgabe zu realisieren.
Meines erachtens nicht sauber, aber wie geht es sonst ?
Das ist die saubere Lösung.

Gleich noch ein paar Anmerkungen: Wenn du testen möchtest, ob ein Namen den Wert ``None`` hat, dann solltest du das mittels ``is None`` machen (None ist ein Sonderfall, dass es nur ein einziges Objekt ``None`` gibt):

Code: Alles auswählen

if tür is None:
Hinzu kommt das Problem, dass deine Bisherigen vergleiche unnötig viel ausschießen. So wird auch ein leerer String zu False ausgewertet. Oder die 0. Oder wer weiß was für ein Objekt, über das du keine Kontrolle hast.

Bei den ganzen Zuweisungen sollte dir auch aufgefallen sein, dass du quasi den Code kopiert hast. Machst du so etwas, dann musst du diesen zusammenfassen:

Code: Alles auswählen

self.tür = from_parameter(tür, "")
self.stiege = from_parameter("stiege", "")
...
Strings solltest du immer mittels ``join`` zusammensetzen, niemals mit ``+=``. Da hast du so einige Stellen, welche du ausbessern musst. Wenn du Strings formatieren möchtest, dann solltest du das allerdings nicht mit ``join`` tun, sondern über die gegebenen String-Formatting-Methoden:

Code: Alles auswählen

"Tür: {}\n".format(self.tür)
Schau dir dazu die Dokumentation an, damit kannst du noch viel mehr anstellen. Beim Erzeugen der Strings musst du auch wieder viel mehr zusammenfassen, da hast du ganz viel identischen Code.

Docstrings ("""...""") gehören direkt an den Anfang des Funktsions-/Klassenkörpers. Und schon gar nicht stellen Strings Kommentare über mehrere Zeilen dar.

zikzak hat geschrieben:

Code: Alles auswählen

class Adresse(Adresszusatz, Standortdaten):
Ist eine Adresse ein Adresszusatz? Nein. Ist eine Adresse (mehrere) Standardortdaten? Nein. Solltest du dann erben? Nein.
zikzak hat geschrieben:

Code: Alles auswählen

# Initialisierung der Objektattribute
Unnütze Kommentare kannst du gleich weglassen. Du brauchst sie nur, wenn sie einen Mehrwert bieten und nicht das offensichtliche beschreiben.
zikzak hat geschrieben:

Code: Alles auswählen

        # __str__ von den Oberklassen ausführen
        zusatz = Adresszusatz.__str__(self)
        standort = Standortdaten.__str__(self)
Das macht man so nicht, sondern verwendet einfach die str-Funktion:

Code: Alles auswählen

zusatz = str(Adresszusatz)
Python kümmert sich dann um den richtigen Aufruf.
zikzak hat geschrieben:

Code: Alles auswählen

            # falls jemand nicht eine [ Adresse ] sondern gleich ein
            # Objekt des Typs Adressen übergibt, soll der Code auch funktionieren
So etwas macht man nicht. Wenn man so etwas zulässt, dann löst man das bei der ERSTEN Zuweisung. Dann hat man danach im Programm nicht mehr überall Sonderfälle stehen. So wie diesen hier, der sonst an tausend Stellen auftritt:
zikzak hat geschrieben:

Code: Alles auswählen

    # obrige Schleife klappt nur, wenn Adressen auch iterierbar sind !
zikzak hat geschrieben:

Code: Alles auswählen

class Kontakt(Adressen):
Und noch einmal, was ich dir gestern schon erzählt habe: Ist ein Kontakt eine Adresse? Nein. Erbst du dann? Nein. Hat ein Kontakt eine (oder mehrere) Adressen? Ja. Also eine Komposition.
Das Leben ist wie ein Tennisball.
zikzak
User
Beiträge: 21
Registriert: Sonntag 22. September 2013, 07:28
Wohnort: Sipbachzell, Österreich

Komposition - ein neuer Versuch:
Das mit den Formatierungen habe ich noch nicht drauf, dass ist aber nur Syntaxsache, das mache ich noch.

Code: Alles auswählen

class Standortdaten(object):
    
    def __init__(self, daten=None):
        self.daten = {} if daten is None else daten
            
    def __str__(self):
        return dict2string(self.daten)

    def __iter__(self):
        return iter(self.daten)


class Adresse():
    
    def __init__(self ,bezeichnung, strasse = "", plz = "", ort = "", land = "",
                 zusatzinfos=None):
                 
        self.bezeichnung=bezeichnung
        self.strasse = strasse
        self.plz = plz
        self.ort = ort
        self.land = land
        self.standortdaten = Standortdaten (zusatzinfos)
            
    def __str__(self):
        
        s = ""
        s += "".join([self.strasse,'\n',self.land,"-",self.plz,'\n', self.bezeichnung
                      ,'\n', "-----------------------------", '\n'])
        s += str(self.standortdaten)
        return s



class Kontakt():
    def __init__(self,vorname, nachname="",anrede="", titel="", mobil="", emails=None,  adressen=None):
        
        self.vorname=vorname
        self.nachname = nachname
        self.anrede = anrede
        self.titel = titel
        self.mobil = mobil
        self.emails = [] if emails is None else emails
        self.adressen = {} if adressen is None else adressen
        
    def set_standart_email(email):
        anzahl = self.emails.count(email)
        if anzahl > 0:
            for item in anzahl:
                self.emails.remove(email)
        self.emails.append(email)
            
    def __str__(self):
        s= ""
        s += "".join(['\n', self.anrede, " ", self.titel, " ", '\n', self.vorname,
                      " ", self.nachname,'\n', "email: ", self.emails[0],
                      '\n', "mobil: ", self.mobil, '\n'])
        
        for wohnsitz,daten in self.adressen.items():
            s += "".join(['\n',"************************************",
                          '\n', wohnsitz, '\n', str(daten)])
        return s

        

    
def dict2string (auszugeben):
    text = ""
    for key,item in auszugeben.items():
        text += "".join([key, ":  ", str(item), '\n'])
    return text
    

anmerkungen = {"tel":"4454446666", "fax":"12234556666", "tür":"2B", "stiege":"4",
               "zusatzinfos":"Eintritt nur mit Voranmeldung", "aufgang":"1"}

zuhause = Adresse("Wallenstein", strasse = "Schillerstrasse 1",plz = "12345",ort = "Objekthausen",
                  land="CH", zusatzinfos = anmerkungen)

ferienhaus = Adresse ("Seehütte", strasse = "Bachweg 1", plz = "55555",
                      ort = "Paradies", land="CN", zusatzinfos = {"Zufahrt":"Schotterweg"} )                     

seppo = Kontakt ("Josef", "Müller", "Herr", "", "5454543322", ["nono@nosay.com", "secon@yuhu.com"],
                 {"Haupt-Wohnsitz":zuhause, "Seehütte":ferienhaus} )
                 
print (seppo)
        



So besser ?
BlackJack

@zikzak: Die Standortdaten für die Adresse würde ich als `Standortdaten`-Objekt übergeben und nicht als Liste. Und das Argument der `__init__` dann auch `standortdaten` und nicht `zusatzinfos` nennen, was sowieso ein wenig irreführend war.

`set_standart_email()` hast Du offensichtlich nicht ausprobiert, das ist voll von Fehlern. Angefangen damit das kein `self` übergeben wird und ganz offensichtlich, dass Du versuchst über eine Zahl zu iterieren, was in Python nicht geht. Ausserdem heisst es Standar*d*.

Bei den `__str__()`-Methoden hast Du es geschafft das `join()` genau da einzusetzen wo es am wenigsten bringt und am meisten unnötige Schreibarbeit produziert. ;-)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@zikzak: Du hast jetzt Standortdaten soweit verallgemeinert, dass es beliebige Daten aufnehmen kann. Da stellt sich für mich die Frage, ob dann überhaupt noch eine eigene Klasse nötig ist, oder Du gleich für »zusatzinfos« ein »dict« nimmst. Andernfalls gibt das, was BlackJack dazu gesagt hat: Übergib nicht die Parameter zum Erzeugen einer Klasse an die __init__ einer anderen Klasse, sondern gleich ein Exemplar.

Hier mal ein Beispiel für eine »__str__«-Methode:

Code: Alles auswählen

    def __str__(self):
        return "\n{anrede} {titel}\n{vorname} {nachname}\nemail: {email}\nmobil: {mobil}\n{adressen}".format(
            anrede=self.anrede, titel=self.titel, vorname=self.vorname,
            nachname=self.nachname, email=self.emails[0], mobil=self.mobil,
            adressen=''.join(
                '\n************************************\n{0}\n{1}'.format(wohnsitz,daten)
                for wohnsitz,daten in self.adressen.items()))
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

zikzak hat geschrieben:Da der Kontakt ja mehrere Adressen haben kann, reicht es nach meinem Verständnis nicht,
einfach von der Klasse Adresse zu erben. Wie macht man das jetzt ?
Einbau einer Liste von Adresse als Items ?
Nicht nur das. Du machst den selben Fehler wie Microsoft mit Windows Phone 7. Ein Kontakt kann durchaus mehr als eine Mobilnummer haben. ;)

Am besten du guckst dir mal die vCard-Spezifikation an. Die gibt einem eine recht sinnvolle Gliederung vor. Dein Ziel sollte sein, dass deine Kontaktklasse eine "vCard" möglichst vollständig darstellen kann.
zikzak
User
Beiträge: 21
Registriert: Sonntag 22. September 2013, 07:28
Wohnort: Sipbachzell, Österreich

Danke für die vielen Korrekturen !

1. Die Email-Funktion habe ich tatsächlich nicht mehr getestet - heute alles neu gemacht.
2. dict eingebaut und somit eine weitere Klasse beseitigt - Code wird immer kürzer !
3. Formatstrings eingeführt, außer in der Funktion dict2string .
4. vcard muss ich mir ansehen , wird etwas dauern. Bitte die verschachtelte dict "mobiles" nicht Ernst nehmen,
das wird noch besser gemacht.

Code: Alles auswählen

# -*- coding: UTF-8 -*-

def dict2string (auszugeben):
    """Hilfsfunktion zur Ausgabe eines dics {STRING:STRING} .

    """
    text = ""
    for key,item in auszugeben.items():
        text += "".join([key, ":  ", str(item), '\n'])
    return text

class Adresse():

    
    def __init__(self ,bezeichnung, strasse = "", plz = "", ort = "", land = "",
                 standortdaten=None):
                 
        self.bezeichnung=bezeichnung
        self.strasse = strasse
        self.plz = plz
        self.ort = ort
        self.land = land
        self.standortdaten = {} if standortdaten is None else standortdaten
        
    def __str__(self):
        return "{strasse}\n{land}-{plz} {ort}\n{bezeichnung}\n  \
                -----------------------------\n{standortdaten}".format(
                    strasse=self.strasse, land=self.land, plz=self.plz, \
                    ort=self.ort, bezeichnung=self.bezeichnung, \
                    standortdaten=dict2string(self.standortdaten))
    

class Kontakt():
    def __init__(self,vorname, nachname="",anrede="", titel="", mobiles=None, emails=None, \
                 adressen=None):
        
        self.vorname=vorname
        self.nachname = nachname
        self.anrede = anrede
        self.titel = titel
        self.mobiles = {"TEL":{"CELL":""}} if mobiles is None else mobiles
        self.emails = [] if emails is None else emails
        self.adressen = {} if adressen is None else adressen
        
    def add_email(self, email, standard=False):
            while True:
                try:
                    self.emails.remove(email) # alle Einträge email löschen
                except ValueError:
                    if standard:
                        self.emails.append(email) # hinten anhängen, die Letzte ist Standard-Email
                    else:
                        hauptemail = self.emails.pop()
                        self.emails.append(email)
                        self.emails.append(hauptemail)
                    break
                   
    def __str__(self):
        return "\n{anrede} {titel}\n{vorname} {nachname}\nemail:{email}\nmobil:{mobiles}\n{adressen}"  \
            .format(anrede=self.anrede, titel=self.titel, vorname=self.vorname,
            nachname=self.nachname, email=self.emails[-1], mobiles=self.mobiles["TEL"]["CELL"],adressen=''.join(
                '\n************************************\n{0}\n{1}'.format(wohnsitz,daten)
                for wohnsitz,daten in self.adressen.items()))



anmerkungen = {"tel":"4454446666", "fax":"12234556666", "tür":"2B", "stiege":"4",
               "zusatzinfos":"Eintritt nur mit Voranmeldung", "aufgang":"1"}

zuhause = Adresse("Wallenstein", strasse="Schillerstrasse 1",plz="12345",ort="Objekthausen",
                  land="CH", standortdaten=anmerkungen)

ferienhaus = Adresse ("Seehütte", strasse="Bachweg 1", plz="55555",
                      ort="Paradies",land="CN", standortdaten={"Zufahrt":"Schotterweg"} )                     

seppo = Kontakt ("Josef", "Müller", "Herr", "", {"TEL":{"CELL":"05454543322"} },
                 ["nono@nosay.com", "schon_da_aber_jetzt_aktuell@yuhu.com",
                  "secon@yuhu.com", "alte_hauptemail@pff.com"],
                 {"Haupt-Wohnsitz":zuhause, "Seehütte":ferienhaus} )
                 

seppo.add_email('uuuuuuuuuuuu@yuhu.com',False)
print (seppo)
print (seppo.emails)
seppo.add_email("schon_da_aber_jetzt_aktuell@yuhu.com", True)
print (seppo)
print (seppo.emails)
seppo.add_email("ganz_NEUE_aktuelle_email@yuhu.com", True)
print (seppo)
print (seppo.emails)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Benutzt Du eigentlich Python 2 oder 3? Deine Benutzung von ``print`` beißt sich ein wenig mit der Encoding-Angabe... zudem sollten zwischen ``print`` und den Klammern dann keine Leerzeichen stehen.

Nur auf die Schnelle: Deine Funktion ``dict2sting`` kann man viel eleganter schreiben:

Code: Alles auswählen

def dict2sting(d):
    return "\n".join("{}:  {}".format(k, v) for k, v in d.items())
Ansonsten habe ich mir den Code nicht wirklich angesehen...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

zikzak hat geschrieben:

Code: Alles auswählen

def dict2string (auszugeben):
    """Hilfsfunktion zur Ausgabe eines dics {STRING:STRING} .

    """
    text = ""
    for key,item in auszugeben.items():
        text += "".join([key, ":  ", str(item), '\n'])
    return text
Du hast `.join()` noch nicht so richtig verstanden. `.join()` soll gerade Dinge wie `text += "nächster Text" ersetzen. Und da, wo du es einsetzt, sollte man lieber String Formatting benutzen.

Hier mal eine mögliche Umsetzung unter Python 2.x:

Code: Alles auswählen

from itertools import starmap

def dict2string(dictionary):
    return '\n'.join(
        starmap('{}: {}'.format, dictionary.iteritems())
    )
Zuletzt geändert von snafu am Sonntag 13. Oktober 2013, 17:20, insgesamt 1-mal geändert.
zikzak
User
Beiträge: 21
Registriert: Sonntag 22. September 2013, 07:28
Wohnort: Sipbachzell, Österreich

Gleich mal ungesetzt !

Code: Alles auswählen

def dict2string (auszugeben):
    """Hilfsfunktion zur Ausgabe eines dics {STRING:STRING} .

    """
    return "".join("{}:  {}\n".format(key,item) for key,item in auszugeben.items() )
Antworten