TypeError: unsupported operand type(s)

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.
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hallo,

ich habe hier eine Funktion zeitspanne_ermitteln(...) sie nimmt Parameter entgegen und soll mir ermitteln wie viel Tage oder Monate VON BIS sind.

Code: Alles auswählen

    def zeitspanne_ermitteln(
        self,
        vonJJJJ: int,
        vonM: int,
        vonT: int,
        bisJJJJ: int,
        bisM: int,
        bisT: int,
        einheit: str,
    ):
        if bisJJJJ == 0 and bisM == 0 and bisT == 0:
            zeitspanne = datetime.now() - datetime(vonJJJJ, vonM, vonT)
        else:
            zeitspanne = datetime(bisJJJJ, bisM, bisT) - datetime(vonJJJJ, vonM, vonT)

        zeitspanne = zeitspanne.total_seconds()

        if einheit == "Tage":
            tage = zeitspanne / 86400
            return tage
        elif einheit == "Monate":
            monate = math.floor(zeitspanne / 2419200) + 1
            return monate
Dann habe ich 2 Proberty Variablen die darauf zugreifen.
die Erste funktioniert wunderbar die Zweite irgendwie nicht. Obwohl sie fast identisch sind?

Als Fehlermeldung bekomme ich:

Code: Alles auswählen

/usr/bin/python3.12 /home/masterblack/PycharmProjects/Verbrauchsrechner/verbrauchsrechner.py 
Traceback (most recent call last):
  File "/home/masterblack/PycharmProjects/Verbrauchsrechner/verbrauchsrechner.py", line 43, in berechne
    f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €"
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/masterblack/PycharmProjects/Verbrauchsrechner/berechnungen/abrechnung.py", line 80, in bisheriger_gezahlter_abschlag
    betrag = monate * self.tarif.monatlicher_abschlag
             ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'

Code: Alles auswählen

	@property
    def grundpreis_tagesgenau_netto(self) -> float:
        """
        Ermittlung des Grundpreises bis zum heutigen Tag netto
        Returns
        -------
        float
        """
        betrag_tag = self.nettobetrag_berechnen(self.tarif.grundpreis) * 12 / 365
        tage = self.zeitspanne_ermitteln(2024, 6, 13, 0, 0, 0, "Tage")
        betrag = round(betrag_tag * tage, 3)
        return betrag
        
        
        @property
    def bisheriger_gezahlter_abschlag(self):
        monate = self.zeitspanne_ermitteln(2024, 6, 1, 0, 0, 0, "Monat")
        betrag = monate * self.tarif.monatlicher_abschlag
        return betrag
juwido
User
Beiträge: 24
Registriert: Donnerstag 15. Dezember 2022, 13:41

keine Ahnung ob es das schon ist, aber unten wird als Einheit "Monat" übergeben, oben aber auf "Monate" geprüft...
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: `zeitspanne_ermitteln()` gibt `None` zurück in `bisheriger_gezahlter_abschlag()`. Hättest Du den Rückgabetyp von `zeitspanne_ermitteln()` annotiert, hätte Dich die Typprüfung darauf hingewiesen.

Wenn man ein ``if``/``elif``-Konstrukt hat, sollte man immer überlegen warum da kein ``else`` ist und eines hinschreiben was einen Fehler auslöst wenn der Fall eigentlich nicht vorkommen sollte. Denn wenn man Fehler macht, passiert es halt doch.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

juwido hat geschrieben: Dienstag 15. Oktober 2024, 16:05 keine Ahnung ob es das schon ist, aber unten wird als Einheit "Monat" übergeben, oben aber auf "Monate" geprüft...
OH MY GOD :roll: Ja das war es. Ja ich war irgendwie so den TypeError am suchen, dass ich gar nicht an einen anderen Fehler gedacht habe.

Dankeschön :)
...sollte man immer überlegen warum da kein ``else`` ist und eines hinschreiben...
Ah ja, stimmt!

Danke
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

Wenn man schon Typannotationen benutzt, sollte man sie auch richtig benutzen:

Code: Alles auswählen

from datetime import datetime
from typing import Literal
from typing import assert_never

