Seite 1 von 2
Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Dienstag 6. Februar 2024, 17:55
von Master_Shredder
Hi zusammen,
ich würde gerne mal wissen ob ich diese Klasse so korrekt geschrieben habe?
Speziell auch mit dem Datenbank Aufruf im Konstruktor. Dies ist ja eigentlich nur ein Attribute anlegen.
Hier die Klasse:
Code: Alles auswählen
import math
import sqlite3
from datetime import datetime
class Abrechnung:
""" Klasse Abrechnung. Hier befinden sich die Methoden zur Berechnung der Verbrauchsdaten"""
def __init__(self):
# Verbindung zur Datenbank herstellen
verbindung = sqlite3.connect("Datenbank/verbrauch.db")
zeiger = verbindung.cursor()
# Datenbank Abfrage
zeiger.execute("SELECT zaehlerstand_letzte_abrechnung FROM tarif")
self.zaehlerstand_letzte_abrechnung = zeiger.fetchone()[0]
zeiger.execute("SELECT arbeitspreis_netto FROM tarif")
self.arbeitspreis = zeiger.fetchone()[0]
zeiger.execute("SELECT grundpreis_netto FROM tarif")
self.grundpreis_netto = zeiger.fetchone()[0]
zeiger.execute("SELECT mehrwertsteuer FROM tarif")
self.mehrwertsteuer = zeiger.fetchone()[0]
zeiger.execute("SELECT Vertragsbeginn FROM Vertragsdaten")
self.vertragsbeginn = zeiger.fetchone()[0]
zeiger.execute("SELECT zu_zahlender_abschlag FROM tarif")
self.zu_zahlender_abschlag = zeiger.fetchone()[0]
verbindung.close()
self.akt_zaehlerstand = 0
self.arbeitspreis_netto = 0
self.arbeitspreis_brutto = 0
self.grundpreis_tag_brutto = 0
self.grundpreis_tag_netto = 0
self.gesamtbetrag_netto = 0
self.gesamtbetrag_brutto = 0
self.gezahlter_abschlag = 0
self.gez_abschlag_ej = 0
self.gp_abz_ablschlag_bj = 0
self.gp_abz_abschlag_ej = 0
# Abrechnungen
# Ermittlung aktueller Zaehlerstand
def ber_akt_zaehlerstand(self, eingabe):
self.akt_zaehlerstand = int(eingabe) - self.zaehlerstand_letzte_abrechnung
# Berechnung Arbeitspreis
def ber_arbeitspreis_netto(self):
self.arbeitspreis_netto = round(self.akt_zaehlerstand * self.arbeitspreis / 100, 2)
def ber_arbeitspreis_brutto(self):
self.arbeitspreis_brutto = round(
self.akt_zaehlerstand * self.arbeitspreis / 100 * self.mehrwertsteuer, 2)
# Berechnung Grundpreis
def ber_grundpreis_brutto(self):
self.grundpreis_tag_brutto = round(self.grundpreis_tag_netto * self.mehrwertsteuer, 2)
...........
Hier geht es jetzt mit so ähnlichen Methoden weiter.
Die Methoden werden dann in dieser Klasse aufgerufen und in einer GUI ausgegeben:
Code: Alles auswählen
..............
class Verbrauchsrechner(QtWidgets.QMainWindow):
"""Main Klasse hier startet das Programm"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Hauptdialog()
self.ui.setupUi(self)
# Slot eingerichtet
self.ui.button_OK.clicked.connect(self.berechne)
# Slot
def berechne(self):
berechnung = Abrechnung()
berechnung.ber_akt_zaehlerstand(self.ui.spin_box_eingabe.value())
berechnung.ber_arbeitspreis_netto()
self.ui.arbeitspreis_netto.setText(str(berechnung.arbeitspreis_netto) + " €")
berechnung.ber_arbeitspreis_brutto()
self.ui.arbeitspreis_brutto.setText(str(berechnung.arbeitspreis_brutto) + " €")
berechnung.ver_grundgebuer()
self.ui.grundpreis_netto.setText(str(berechnung.grundpreis_tag_netto) + " €")
berechnung.ber_grundpreis_brutto()
self.ui.grundpreis_brutto.setText(str(berechnung.grundpreis_tag_brutto) + " €")
berechnung.ber_gesamtbetrag_netto()
self.ui.gesamtpreis_netto.setText(str(berechnung.gesamtbetrag_netto) + " €")
berechnung.ber_gesamtbetrag_brutto()
self.ui.gesamtpreis_brutto.setText(str(berechnung.gesamtbetrag_brutto) + " €")
berechnung.er_abschlag()
self.ui.abschlag_bis_jetzt.setText(str(berechnung.gezahlter_abschlag) + " €")
self.ui.abschlag_ende_des_jahres.setText(str(berechnung.gez_abschlag_ej) + " €")
berechnung.ber_gp_abz_abschlag_bj()
self.ui.gp_abz_abschlag_bj.setText(str(berechnung.gp_abz_ablschlag_bj) + " €")
berechnung.ber_gp_abz_abschlag_ej()
self.ui.gp_abz_abschlag_ej.setText(str(berechnung.gp_abz_abschlag_ej) + " €")
...................
Danke schon mal für die Hilfe
MFG Master_Shredder
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Dienstag 6. Februar 2024, 18:17
von __blackjack__
@Master_Shredder: Das ist falsch, das ist so keine sinnvolle Klasse.
Und die Datenbank an sich sieht schon fragwürdig aus. So eine Abfrage hat keine Ordnung. Da kann bei dem `fetchone()` jeweils ein beliebiger Datensatz ausgewählt werden, das heisst keiner der Werte aus der `tarif`-Tabelle muss zu den anderen passen. Das würde so nur korrekt sein, wenn die beiden Tabellen immer nur genau einen Datensatz enthalten würden, wo dann die Frage ist warum das in einer Datenbank steht.
Wenn das eine sinnvolle Datenbank wäre, müsste man auch über einen Fremdschlüssel sicherstellen, dass die Werte aus der `tarif`-Tabelle zu dem Wert aus der `Vertragsdaten`-Tabelle passt.
Und man würde nicht jeden Wert aus `tarif` einzeln abfragen, sondern alle zusammengehörenden Werte in einer Abfrage zusammenfassen.
So etwas wie eine Datenbankabfrage würde ich auch nicht in der `__init__()` machen. Das macht es sehr schwer für diese Klasse Unit-Tests mit Beispieldaten zu schreiben, oder die Werte auch mal von woanders zu holen als aus einer SQLite-Datenbank. Die `__init__()` ist besser nur zum Initialisieren und ggf. Validieren von Attributen da. Wenn man mehr machen will/muss, bietet es sich an dafür eine `classmethod()` zu schreiben.
Den Datenbankdateinamen sollte man als Argument übergeben. Auch wieder damit das testbar wird.
Die ganzen Attribute die mit 0 initialisiert werden und dann später durch einzelne Methodenaufrufe dann erst den tatsächlichen Wert bekommen, sollte es nicht geben. Das ist fehleranfällig, weil man immer erst die dazugehörige Methode aufrufen muss, damit es einen sinnvollen Wert gibt, und Änderungen an den Ausgangsdaten nach der Berechnung lassen die Werte inkonsistent werden. Das wären vielleicht alles besser Properties (`property()`).
Es würde sich hier auch anbieten auf ein ORM wie SQLAlchemy aufzusetzen, bevor man anfängt sich so etwas selber zu schreiben, oder das alles ”zu Fuss” auszuformulieren.
Namen sollten keine kryptische Abkürzungen enthalten. So etwas wie `ber_gp_abz_abschlag_ej()` versteht doch keiner.
Das Zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Dienstag 6. Februar 2024, 19:31
von noisefloor
Hallo,
nochmal zur DB: der Code mag zufällig funktionieren, aber in der Form, wie du abfragst, hast du _keine_ garantierte Reihenfolge der Rückgabewerte aus der DB. D.h. `fetchone()[0]` liefert dir _nicht_ zwingend den neuesten Wert - aber wenn das vielleicht zufällig bei dir bis jetzt der Fall ist. Da muss dann noch eine `ORDER BY` Klausel hin, die die Rückgabewerte z.B. nach Datum sortieren würde (was im gegebenen Fall sinnvoll erscheint, dass jeder Eintrag in `tarif` ein Datum hat.
Abgesehen davon hast du unnötig viele Roundtrips durch die Datenbank - alle Abfragen gegen `tarif` kannst du ohne Probleme in eine Abfrage zusammenziehen.
Die GUI-Klasse ist IMHO komisch benannt - da wird ja offensichtlich nicht (nur) der Verbrauch berechnet, sondern auch irgendwas mit Kosten.
Bei der Abrechnung Klasse würde ich das Abholen der Daten auch in eine eigene Methode auslagern (wie z.B. `get_data_from_database` oder so ähnlich), die man explizit aufruft, um an die Daten zu kommen
Gruß, noisefloor
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Dienstag 6. Februar 2024, 20:33
von Sirius3
Das ganze könnte so aussehen:
Code: Alles auswählen
DATABASE_NAME = "Datenbank/verbrauch.db"
class Abrechnung:
""" Klasse Abrechnung. Hier befinden sich die Methoden zur Berechnung der Verbrauchsdaten"""
def __init__(self, zaehlerstand_letzte_abrechnung, arbeitspreis_netto, grundpreis_netto, mehrwertsteuer, zu_zahlender_abschlag, Vertragsbeginn, aktueller_zaehlerstand):
self.zaehlerstand_letzte_abrechnung = zaehlerstand_letzte_abrechnung
self.arbeitspreis_netto = arbeitspreis_netto
self.grundpreis_netto = grundpreis_netto
self.mehrwertsteuer = mehrwertsteuer
self.zu_zahlender_abschlag = zu_zahlender_abschlag
self.vertragsbeginn = vertragsbeginn
self.aktueller_zaehlerstand = aktueller_zaehlerstand
@classmethod
def from_database(cls, database, aktueller_zaehlerstand):
with closing(database.cursor()) as cursor:
cursor.execute("SELECT zaehlerstand_letzte_abrechnung, arbeitspreis_netto, grundpreis_netto, mehrwertsteuer, zu_zahlender_abschlag, Vertragsbeginn FROM tarif, Vertragsdaten WHERE Vertragsdaten.tarif_id = tarif.id AND bedingung_fuer_aktuellen_vertrag")
zaehlerstand_letzte_abrechnung, arbeitspreis_netto, grundpreis_netto, mehrwertsteuer, zu_zahlender_abschlag, Vertragsbeginn = cursor.fetchone()
return cls(zaehlerstand_letzte_abrechnung, arbeitspreis_netto, grundpreis_netto, mehrwertsteuer, zu_zahlender_abschlag, Vertragsbeginn, aktueller_zaehlerstand)
@property
def arbeitspreis_netto(self):
return self.akt_zaehlerstand * self.arbeitspreis / 100
@property
def arbeitspreis_brutto(self):
return self.arbeitspreis_netto * self.mehrwertsteuer
...
def berechne(self):
zaehlerstand = self.ui.spin_box_eingabe.value()
with closing(sqlite3.connect(DATABASE_NAME)) as database:
abrechnung = Abrechnung.from_database(database, zaehlerstand)
self.ui.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €")
self.ui.arbeitspreis_brutto.setText(f"{abrechnung.arbeitspreis_brutto:.2f} €")
...
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Mittwoch 7. Februar 2024, 15:14
von Master_Shredder
Hi @all,
WOW, OK, interessant.
Also mit der Klassen Methode from_database() frage ich die Datenbank ab und gebe die Werte zurück und initialisiere damit die Attribute.
Die anderen Methoden sind eigentlich Property Getter die in der Rückgabe die Werte erst ermitteln. Interessant.
Und mit der "with" Anweisung rufe ich die Datenbank auf und stelle sicher, dass sie wieder geschlossen wird. Also eigentlich das ganze "connect" und "close" in einer Anweisung, cool.
Das Zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.
Ah ja. Das ist ja auch cool. Da habe ich gerade auch noch gerundet

.
Es würde sich hier auch anbieten auf ein ORM wie SQLAlchemy aufzusetzen, bevor man anfängt sich so etwas selber zu schreiben, oder das alles ”zu Fuss” auszuformulieren.
OK, dies muss ich mir mal genau ansehen.
müsste man auch über einen Fremdschlüssel sicherstellen, dass die Werte aus der `tarif`-Tabelle zu dem Wert aus der `Vertragsdaten`-Tabelle passt.
K. sehe ich mir an.
Also die Datenbank ist da um die Vertragsdaten, Tarifdaten zu speichern auch bei Änderung. Und des weiteren soll sie später noch Daten bzw. Zählerstande, nach Wunsch, speichern um einen Überblick des Verbrauchs fürs ganze Jahr zu bekommen z.B. Strom.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Mittwoch 7. Februar 2024, 19:26
von DeaD_EyE
Man spart sich viel Tipparbeit bei der __init__ Methode, wenn man eine
dataclass verwendet.
Falls man etwas innerhalb der
__init__ Methode machen muss, kann man die Methode
__post_init__ verwenden.
Anmerkung: Man muss im eingerückten Block der Klassendefinition die Typen der Namen mit angeben.
Getrennt wird mit einem Doppelpunkt. Das heißt aber nicht, dass die Dataclass eine
automatische Typenkonvertirung vornimmt und es wird auch nicht überprüft, ob die Typen überhaupt stimmen.
Um sich noch mehr Schreibarbeit zu ersparen, kann könnte auch auf einen ORM zurückgreifen. SQLAlchemy ist ja bereits erwähnt worden. Die ORM checken auch die Datentypen und es ist garantiert, dass ein Fehler ausgelöst wird, wenn die Daten den falschen Datentyp haben. Abstraktion kostet immer Speicherplatz und etwas mehr Laufzeit. Wenn man z.B. vor hat das mit einem Raspberry Pi auf einer SD-Karte zu machen, dauert das importieren von SQLAlchemy mehr als eine Sekunde.
Code: Alles auswählen
from contextlib import closing
from datetime import date as Date
# in der Standardbibliothek gibt es einige Module,
# die sich nicht an die Namensgebungskonventionen halten.
# Macht aber nichts, kann man umbenennen
# date ist eine Klasse, also der erste Buchstabe Groß
from dataclasses import dataclass
# oftmals die einfachere Möglichkeit, vor allem dann, wenn
# man sehr viele Argumente für die Initialisierung der Instanz übergeben muss
@dataclass
class Abrechnung:
"""Klasse Abrechnung. Hier befinden sich die Methoden zur Berechnung der Verbrauchsdaten"""
zaehlerstand_letzte_abrechnung: int
grundpreis_netto: float
mehrwertsteuer: float
zu_zahlender_abschlag: int
vertragsbeginn: Date
aktueller_zaehlerstand: int
@classmethod
def from_database(cls, database, aktueller_zaehlerstand):
with closing(database.cursor()) as cursor:
# https://www.freeformatter.com/sql-formatter.html
cursor.execute(
"""
SELECT
zaehlerstand_letzte_abrechnung,
arbeitspreis_netto,
grundpreis_netto,
mehrwertsteuer,
zu_zahlender_abschlag,
vertragsbeginn
FROM
tarif,
vertragsdaten
WHERE
vertragsdaten.tarif_id = tarif.id
AND bedingung_fuer_aktuellen_vertrag
"""
)
return cls(*cursor.fetchone())
@property
def arbeitspreis_netto(self):
return self.aktueller_zaehlerstand * self.arbeitspreis / 100
@property
def arbeitspreis_brutto(self):
return self.arbeitspreis_netto * self.mehrwertsteuer
def berechne(self):
zaehlerstand = self.ui.spin_box_eingabe.value()
with closing(sqlite3.connect(DATABASE_NAME)) as database:
abrechnung = Abrechnung.from_database(database, zaehlerstand)
self.ui.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €")
self.ui.arbeitspreis_brutto.setText(f"{abrechnung.arbeitspreis_brutto:.2f} €")
...
Hinweis: Ich verwende für die Formatierung
ruff. Nach mehr als 10 Jahren kann man aber auch händisch Code schreiben, der durch Black/Ruff nicht mehr umformatiert wird.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Donnerstag 8. Februar 2024, 17:46
von Master_Shredder
Hi @all,
Man spart sich viel Tipparbeit bei der __init__ Methode, wenn man eine dataclass verwendet.
Falls man etwas innerhalb der __init__ Methode machen muss, kann man die Methode __post_init__ verwenden.
@DeaD_EyE Ja danke, aber ich will es mir jetzt nicht noch komplizierter machen. Ich bin noch nicht all so gut und mache dies nur aus Hobby.
Und ich sollte ja auch alles verstehen was ich tue.
Aber nun ich habe ein anderes Problem. Ich habe mir mal SQLAlchemy ORM angesehen und finde es gut, es kann mir viel Arbeit sparen und ist besser wartbar.
Nur irgendwie bekomme ich es nicht zum arbeiten.
Code: Alles auswählen
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
DATABASE_NAME = "Datenbank/testdatenbank.db"
engine = create_engine(DATABASE_NAME)
conn = engine.connect()
class Abrechnung:
""" Klasse Abrechnung. Hier befinden sich die Methoden zur Berechnung der Verbrauchsdaten"""
def from_database(self):
with Session(engine) as session:
person = session.get(Person, 1)
print(person)
Ich habe auch ein Paar andere Methoden versucht. Abfragen über .query oder select
Aber irgendwie will es nicht. Es ist auch immer der Fall, dass meine IDE das Tabellen Argument nicht erkennt.
Und die Doc macht es mir auch nicht gerade leicht.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Donnerstag 8. Februar 2024, 18:49
von __blackjack__
@Master_Shredder: SQLAlchemy hat eine ziemlich gute Dokumentation. Man muss sich da halt einarbeiten.
Der Quelltext sieht irgendwie geraten aus. `conn` wird nirgends verwendet. `Person` ist nirgends definiert, das kann dann so natürlich nicht funktionieren. `engine` und `conn` sind auch keine Konstanten, sollten also nicht einfach so auf Modulebene als globale Variablen existieren.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Donnerstag 8. Februar 2024, 19:11
von Master_Shredder
SQLAlchemy hat eine ziemlich gute Dokumentation.
Ja, das Glaube ich auch. Nur ein großes Problem ist das es Englisch ist, also nicht gerade ein einfaches.
Und mir ist kein, sage mal, direkter Weg ersichtlich.
`conn` wird nirgends verwendet.
Oh, ja das stimmt. Das ist wohl irgendwann bei den verschiedenen Versuchen untergegangen.
Also ich habe mir auch ein Paar Videos angesehen. Nur die beginnen da IMMER mit der Verbindung und schreiben dann die Tabelle. Und nicht machen einfach mal ein Paar Abfragen.
Da ist mir auch aufgefallen, dass SIE da eine Klasse schreiben die wie die Tabelle heißt, wie bei mir "Person".
Denke mal dann würde es vielleicht so funktionieren.
Aber dies brauche ich ja eigentlich alles nicht. Ich habe ja meine Tabellen und Spalten und möchte sie nur abfragen.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 00:06
von snafu
Die Doku von SQLAlchemy kann am Anfang tatsächlich etwas verwirrend sein, zumal nun auch die API in der Version 2.0 dazugekommen ist. Ganz grob gesagt erstellt man zuerst die Struktur für die Daten:
Code: Alles auswählen
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = "user_account"
id = Column(Integer, primary_key=True)
name = Column(String(30))
fullname = Column(String)
addresses = relationship(
"Address", back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self):
return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
email_address = Column(String, nullable=False)
user_id = Column(Integer, ForeignKey("user_account.id"), nullable=False)
user = relationship("User", back_populates="addresses")
def __repr__(self):
return f"Address(id={self.id!r}, email_address={self.email_address!r})"
... dann erstellt man ein paar Objekte (normalerweise natürlich mit Nutzereingaben oder aus einer anderen Quelle):
Code: Alles auswählen
from sqlalchemy.orm import Session
with Session(engine) as session:
spongebob = User(
name="spongebob",
fullname="Spongebob Squarepants",
addresses=[Address(email_address="spongebob@sqlalchemy.org")],
)
sandy = User(
name="sandy",
fullname="Sandy Cheeks",
addresses=[
Address(email_address="sandy@sqlalchemy.org"),
Address(email_address="sandy@squirrelpower.org"),
],
)
patrick = User(name="patrick", fullname="Patrick Star")
session.add_all([spongebob, sandy, patrick])
session.commit()
Und dann macht man die gewünschten Abfragen:
Code: Alles auswählen
from sqlalchemy import select
session = Session(engine)
stmt = select(User).where(User.name.in_(["spongebob", "sandy"]))
for user in session.scalars(stmt):
print(user)
Alle Code-Beispiele stammen von hier:
https://docs.sqlalchemy.org/en/14/orm/quickstart.html
Das kann ich auch zum Nachlesen wärmstens empfehlen.
Und für die API 2.0:
https://docs.sqlalchemy.org/en/20/orm/quickstart.html
Letzteres ist für mich ehrlich gesagt auch noch Neuland. Ich finde die alte API einfacher. Wird aber wohl alles seinen Sinn haben und man sollte sich die auch zeitnah aneignen.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 01:29
von grubenfox
Master_Shredder hat geschrieben: Donnerstag 8. Februar 2024, 19:11
Aber dies brauche ich ja eigentlich alles nicht. Ich habe ja meine Tabellen und Spalten und möchte sie nur abfragen.
Der interessante Part in der Doku beginnt wohl hier:
https://docs.sqlalchemy.org/en/20/core/reflection.html
https://docs.sqlalchemy.org/en/14/core/reflection.html
Oberflächlich betrachtet (nur die ersten 2-3 Absätze angeschaut) scheint sich da zwischen den Versionen nichts geändert zu haben.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 09:11
von __blackjack__
Ich persönlich schreibe die Klassen lieber selbst. Es ist dann auch gleichzeitig Dokumentatation und ”Behälter” für Dokumentation (DocStrings) und man kann den Klassen noch Properties und Methoden verpassen. Und bei bereits bestehenden Datenbanken auch mal Namen ”korrigieren”.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 20:41
von Master_Shredder
Jaa, ich habe mal etwas hinbekommen. Ich habe es jetzt mal nach einem Video Tutorial gemacht.
https://piped.kavin.rocks/playlist?list ... rqOeieFRWn
Also es funktioniert schon mal. Hoffe dies kann man grundsätzlich so verwenden?
Also ich habe eine extra Klasse für die "Verbindung" erstellt. Ich denke mal, dass ist was @ __blackjack__ meinte.
Persönlich finde ich sie auch nicht schlecht. Und ich kann mir auch gut vorstellen das sie auch die von @ __blackjack__ erwähnten Vorteile hat.
Datenbankverbindung.py:
Code: Alles auswählen
from sqlalchemy import create_engine, Column
from sqlalchemy.orm import declarative_base
from sqlalchemy.sql.sqltypes import Integer, Float
engine = create_engine('sqlite:///Datenbank/verbrauch.db')
base = declarative_base()
conn = engine.connect()
class Tarif(base):
__tablename__ = 'tarif'
tarif_id = Column(Integer, primary_key=True, autoincrement=True)
arbeitspreis = Column(Float, nullable=False)
grundpreis = Column(Float, nullable=False)
mehrwertsteuer = Column(Float, nullable=False)
zaehlerstand_letzte_abrechnung = Column(Float, nullable=False)
zu_zahlender_abschlag = Column(Integer, nullable=False)
def __init__(self, arbeitspreis: float, grundpreis: float, mehrwertsteuer: float,
zaehlerstand_letzte_abrechnung: float, zu_zahlender_abschlag: float):
self.arbeitspreis = arbeitspreis
self.grundpreis = grundpreis
self.mehrwertsteuer = mehrwertsteuer
self.zaehlerstand_letzte_abrechnung = zaehlerstand_letzte_abrechnung
self.zu_zahlender_abschlag = zu_zahlender_abschlag
base.metadata.create_all(conn)
Hier habe ich dann die Klasse Abrechnung:
Code: Alles auswählen
from sqlalchemy.orm import Session
from Datenbankverbindung import Tarif, engine
class Abrechnung:
""" Klasse Abrechnung. Hier befinden sich die Methoden zur Berechnung der Verbrauchsdaten"""
def __init__(self, arbeitspreis, grundpreis, mehrwertsteuer):
self.arbeitspreis = arbeitspreis
self.grundpreis = grundpreis
self.mehrwertsteuer = mehrwertsteuer
@classmethod
def from_database(cls, eingegebener_zaehlerstand):
my_session = Session(bind=engine)
tarif = my_session.get(Tarif, 1)
return cls(tarif.arbeitspreis,
tarif.grundpreis, tarif.mehrwertsteuer)
Ja die Doc von SQLAlchemy ist etwas kompliziert geschrieben. Es ist so eine Puzzle Doc, ja da brauch man erst mal Durchblick. Das ist bei mir wie wenn ich in die Java Doc schaue, da bräuchte ich auch gar keine, da verstehe ich nichts

. Muss mal einen Anfang bekommen, dann wird es wohl gehen. Also wie ich das hier nach dem Video Tutorial gemacht habe war es echt easy. Aber ja wie ich jetzt wieser gemerkt habe, gibt es jetzt da auch noch eine neuere Version.

Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 21:03
von __blackjack__
@Master_Shredder: Die `Tarif.__init__()` ist überflüssig. Die Klasse erbt bereits eine sinvolle `__init__()`.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 21:28
von snafu
Das ist überhaupt sehr wirsch umgesetzt. Warum enthält der Tarif so Dinge wie den letzten Zählerstand und den Abschlag? Warum dupliziert die Abrechnung nochmal die Hälfte der Tarif-Attribute? Das sollten besser Properties der Abrechnung sein, wo der zu zahlende Abschlag sowie Guthaben/Nachzahlung sich aus den Angaben zum Tarif in Abhängigkeit vom Verbrauch ergeben. Auch die Umsatzsteuer würde man doch normalerweise erst in der eigentlichen Rechnung ermitteln.
Wenn man Attribute aus einer anderen Tabelle übernehmen möchte, dann macht man das übrigens mit einer Relation (in SQLAlchemy: ``relationship()``). Ich verstehe, dass das am Anfang vielleicht noch nicht alles so optimal sitzt, aber insbesondere das Design der beiden eingangs angesprochenen Klassen würde ich mal stark überdenken...
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 21:46
von noisefloor
Hallo,
zum Tabellendesign: beim Tarif fehlt IMHO das Datum, ab wann dieser gilt. Und ggf. ein Enddatum, ab wann er nicht mehr gilt (gegolten hat). Warum ist der Abschlag ein Interger-Wert? Es ist IMHO nicht kategorisch gegeben, dass der Abschlag eine glatte Zahl sein muss. AFAIK war die Mehrwehrsteuer bis dato immer ein Integerwert - muss natürlich nicht bis in allen Ewigkeit so bleiben. Der Zählerstand sollte IMHO mit Datum in einen eigene Tabelle und dann ggf. noch zu jedem Eintrag ein Fremdschlüselbezug zum für diesen Wert gültigen Tarif.
Gruß, noisefloor
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Freitag 9. Februar 2024, 22:34
von Master_Shredder
Die `Tarif.__init__()` ist überflüssig. Die Klasse erbt bereits eine sinvolle `__init__()`.
Ah ok. Danke @__blackjack__
@die Frage warum ab und an etwas fehlt.
Ich weiß, dass ab und etwas fehlt oder so vielleicht nicht ganz korrekt ist.
Erstens kann ich mir nicht denken wie das Programm letzten Endes aussieht, jedenfalls nicht so, dass ich nach meiner Erfahrung weiß, dass es gleich so sein sollte.
Und Zweitens will ich mir nicht einen Haufen Baustellen reinbauen die dann auch nie fertig werden. Ja da fehlt auch noch der Versorger Name und und und.
Und ja will die Tabellen noch klein halten und es erst mal mit denen hinbekommen.
Ich arbeite ab und an mal daran, gerade mal etwas mehr. Dies ist ein Bastelobjekt auch noch für die Zukunft. Muss noch Erfahrung sammeln.
Warum ist der Abschlag ein Interger-Wert? Es ist IMHO nicht kategorisch gegeben
Ja, da gebe ich dir recht

guter Hinweis. Das war eigentlich weil ich immer Glatte Beträge zahle

.
@snaf
Warum dupliziert die Abrechnung nochmal die Hälfte der Tarif-Attribute?
Wie meinst du das? Wo dupliziere ich sie?
Nachwort: Lieber klein und fein, wie groß, Fett und totales Chaos.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Samstag 10. Februar 2024, 10:00
von noisefloor
Hallo,
die Denke ist IMHO nicht richtig, wenn es um das Tabellendesign geht. Klar kann man evtl. jetzt noch nicht wissen, wo das ganze in X Jahren steht. Aber wenn man jetzt schon Fehler macht, im Sinne von keine Relationen einbauen wo welche hin gehören (z.B. indem der Zählerstand nicht direkt in einen eigene Tabelle kommt), dann endet das ziemlich sicher im Chaos. Im Nachhinein größere Änderungen an der DB-Struktur zu machen kann nämlich mega-viel Aufwand sein, wenn man Daten auf X Tabellen neu aufteilen muss.
Gruß, noisefloor
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Samstag 10. Februar 2024, 10:51
von Sirius3
Beim Tabellen Design stellt man sich die Frage, welche Daten ich in welchen Einheiten bekomme.
Du hast einen Tarif, der hat ein Startdatum, ein Grundpreis und einen Arbeitspreis.
Dann hast du einen Zählerstand, der besteht aus einem Datum und einem Zählerstand.
Das reicht schon an Daten die du in einer Datenbank speichern musst; die Abrechnung ergibt sich alleine durch diese Informationen.
Der Rest ist eine Funktion "Abrechnung", mit dem Argumenten Startdatum und Enddatum. Aus der Zählerstand Tabelle kannst du dann den Verbrauch pro Tag im Abrechnungszeitraum ausrechnen. Aus der Tariftabelle kommt dann für jeden Tag ein Grundpreis und ein Arbeitspreis. Aus dem Verbrauch an jedem einzelnen Tag vergeben sich dann die Kosten für einen Tag. Die Summe ist dann der zu zahlende Betrag, und in Monate umgerechnet der neue Abschlag.
Damit hast du ein sehr einfaches Datenbankdesign. Neue Informationen bedeuten immer nur einen neuen Eintrag in einer Tabelle. Der Rest ergibt sich durch einfache Berechnung.
Re: Ist die Programmierung dieser Klasse in Ordnung?
Verfasst: Samstag 10. Februar 2024, 16:50
von snafu
Master_Shredder hat geschrieben: Freitag 9. Februar 2024, 22:34
@snafu
Warum dupliziert die Abrechnung nochmal die Hälfte der Tarif-Attribute?
Wie meinst du das? Wo dupliziere ich sie?
Duplizieren war vielleicht nicht ganz treffend. Ich meinte die Stelle, wo du einige Attribute vom Tarif an die Abrechnung übergibst. Da würde man besser das Tarif-Objekt als Ganzes übergeben und dann innerhalb der Abrechnung auf die benötigten Attribute zugreifen.