Das geht doch eleganter... nur wie?

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,

lässt sich folgendes nicht eleganter lösen?

Code: Alles auswählen

if self.style == 1:
    return date.weekday() in self.days
else:
    return date.day in self.days
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Gremlin
User
Beiträge: 166
Registriert: Freitag 28. Mai 2010, 23:49

Du könntest das else weglassen :wink:
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Vielleicht noch ein Vorschlag...? ;-)
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
ws
User
Beiträge: 65
Registriert: Freitag 16. Juni 2006, 13:19

Mehrere returns finde ich meistens etwas unschön, da sie das Verständnis des Kontrollflusses m.E. erschweren.

Vielleicht so:

Code: Alles auswählen

ret = date.weekday() in self.days if self.style == 1 else date.day in self.days

return ret
Wobei ich nicht weiss, was Dein date ist.

Gruss

Wolfgang
lunar

@ws: Sein "date" ist wohl ein "datetime.date" aus der Standardbibliothek. Man kann die Wahl des Attributes noch von der eigentlichen Abfrage trennen, dann ist das Ganze noch etwas besser zu lesen, weil der bedingte Ausdruck nicht mehr so komplex ist:

Code: Alles auswählen

day = date.weekday() if self.style == 1 else date.day
return day in self.days
Die ursprüngliche Lösung ist in dieser Hinsicht allerdings nur marginal umständlicher, und meines Erachtens gut genug.

Wirklich störend finde ich da eher die nichtssagende, "magische" 1. Magische Zahlen sollte man vermeiden, und zumindest durch Konstanten (hier vielleicht "STYLE_USE_WEEKDAYS" oder so) ersetzen, am besten aber gänzlich vermeiden.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Mein Beispiel verwende ich in einer Klasse Repeat(), die Terminwiederholungen organisiert. Mit dem style-Parameter übergebe ich die Info, ob es sich um eine tägliche (0), wöchentliche (1), monatliche (2) oder jährliche (3) Wiederholung handelt. Je nachdem greifen eben verschiedene Methoden.

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

@mutetella: IMHO nicht so gut. Ich würde das vielleicht über zwei verschiedene Klassen lösen, eine für fixe Termine (also "jährlich") und eine für in regelmässigen Abständen wiederkehrende Termine (täglich/wöchentlich/alle x Tage).
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Ich habe jetzt mal meine Repeat() ins pastebin gesetzt:
http://paste.pocoo.org/show/274365/
Begrenzungen nach vorne oder hinten sind noch nicht enthalten. Noch ein paar Beispiele zur Erklärung der Parameter:

parent_date wiederholt sich alle 2 Tage:
Repeat(parent_date=datetime.date.today(), style=0, interval=2)
parent_date wiederholt sich jede Woche am Mo und Mi:
Repeat(parent_date=datetime.date.today(), style=1, interval=1, days=[0,2])
parent_date wiederholt sich alle 3 Monate am 1. Donnerstag des Monats:
Repeat(parent_date=datetime.date.today(), style=2, interval=3, days=[3], weeks=[1])
parent_date wiederholt sich jedes Jahr am 15. und 20. Februar:
Repeat(parent_date=datetime.date.today(), style=3, interval=1, days=[15,20], months=[2])

Abfrage jeweils mit Repeat.request(datetime.date-object))

Ich habe auch schon darüber nachgedacht, die unterschiedlichen Wiederholungen auf verschiedene Klassen aufzuteilen. Mir fehlt allerdings einfach eine Vorstellung davon.
Welchen Nutzen hätte ich davon?
Was ist an jährlichen Wiederholungen "fixer" als an täglichen?
Eine Aufteilung wäre doch nur ein Auslagern von Methoden und damit eine Entschlackung, oder? Wenn ja, steht das dafür?

Bin ratlos. Oder passt das so?
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Der Nutzen von einer Aufteilung wäre, dass jedes Objekt nur noch die Daten hätte, die es auch wirklich braucht und es wäre übersichtlicher. Ausserdem leichter erweiterbar, denn dann bräuchte man eine weitere Wiederholungsart -- beispielsweise auf Mondphasen bezogen, nicht zusätzlich mit einer magischen Style-Konstante in die `_make_style` reinfummeln, sondern könnte dafür einfach eine neue Klasse schreiben. Mit Klassen kann man auch einfacher Implementierungen austauschen. Die Style-Konstanten fallen weg und es gibt einen ganzen Haufen ``if``\s weniger.