type Einheit = Literal["Tage", "Monate"]

def zeitspanne_ermitteln(
    vonJJJJ: int,
    vonM: int,
    vonT: int,
    bisJJJJ: int,
    bisM: int,
    bisT: int,
    einheit: Einheit,
) -> float:
    if bisJJJJ == 0 and bisM == 0 and bisT == 0:
        zeitspanne = datetime.now() - datetime(vonJJJJ, vonM, vonT)
    else:
        zeitspanne = datetime(bisJJJJ, bisM, bisT) - datetime(vonJJJJ, vonM, vonT)

    match einheit:
        case "Tage":
            return zeitspanne.total_seconds() / 86_400
        case "Monate":
            return zeitspanne.total_seconds() // 2_419_200 + 1
        case _:
            assert_never(einheit)


def main() -> None:
    zeitspanne_ermitteln(1234, 5, 6, 1235, 6, 7, "Monate")
    zeitspanne_ermitteln(1234, 5, 6, 1235, 6, 7, "Monat")


if __name__ == "__main__":
    main()

Code: Alles auswählen

$ mypy --strict t.py
t.py:32: error: Argument 7 to "zeitspanne_ermitteln" has incompatible type "Literal['Monat']"; expected "Literal['Tage', 'Monate']"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
Zwei Vorteile mit `Literal["Tage", "Monate"]` und `match`: `mypy` kann überprüfen, dass man nichts falsches übergibt, und es kann überprüfen, dass man alle Möglichkeiten im `match` beachtet hat.

Oder man benutzt `enum.Enum`, damit hat man die gleichen Vorteile, aber ein bisschen mehr Tipparbeit.
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also mir gefällt die API der Funktion nicht. Weder bei den Argumenten 1 oder 2 Datumsangaben als einzelne Zahlen statt als `date`-Objekte und die zweite Angabe ist ”optional” in dem man da drei Argumente mit dem Wert 0 übergibt. Und eine Funktion die über ein Zeichenketten-Argument entscheidet in welcher ”Einheit” das eigentlich gleiche Ergebnis zurückgegeben wird, ist auch nicht gut. Zumal die Werte nicht wirklich gleichwertig sind, denn aus den Monaten kann man nicht die Tage errechnen. An der Stelle müsste man sowieso mal dokumentieren was „ein Monat“ hier eigentlich bedeutet. Monate sind ja nicht gleich lang. Tage auch nicht unbedingt (Schaltjahre und -sekunden).

Und der Funktionsname ist falsch. Eine Zeitspanne hat einen Anfang und ein Ende. Was hier ausgerechnet wird ist eine Dauer oder die *Länge* einer Zeitspanne.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

@Master_Shredder: Variablennamen sollten sinnvoll benannt sein und werden komplett klein geschrieben. Was bedeutet JJJJ?
Warum gibt es Bruchteile von Tage aber keine Burchteile von Monaten? Warum ist bei einer Zeitspanne von exakt einem Monat das Resultat 2?
Wenn `self` in einer Funktion gar nicht benutzt wird, ist das ein Alarmsignal, dass man in Wirklichkeit gar keine Klasse hat.
Statt literale Strings benutzt man am besten ein Enum, das vermeidet Schreibfehler.

Code: Alles auswählen

class ZeitEinheit(enum.Enum):
    Tage=Enum.auto()
    Monate=Enum.auto()


def zeitspanne_ermitteln(start_datum, end_datum=None, einheit=ZeitEinheit.Tage):
    start_datum = datetime(*start_datum)
    end_datum = datetime.now() if end_datum is None else datetime(*end_datum)
    zeitspanne = (end_datum - start_datum).total_seconds() / (60*60*24)

    if einheit == ZeitEinheit.Tage:
        return zeitspanne
    elif einheit == ZeitEinheit.Monate:
        return zeitspanne / 28
    else:
        raise ValueError("einheit")
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

@narpfel Ah, eine match-Anweisung. Da bin ich dabei :D .
Nur was bedeutet den diese Schreibweise, mit Underscores bei den Tausendertrennzeichen? 2_419_200
Und warum sind hier zwei // (Slashes) hat dies vielleicht etwas damit zu tun, dass ich nicht mehr ausklammern muss?
Weder bei den Argumenten 1 oder 2 Datumsangaben als einzelne Zahlen statt als `date`-Objekte und die zweite Angabe ist ”optional” in dem man da drei Argumente mit dem Wert 0 übergibt.
Ja OK bin für Verbesserungen offen. Nur wie soll ich den "date" Objekte übergeben. Und wie soll ich entscheiden lassen was nun verwendet werden soll?
Benutzeravatar
snafu
User
Beiträge: 6850
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Master_Shredder hat geschrieben: Mittwoch 16. Oktober 2024, 14:46 Nur was bedeutet den diese Schreibweise, mit Underscores bei den Tausendertrennzeichen? 2_419_200
Das ist zur Vereinfachung von größere Zahlenangaben gedacht. Die Unterstriche werden von Python ignoriert und sollen einfach nur dem menschlichen Leser helfen. Das hätte man übrigens auch selber ausprobieren können. ;)
Master_Shredder hat geschrieben: Mittwoch 16. Oktober 2024, 14:46 Und warum sind hier zwei // (Slashes) hat dies vielleicht etwas damit zu tun, dass ich nicht mehr ausklammern muss?
Mit den beiden Slashes sorgt man für eine ganzzahlige Division. Es kommt beim Ergebnis also immer ein Integer raus. Auch hier gilt mein Kommentar zum Thema DIY.
Master_Shredder hat geschrieben: Mittwoch 16. Oktober 2024, 14:46 Ja OK bin für Verbesserungen offen. Nur wie soll ich den "date" Objekte übergeben. Und wie soll ich entscheiden lassen was nun verwendet werden soll?
Ich gebe mal einen Hinweis als Code:

Code: Alles auswählen

from datetime import date

heute = date(2024, 10, 16)
Und so wie die Funktion geschrieben ist, kann man wahlweise auch ein Tupel oder eine Liste mit Jahr, Datum, Tag übergeben. Durch Anwendung der *-Syntax (Tuple Unpacking) wird die Funktionssignatur recht flexibel an dieser Stelle.
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Ich gebe mal einen Hinweis als Code:
WOW, genial! Danke für den Tipp.

Ich habe auch den Bezeichner angepasst @__blackjack__ hoffe dieser trifft jetzt besser zu.

Achso meinen IDE(PyCharm) gibt noch an ich soll die Methode in eine static Methode umwandeln???

Code: Alles auswählen

 @staticmethod
    def vergangene_zeit_ermitteln(
        anfangsdatum: datetime, enddatum: datetime, einheit: Einheit
    ) -> float:
        zeitspanne = enddatum - anfangsdatum
        match einheit:
            case "Tage":
                return zeitspanne.total_seconds() / 86_400
            case "Monate":
                return zeitspanne.total_seconds() // 2_419_200 + 1
            case _:
                assert_never(einheit)

Code: Alles auswählen

@property
    def grundpreis_tagesgenau_netto(self) -> float:
        """
        Ermittlung des Grundpreises bis zum heutigen Tag netto
        Returns
        -------
        float
        """
        betrag_tag = self.nettobetrag_berechnen(self.tarif.grundpreis) * 12 / 365
        tage = self.vergangene_zeit_ermitteln(
            datetime(2024, 6, 13), datetime.now(), "Tage"
        )
        betrag = round(betrag_tag * tage, 3)
        return betrag

Code: Alles auswählen

@property
    def bisheriger_gezahlter_abschlag(self) -> float:
        monate = self.vergangene_zeit_ermitteln(
            datetime(2024, 6, 1), datetime.now(), "Monate"
        )
        betrag = monate * self.tarif.monatlicher_abschlag
        return betrag
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: Eigentlich eher in eine Funktion, denn das hat ja mit der Klasse eigentlich nichts zu tun, sondern ist eine allgemein verwendbare Funktion.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Eigentlich eher in eine Funktion, denn das hat ja mit der Klasse eigentlich nichts zu tun, sondern ist eine allgemein verwendbare Funktion.
@__blackjack__ Ja, genau nur wenn ich die Methode dann als Funktion außerhalb der Klasse platziere, ist dies doch schlechter Programmierstil oder sogar nicht gut in Bezug auf die Sicherheit oder?