Ich habe mal tägliche und wöchentliche Wiederholung skizziert (völlig ungetestet):

Code: Alles auswählen

from datetime import timedelta as Timedelta
from itertools import ifilter


def iter_dates(start, end, step=Timedelta(days=1)):
    while start <= end:
        yield start
        start += step


def week_number(date):
    return int(date.strftime('%W'))


class Recurrence(object):
    def __init__(self, date, exceptions):
        self.date = date
        self.execptions = set(exceptions)
    
    def is_active(self, date):
        return date not in self.exceptions
    
    def iter_active_days(self, start, end):
        return ifilter(self.is_active, iter_dates(start, end))


class DailyRecurrence(Recurrence):
    def __init__(self, date, exceptions, every_n_days):
        Recurrence.__init__(self, date, exceptions)
        self.every_n_days = every_n_days
    
    def is_active(self, date):
        return (Recurrence.is_active(self, date)
                and (date - self.date).days % self.every_n_days == 0)


class WeeklyRecurrence(Recurrence):
    def __init__(self, date, exceptions, every_n_weeks, on_days):
        Recurrence.__init__(self, date, exceptions)
        self.every_n_weeks = every_n_weeks
        self.on_days = on_days
    
    def is_active(self, date):
        distance = week_number(date) - week_number(self.date)
        return (Recurrence.is_active(self, date)
                and distance % self.every_n_weeks == 0
                and date.weekday() in self.on_days)
In der Basisklasse `Recurrence` kann man auch einen generellen Bereich von-bis unterbringen und andere Sachen die für alle Arten von Wiederholungen Sinn machen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

So, hab' die Repeat() jetzt mal aufgedröselt:
http://paste.pocoo.org/show/275064/

Warum nicht gleich so? Immer wieder häng' ich in meinen schwäbischen Wurzeln (des braucht doch net 'ne extra Klasse, kann ma' sich doch spar'n!) fest. Aber so ist das einfach toll. Aufgeräumt, überschaubar und übermorgen immer noch zu verstehen.

Ein paar Fragen hätte ich noch:
  • Warum schreibst Du Funktionen oben und nicht unten ins Modul? Tieferer Grund oder nur eine Eigenart von Dir?
    Warum nimmst Du für 'exceptions' ein set?
    Welche Auswirkung hätte es, wenn ich Repeat() nicht von object ableiten würde? Falls das jetzt viel zu weit führt, dann lassen wir das halt. Ein wenig habe ich mich schon mit newStyle-Klassen beschäftigt, kann aber noch nicht feststellen, was mir das konkret für Vorteile bringt.
Wie auch immer, allein darüber, dass Du mir den Modulo-Operator gezeigt hast, könnt' ich mich freuen wie ein Schneekönig!! :-) Das ist wieder eines von den Dingen, über die man viel zu schnell hinwegliest, weil man glaubt, es ja doch nie zu brauchen... :roll: Dadurch spar' ich mir diese doofe 'float == int(float)'-Abfrage.

Und zuletzt: Gibt es an meinen Klassen noch was auszusetzen?

Tausend Dank schon mal!
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Die Reihenfolge von Funktionen und Klassen schreibe ich in der Regel so, dass möglichst nichts verwendet wird, was nicht weiter oben schon definiert wurde. Es ist also nicht strikt nach Funktionen und Klassen getrennt -- wenn ich eine Funktion schreibe die eine Klasse "aufruft", dann schreibe ich die Klasse wenn's geht irgendwo davor. Das ist Geschmackssache. Ich baue sozusagen gerne auf kleineren Bausteinen auf. Andere Leute machen's bewusst genau umgekehrt und ordnen die Bausteine höherer Ebene weiter vorne an und gehen dann bis zu den Hilfsfunktionen runter. Und wieder andere denken gar nicht weiter drüber nach. :-)

Bei einem `set()` für die Ausnahmen geht der ``in``-Operator schneller als bei einer Liste.