Da ich noch weitere Methoden habe, die allgemein verwendbar sind, dachte ich mir, lagere ich diese in ein extra Modul(im gleichen Package) aus.
Doch aus irgendeinem Grund heißt es im jetzigen Modul, wenn ich auf die äußeren Methoden zugreifen will "Parameter 'PARAMETER' unfilled"

Code: Alles auswählen

"""
Modul Abrechnung. Hier befinden sich die Klasse Abrechnung und die Methoden zur Berechnung des Verbrauches
und der Kosten
"""

from datetime import datetime

from berechnungen.berechnung import Berechnung
from datenbank.datenbankverbindung import Tarif, setup_session


class Abrechnung:
    """
    Klasse Abrechnung
    """

    def __init__(self, eingegebener_zaehlerstand: int, tarif):
        self.eingegebener_zaehlerstand = eingegebener_zaehlerstand
        self.tarif = tarif

    @classmethod
    def eingabe_zaehlerstand(cls, eingegebener_zaehlerstand: int):
        session = setup_session()
        tarif = session.get(Tarif, 2)
        return cls(eingegebener_zaehlerstand, tarif)

    @property
    def verbrauch(self) -> int:
        """
        Ermittlung des Verbrauches anhand des Zaehlerstandes vom Ende des letzten Rechnungszeitraumes
        und des eingegeben Zaehlerstandes
        Returns
        -------
        Int ermittelter Verbrauch
        """
        betrag = (
            self.eingegebener_zaehlerstand - self.tarif.zaehlerstand_letzte_abrechnung
        )
        return betrag

    @property
    def arbeitspreis_netto(self) -> float:
        return Berechnung.nettobetrag_berechnen(self.tarif.arbeitspreis)

    @property
    def grundpreis_tagesgenau_netto(self) -> float:
        """
        Ermittlung des Grundpreises bis zum heutigen Tag netto
        Returns
        -------
        float
        """
        betrag_tag = Berechnung.nettobetrag_berechnen(self.tarif.grundpreis) * 12 / 365
        tage = Berechnung.vergangene_zeit_ermitteln(
            datetime(2024, 6, 13), datetime.now(), "Tage"
        )
        betrag = round(betrag_tag * tage, 3)
        return betrag

    @property
    def gesamtbetrag_netto(self) -> float:
        betrag = self.arbeitspreis_netto + self.grundpreis_tagesgenau_netto
        return betrag

    @property
    def gesamtbetrag_brutto(self) -> float:
        betrag = Berechnung.bruttobetrag_berechnen(self.gesamtbetrag_netto)
        return betrag

    @property
    def bisheriger_gezahlter_abschlag(self) -> float:
        monate = Berechnung.vergangene_zeit_ermitteln(
            datetime(2024, 6, 1), datetime.now(), "Monate"
        )
        betrag = monate * self.tarif.monatlicher_abschlag
        return betrag

    @property
    def abschlag_jahresende(self) -> float:
        """
        Hier wird die Summe des Abschlages bis zum Ende des Rechnungszeitraumes bei gleichbleibendem Betrag ermittelt
        Returns
        -------
        float Summe des voraussichtlich eingezahlten abschlages für den gesamten Rechnungszeitraum
        """
        betrag = self.tarif.monatlicher_abschlag * 12
        return betrag

    @property
    def akt_kosten_abz_gezahlter_abschlag(self) -> float:
        betrag = self.bisheriger_gezahlter_abschlag - self.gesamtbetrag_brutto
        return betrag

    @property
    def akt_kosten_abz_gezahlter_abschlag_jahresende(self) -> float:
        betrag = self.abschlag_jahresende - self.gesamtbetrag_brutto
        return betrag

    

Code: Alles auswählen

from datetime import datetime
from typing import Literal, assert_never

from datenbank.datenbankverbindung import Tarif, setup_session