Properties kann man nur bei "Newstyle"-Klassen verwenden, darum erbe ich grundsätzlich von `object`. Man weiss ja nie wann man mal ein Property einsetzen möchte.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

BlackJack hat geschrieben:Bei einem `set()` für die Ausnahmen geht der ``in``-Operator schneller als bei einer Liste.
Das find' ich seeehr GUT!
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Ich würde \ vermeiden. Also so: http://paste.pocoo.org/compare/275379/275064/

Edit: und die ganzen `and`\s habe ich da hingepackt, wo sie PEP8 gerne sieht (und ich übrigens auch).
BlackJack

@derdon: Tja, so hat wohl am Ende doch jeder etwas an PEP8 auszusetzen: Ich fange fortgesetzte Zeilen immer mit dem Operator an, weil ich dann gleich am Zeilenanfang sehe, dass die eingerückte Zeile ganz offensichtlich ein fortgesetzter Mehrzeiler ist, ohne dass ich in der Zeile davor an's Ende schauen muss ob da ein ``:`` steht und es somit kein Mehrzeiler sondern eine "suite" ist.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hmm... das ist ja jetzt wieder so eine Sache. Bisher hatte ich Operatoren auch an's Ende der Zeile gesetzt, BlackJack's Schreibweise hat mir jedoch auf Anhieb besser gefallen.

Ist jetzt natürlich für ein Greenhorn wie mich schwierig, 'ne Entscheidung zu treffen. Jedenfalls tendiere ich aus denselben Gründen wie BlackJack dazu, Operatoren an den Zeilenanfang zu setzen. Auch gefällt mir das Beispiel aus dem offiziellen Style Guide nicht wirklich:

Code: Alles auswählen

class Rectangle(Blob):

    def __init__(self, width, height,
                 color='black', emphasis=None, highlight=0):
        if width == 0 and height == 0 and \
           color == 'red' and emphasis == 'strong' or \
           highlight > 100:
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and (color == 'red' or
                                           emphasis is None):
            raise ValueError("I don't think so -- values are %s, %s" %
                             (width, height))
        Blob.__init__(self, width, height, 
            color, emphasis, highlight)
Übersichtlicher finde ich:

Code: Alles auswählen

class Rectangle(Blob):
    def __init__(self, width, height,
        color='black', emphasis=None, highlight=0):
        if width == 0 and height == 0 \
            and color == 'red' and emphasis == 'strong' \
            or highlight > 100:
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and color == 'red' \
            or emphasis is None:
            raise ValueError("I don't think so -- values are %s, %s" %
                (width, height))
        Blob.__init__(self, width, height,
            color, emphasis, highlight)
Vielleicht gibt's ja noch weitere Ansichten?
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

BlackJack hat geschrieben:@mutetella: IMHO nicht so gut. Ich würde das vielleicht über zwei verschiedene Klassen lösen, eine für fixe Termine (also "jährlich") und eine für in regelmässigen Abständen wiederkehrende Termine (täglich/wöchentlich/alle x Tage).
Hmm, dann würde ich doch direkt mal den 29. Februar als jährlich wiederkehrenden Termin eintragen. :)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@mkesper:
Daran hänge ich auch gerade fest... Wenn ich "von vorne" komme, ist das ja weiters kein Problem. Allerdings benötige ich ja die Möglichkeit, ein vorgegebenes Datum darauf zu prüfen, ob eventuelle Wiederholungen darauf stattfinden.
Wenn ich also den 29. Februar als wiederkehrenden Termin eintrage, findet dieser in allen Nicht-Schaltjahren am 01. März statt, in den Schaltjahren am 29. Februar. Schicke ich also einen 01. März eines Nicht-Schaltjahres zur Überprüfung, muss trotz des falschen Tages und des falschen Monats ein True zurückkommen.
Eine Lösung könnte sein, das zu überprüfende Datum erstmal durch ein "Aggregat" zu schicken, das dann "Problemdaten" wie eben dem 01. März eines Nicht-Schaltjahres noch den 29. Februar "hinklebt", damit sowohl für den 01. März wie auch für den 29. Februar ein Treffer stattfinden kann.

Hmm... noch nicht zu Ende gedacht...

Andere Ideen?

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