class Berechnung:
    def __init__(self):
        session = setup_session()
        self.tarif = session.get(Tarif, 2)

    type Einheit = Literal["Tage", "Monate"]

    def nettobetrag_berechnen(self, bruttobetrag: float) -> float:
        """
        Berechnet den Nettobetrag an einem uebergebenen Bruttobetrag anhand in der Datenbank hinterlegten Prozentsatz
        Parameters
        ----------
        bruttobetrag

        Returns
        -------

        """
        mehrwertsteuer_betrag = bruttobetrag * self.tarif.mehrwertsteuer / 100
        betrag = bruttobetrag - mehrwertsteuer_betrag
        return betrag

    def bruttobetrag_berechnen(self, nettobetrag: float) -> float:
        """
        Addiert zu einem Betrag den in der Datenbank hinterlegten Prozentsatz
        Parameters
        ----------
        nettobetrag

        Returns
        -------
        Betrag plus Mehrwertsteuer
        """
        mehrwertsteuer_betrag = nettobetrag * self.tarif.mehrwertsteuer / 100
        betrag = nettobetrag + mehrwertsteuer_betrag
        return betrag

    @staticmethod
    def vergangene_zeit_ermitteln(
        anfangsdatum: datetime, enddatum: datetime, einheit: Einheit
    ) -> float:
        zeitspanne = enddatum - anfangsdatum
        match einheit:
            case "Tage":
                return zeitspanne.total_seconds() / 86_400
            case "Monate":
                return zeitspanne.total_seconds() // 2_419_200 + 1
            case _:
                assert_never(einheit)
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: Im Gegenteil, schlechter Programmierstil ist es Funktionen in Klassen zu stecken. Wenn das schlechter Stil wäre, hätte man Funktionen als Sprachmittel ja einfach weg lassen können. Es gibt sie aber, und es gibt ja auch viele Funktionen in der Python-Standardbibliothek.

Was ist denn der Code zu der Fehlermeldung und wie sieht die genau aus? Das sieht nicht nach etwas aus was von Python selbst kommt.

``from berechnungen.berechnung import Berechnung`` sieht bedenklich aus. Das ist ja nicht nur ein Modul mit wahrscheinlich nur einer Klasse, sondern auch noch ein unnütz aussehendes Package in dem das dann noch mal drin ist. Wahrscheinlich sollte die Klasse schon nicht in einem eigenen Modul sein.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Was ist denn der Code zu der Fehlermeldung und wie sieht die genau aus?
Ah sorry, hatte ich vergessen.

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/masterblack/PycharmProjects/Verbrauchsrechner/verbrauchsrechner.py", line 30, in berechne
    self.ui.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €")
                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/masterblack/PycharmProjects/Verbrauchsrechner/berechnungen/abrechnung.py", line 43, in arbeitspreis_netto
    return Berechnung.nettobetrag_berechnen(self.tarif.arbeitspreis)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Berechnung.nettobetrag_berechnen() missing 1 required positional argument: 'bruttobetrag'

Process finished with exit code 0

Code: Alles auswählen

 @property
    def arbeitspreis_netto(self) -> float:
        return Berechnung.nettobetrag_berechnen(self.tarif.arbeitspreis)

    @property
    def grundpreis_tagesgenau_netto(self) -> float:
        """
        Ermittlung des Grundpreises bis zum heutigen Tag netto
        Returns
        -------
        float
        """
        betrag_tag = Berechnung.nettobetrag_berechnen(self.tarif.grundpreis) * 12 / 365
        tage = Berechnung.vergangene_zeit_ermitteln(
            datetime(2024, 6, 13), datetime.now(), "Tage"
        )
        betrag = round(betrag_tag * tage, 3)
        return betrag
        
@property
    def gesamtbetrag_brutto(self) -> float:
        betrag = Berechnung.bruttobetrag_berechnen(self.gesamtbetrag_netto)
        return betrag
        
``from berechnungen.berechnung import Berechnung`` sieht bedenklich aus.
Ja, das "from berechnungen." kommt mir auch sehr redundant vor da "abrechnung.py" sich ja im gleichen Package befindet.
Im Gegenteil, schlechter Programmierstil ist es Funktionen in Klassen zu stecken.
Sollte ich die Klasse "Berechnung" komplett sein lassen?
Benutzeravatar
__blackjack__
User
Beiträge: 14000
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: Du versuchst da eine Methode auf der *Klasse* aufzurufen, statt auf einem Objekt das diese Klasse als Typ hat.

`Berechnung` kapselt ja eigentlich nur das `Tarif`-Objekt. Sieht für mich nicht so wirklich sinnvoll aus. `Abrechnung` eigentlich auch nicht. Wobei die Abrechnung ja auch einen Tarif kennt. Ist das der selbe? Wenn der nicht von dem `Berechnung`-Objekt abgefragt wird, wäre das komisch. Wobei es auch komisch wäre den von dem `Berechnung`-Objekt abzufragen. Mir würde auch so gar nicht gefallen das Objekte in der `__init__()` selbst eine Datenbankverbindung aufbauen und da Sachen abfragen. Wie testet man das denn dann ohne die Datenbank? So eine Abrechnung sollte die Logik für die Abrechnung enthalten und nichts von einer Datenbank wissen müssen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Qubit
User
Beiträge: 130
Registriert: Dienstag 7. Oktober 2008, 09:07

Ich weiß zwar nicht genau, was du da an Zeitdifferenzen berechnen willst, aber Python hat da schon etwas on board, nennt sich "relativedelta"..

Code: Alles auswählen

import datetime
from dateutil.relativedelta import relativedelta



def zeitspanne_ermitteln(start,end):
    return relativedelta(end, start)



def main():

    von='2024-10-10 00:45:10'
    bis='2026-09-03 18:30:30'

    start_time = datetime.datetime.strptime(von, '%Y-%m-%d %H:%M:%S')
    end_time = datetime.datetime.strptime(bis, '%Y-%m-%d %H:%M:%S')

    diff = zeitspanne_ermitteln(start_time,end_time)

    print(f"Start: {start_time}")
    print(f"End: {end_time}")
    print(f"Differenz: {diff.years} Jahre, {diff.months} Monate,  {diff.days} Tage, {diff.hours} Stunden, {diff.minutes} Minuten, {diff.seconds} Sekunden")

if __name__ == "__main__":
    main()
Start: 2024-10-10 00:45:10
End: 2026-09-03 18:30:30
Differenz: 1 Jahre, 10 Monate, 24 Tage, 17 Stunden, 45 Minuten, 20 Sekunden
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Du versuchst da eine Methode auf der *Klasse* aufzurufen, statt auf einem Objekt das diese Klasse als Typ hat.
Ja das stimmt, da Stimme ich zu.
Wobei die Abrechnung ja auch einen Tarif kennt. Ist das der selbe?
Ja ist der selbe Tarif.

Ist es so besser? Ich habe ein Objekt aus "berechnung" erstellt über welsches ich jetzt aufrufe.
Und die Funktionen in "berechnung" erhalten die "mehrwertsteuer" des "Tarif"-Objektes als Parameter.

Code: Alles auswählen

from datetime import datetime
from typing import Literal, assert_never

type Einheit = Literal["Tage", "Monate"]


def nettobetrag_berechnen(bruttobetrag: float, mehrwertsteuer: float) -> float:
    """
    Berechnet den Nettobetrag an einem uebergebenen Bruttobetrag anhand in der Datenbank hinterlegten Prozentsatz
    Parameters
    ----------
    bruttobetrag
    mehrwertsteuer

    Returns
    -------

    """
    mehrwertsteuer_betrag = bruttobetrag * mehrwertsteuer / 100
    betrag = bruttobetrag - mehrwertsteuer_betrag
    return betrag


def bruttobetrag_berechnen(nettobetrag: float, mehrwertsteuer: float) -> float:
    """
    Addiert zu einem Betrag den in der Datenbank hinterlegten Prozentsatz
    Parameters
    ----------
    nettobetrag
    mehrwertsteuer

    Returns
    -------
    Betrag plus Mehrwertsteuer
    """
    mehrwertsteuer_betrag = nettobetrag * mehrwertsteuer / 100
    betrag = nettobetrag + mehrwertsteuer_betrag
    return betrag


def vergangene_zeit_ermitteln(
    anfangsdatum: datetime, enddatum: datetime, einheit: Einheit
) -> float:
    zeitspanne = enddatum - anfangsdatum
    match einheit:
        case "Tage":
            return zeitspanne.total_seconds() / 86_400
        case "Monate":
            return zeitspanne.total_seconds() // 2_419_200 + 1
        case _:
            assert_never(einheit)

Code: Alles auswählen

"""
Modul Abrechnung. Hier befinden sich die Klasse Abrechnung und die Methoden zur Berechnung des Verbrauches
und der Kosten
"""

from datetime import datetime

from berechnungen import berechnung
from datenbank.datenbankverbindung import Tarif, setup_session


class Abrechnung:
    """
    Klasse Abrechnung
    """

    def __init__(self, eingegebener_zaehlerstand: int, tarif):
        self.eingegebener_zaehlerstand = eingegebener_zaehlerstand
        self.tarif = tarif

    berechne = berechnung

    @classmethod
    def eingabe_zaehlerstand(cls, eingegebener_zaehlerstand: int):
        session = setup_session()
        tarif = session.get(Tarif, 2)
        return cls(eingegebener_zaehlerstand, tarif)

  .............

    @property
    def arbeitspreis_netto(self) -> float:
        return self.berechne.nettobetrag_berechnen(
            self.tarif.arbeitspreis, self.tarif.mehrwertsteuer
        )

    @property
    def grundpreis_tagesgenau_netto(self) -> float:
        """
        Ermittlung des Grundpreises bis zum heutigen Tag netto
        Returns
        -------
        float
        """
        betrag_tag = (
            self.berechne.nettobetrag_berechnen(
                self.tarif.grundpreis, self.tarif.mehrwertsteuer
            )
            * 12
            / 365
        )
        tage = self.berechne.vergangene_zeit_ermitteln(
            datetime(2024, 6, 13), datetime.now(), "Tage"
        )
        betrag = round(betrag_tag * tage, 3)
        return betrag

    @property
    def gesamtbetrag_netto(self) -> float:
        betrag = self.arbeitspreis_netto + self.grundpreis_tagesgenau_netto
        return betrag

    @property
    def gesamtbetrag_brutto(self) -> float:
        betrag = self.berechne.bruttobetrag_berechnen(
            self.gesamtbetrag_netto, self.tarif.mehrwertsteuer
        )
        return betrag

    @property
    def bisheriger_gezahlter_abschlag(self) -> float:
        monate = self.berechne.vergangene_zeit_ermitteln(
            datetime(2024, 6, 1), datetime.now(), "Monate"
        )
        betrag = monate * self.tarif.monatlicher_abschlag
        return betrag
        
...............
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@Master_Shredder: `berechnungen.berechnung` ist immer noch eine Verschachtelung zu viel. Und `berechnung` ist auch nicht wirklich ein guter Name, weil zu allgemein (alles, was ein Programm macht, ist im Endeffekt eine Berechnung).

`datenbank.datenbankverbindung` ist auch zu verschachtelt IMHO.

Warum hast du in der Klasse `berechne = berechnung` geschrieben?

Und (den Hinweis hat __blackjack__ sicher schon gegeben): Man muss nicht jedes Zwischenergebnis an einen Namen binden. Statt

Code: Alles auswählen

    @property
    def gesamtbetrag_brutto(self) -> float:
        betrag = self.berechne.bruttobetrag_berechnen(
            self.gesamtbetrag_netto, self.tarif.mehrwertsteuer
        )
        return betrag
besser

Code: Alles auswählen

    @property
    def gesamtbetrag_brutto(self) -> float:
        return self.berechne.bruttobetrag_berechnen(
            self.gesamtbetrag_netto, self.tarif.mehrwertsteuer
        )
Und bitte unbedingt `--strict` für mypy benutzen, deine Typannotationen sind immer noch kaputt.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

@narpfel: bei Sprachen, die Ducktyping unterstützen sind Typannotation per Definition kaputt.
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@Sirius3: Stimmt, aber wenn man schon Typannotationen benutzt, dann sollte man sie wenigstens so benutzen, dass sie korrekt sind. Selbst wenn sie dann zu einschränkend sind o. Ä.
Antworten