Übergabe von Variablen in 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.
Chris71
User
Beiträge: 9
Registriert: Mittwoch 9. September 2015, 13:18

Hallo,

In meinen ersten Tests versuche ich mich an einer Klasse:

Code: Alles auswählen

#!/usr/bin/env python3

import time


class ausgabe():
    def __init__(self, anzahl):
        self.anzahl = anzahl
    
    print("Start!")
    
    def rechnen(self):
        while anzahl > 0:
            time.sleep(1)
            if anzahl == 5:
                print("Abbruch")
                break
            print(anzahl)
            anzahl = anzahl + 1
        else: 
            print("Fertig!")
            
ausgabe = ausgabe.rechnen(2)
Als Fehlermeldung kommt: UnboundLocalError: local variable 'anzahl' referenced before assignment
Leider verstehe ich die Fehlermeldugn nicht; Was will er mir sagen?
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@Chris71: im Klassenkörper sollte eigentlich nichts außer Definitionen stehen. Ein print gehört da nicht hin. Instanzattribute werden über "self" dereferenziert. Die while-Bedingung ist, wenn sie einmal erfüllt ist, immer erfüllt. Da wäre ein if besser.
BlackJack

@Chris71: Zumal das semantisch so überhaupt gar keine Klasse ist. Wenn man nur eine `__init__()` und *eine* weitere Methode hat, dann ist das üblicherweise ein Warnzeichen das man eine Funktion viel zu kompliziert schreibt.

Klassennamen werden konventionell mit einem grossen Anfangsbuchstaben geschrieben und in MixedCase falls der Name aus mehreren Worten besteht. So kann man am Namen die Klasse und exemplare davon leicht unterscheiden, auch wenn man den ”gleichen” Namen für beides verwendet. Beispiel: ``parrot = Parrot()``. Hier ist `parrot` ein Exemplar das aus der Klasse `Parrot` erstellt wurde. Du erstellst in Deinem Code überhaupt gar kein Exemplar von `ausgabe`.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Um es mal konkret zu machen.
Zeile 23 muesste so lauten:

Code: Alles auswählen

ausgabe = ausgabe(2).rechnen()
Und alle `anzahl` in `rechnen` muessen `self.anzahl` sein.

Aber um es noch mal zu betonen: Das ist keine Klasse.
garreth
User
Beiträge: 41
Registriert: Donnerstag 23. Oktober 2014, 12:04

Zudem solltest du dir Gedanken darüber machen was passiert wenn man deine Klasse mit einer Startzahl größer als fünf startet. Wenn Anfang und Ende bekannt sind solltest du lieber auf eine "for"-Schleife zurück greifen.

Code: Alles auswählen

#!/usr/bin/env python3


import time


def print_number(start):
    print("Start!")
    for i in range(start, 5):
        time.sleep(1)
        print(i)
    print("Fertig!")


if __name__ == '__main__':
    print_number(2)
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Code: Alles auswählen

import time

class Ausgabe:
    def __init__(self, anzahl):
        self.anzahl = anzahl
        print("Start!")
   
    def rechnen(self):
        while self.anzahl > 0:
            time.sleep(1)
            if self.anzahl == 5:
                print("Abbruch")
                break
            print(self.anzahl)
            self.anzahl += 1
        else:
            print("Fertig!")
           
a = Ausgabe(2)
a.rechnen()
a fool with a tool is still a fool, www.magben.de, YouTube
Chris71
User
Beiträge: 9
Registriert: Mittwoch 9. September 2015, 13:18

wow! Vielen Dank für die vielen und schnellen Antworten!

Zuerst, ich möchte mit dieser Übung für mich nur mir selbst Python beibringen, um selbst zu verstehen wie das funktioniert, daher ist in er Bsp-Klasse auch nur eine "def" dabei usw.

Wie ist das zu verstehen, dass in eine Klasse kein Print hineingehört? Also grundsätzlich nicht oder nur in meinem Bsp wobei dies ja nur für mich zum lernen sein soll. Besser return verwenden?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Uns ist schon klar, dass du damit lernen willst, wie Klassen funktionieren. Der Punkt ist nur, dass dazugehoert zu erkennen, wann Klassen noetig bzw hilfreich sind.

Das `print` an der Stelle wird ausgefuehrt, wenn `class` ausgefuehrt, d.h. erstellt, wird. Das ist der Fall, wenn das Modul das `ausgabe` enthaelt importiert wird.
Mit anderen Worten koenntest du das `print` auch vor die Klassendefinition schreiben und es aenderte sich nichts.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Die Technik die Du anwendest wird "Caveman Debugging" genannt, der Fehler wird mit print-statements totgeworfen. Eine bessere Möglichkeit zu sehen wie das Skript Zeile für Zeile abgearbeitet wird, ist ein Debugger. Mit einem guten Debugger in einer guten Entwicklungsumgebung kannst Du das Skript Zeile für Zeile laufen lassen und kannst dabei in alle Variablen hineinschauen.
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@MagBen: Wobei das in Python nicht unüblich ist mal kurz ein `print()` irgendwo einzufügen. Wenn das Projekt etwas grösser ist, dann stattdessen gleich `logging.debug()`. Oder das `q`-Modul für quick & dirty debugging.

Ein Debugger ist nicht immer ein guter Ersatz dafür weil man oft ja nicht Schritt für Schritt durchgehen will sondern nur an bestimmten Stellen sich bestimmte Werte anschauen möchte oder sehen möchte.
Chris71
User
Beiträge: 9
Registriert: Mittwoch 9. September 2015, 13:18

Dafür was ich mache gibts sogar einen Namen. :)
Stimmt ich mache dies um zu sehen wann und was es tut. Aber auch um später mal "sinnvolle" Funktionen dort einbauen zu können. Wenn ich das ganze mal besser kann möchte ich gerne per Webanwendung auf dem Raspberry über Pythonscripte etwas ausführen lassen. Ist es dafür sinnvoller Klassen mit Funktionen in eigenen Pythonscripten zu verwenden oder besser anders vorgehen?
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

MagBen hat geschrieben:"Caveman Debugging"
Ich sage Jehova:
Dave Beazley hat geschrieben:If you wanna do debugging, the only proper way to do it is with the print statement. Okay, well I just gonna say that upfront, you have to use the print statement, that's the only one and true way to debug.
Quelle: http://www.youtube.com/watch?&v=sPiWg5jSoZI&t=518
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Ich sage ja nicht, dass print-statements schlecht sind, ich meinte nur, ein Debugger ist besser. Wenn ein Debugger aber nicht möglich ist (Server-Anwendung, Multi-Threading, GUI-Event-Handling, ...) dann ist print oder logging oftmals die einzige Möglichkeit zu debuggen.

Ein Debugger muss auch nicht ständig im Einzelschritt Modus laufen. Das kann man über Breakpoints regeln.
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Chris71 hat geschrieben:ich möchte mit dieser Übung für mich nur mir selbst Python beibringen, um selbst zu verstehen wie das funktioniert
Klassen und OOP allgemein werden nicht als die besten Anfängerthemen angesehen. Wie neu bist du denn bei Python? Hast du schon woanders programmiert, oder lernst du Programmieren auch noch neu? Das ist ja unabhängig von der jeweils verwendeten Sprache.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
BlackJack

@MagBen: Die Haltepunkte muss man aber verwalten und mir geht es oft so das ich einen bestimmten Wert sehen möchte der in einer Schleife existiert oder in einer Funktion/Methode die mehrfach aufgerufen wird und ich will dabei aber nicht jedes mal in einen Haltepunkt laufen und den per Klick bestätigen müssen.

Insgesamt finde ich die Handhabung eines Debuggers deutlich umständlicher als `print()`, `q`, oder `logging` (sofern man letzteres sowieso schon für das Programm benutzt).

Oft wüsste ich auch gar nicht wo ich Haltepunkte setzen sollte, weil man in Python ja eher selten Konstrukte wie beispielsweise in C oder Pascal hat, wo eine Funktion aus zwei bis drei verschachtelten Schleifen und jeder Menge Index- und Hilfvariablen besteht und man das alles nur schwer im Kopf nachvollziehen kann. Und Fehler fliegen einem dort nicht als Ausnahmen mit einem Typ, einer Nachricht, und einem Traceback um die Ohren, sondern als kommentarloser Programmabsturz oder fehlerhafte oder korrupte Daten(strukturen). Das wird in Python üblicherweise durch sicherere, ausdrucksstärkere Sprachmittel geregelt und Funktionen die das gleiche machen wie die C/Pascal-Gegenstücke sind in der Regel deutlich kürzer.
BlackJack

@Chris71: Ob man Klassen verwendet hängt einzig und alleine davon ab ob Klassen für eine konkrete Aufgabe sinnvoll sind. Das gilt im Grunde auch für Aufgaben die über eine Webanwendung ausgeführt werden sollen. Also ob man das im Webserver direkt regelt, oder einen externen Prozess dafür aufruft, oder nebenher einen Daemon/Server dafür laufen hat mit dem der Webserver das kommuniziert. Ein Task-Server wie Celery käme eventuell auch in Betracht.
Chris71
User
Beiträge: 9
Registriert: Mittwoch 9. September 2015, 13:18

ich stelle mir mein Projekt so vor, dass die Webanwendung größtenteils "nur" die Oberfläche bildet und die Mess- und Regelarbeit inkl Daten in Datenbank schreiben dann die Webanwendung "beauftragt" und dann die Pythonycripte auf dem Raspberry ausführen. Kann man grob sagen für was Klassen sinnvoll sind und für welche Themen man eher dies oder das nimmt?
BlackJack

@Chris71: Wann Klassen sinnvoll sind hat einzig und allein damit zu tun ob es Sinn macht eine Klasse zu schreiben, also ob man Daten und Funktionen die darauf operieren sinnvoll zu einem Objekt zusammenfassen kann. Ob man einen Zustand und Operationen die ihn verändern in einer Klasse kapseln kann. Wenn man das gleiche ganz einfach mit einer Funktion ausdrücken kann, wie Dein Beispiel, dann hat die Klasse offensichtlich keinen Sinn.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Chris71: Ob man Klassen oder Funktionen nimmt, hängt nicht von irgendwelchen Themen ab.

Eine Funktion repräsentiert eine einzelne Berechnung, im allgemeinsten Sinne. Etwas, das der Rechner tun soll und das ich als logische Einheit betrachte. Funktionen sollten so programmiert werden, dass klar ist, ob sie den Zustand ihrer Laufzeitumgebung modifizieren. Eine Funktion, die etwas in eine Datei schreibt, sollte nicht check_something() oder read_the_other_thing() heißen, sondern eher write_the_stuff(). Der Name sollte im Imperativ stehen um die Funktion als Befehl kenntlich zu machen. Funktionen, die Werte berechnen, sollten keine Seiteneffekte haben, dh., sie sollten den Zustand ihrer Laufzeitumgebung nicht ändern. Der Name einer solchen Funktionen sollte klar machen, dass es sich um eine Abfrage handelt. In anderen (Nicht-Objekt-Orientierten) Sprachen als Python verwendet man gerne get_ als Präfix, also get_state(something), get_total_price(order), get_customer_address(customer_id). In Python dagegen würde man sich Klassen definieren. Diese modularisieren den Zustand über eine Menge von Daten. Die Klassen sollte man sich allerdings nicht als bloße Datentöpfchen vorstellen, in die man zusammengehörige Daten reinwirft, um sie dann von außen ab und zu umzurühren, zu vermehren oder zu vermindern. Sondern man sollte sich eine Klasse als Beschreibung von Objekten (den sog. Exemplaren oder Instanzen) vorstellen, wobei diese Objekte es sind, die sich in je irgendeinem Zusand befinden, den sie einkapseln. zB. en File-Objekt. Es hat die logischen Zustände geöffnet oder geschlossen. Ein File-Objekt kann sich zu jedem Zeitpunkt nur in genau einem dieser Zustände befinden, dh. nie zugleich geöffnet und geschlossen sein. Wenn es sich im geöffnet-Zustand befindet, kann man Daten lesen oder schreiben, je nachdem, ob man die Datei zum Lesen oder Schreiben geöffnet hat. Im geschlossen-Zustand kann man weder lesen noch schreiben. Als Klasse könnte das ungefähr so aussehen:

Code: Alles auswählen

class File:

    def __init__(self, name):
        self.name = name
        self._is_open = False
        self._mode = None

    def open(self, mode):
        if self._is_open:
            raise Exception('file already open')
        # <-- hier sollte das tatsächliche Öffnen der Datei stattfinden
        self._mode = mode
        self._is_open = True

    def close(self):
        if not self._is_open:
            raise Exception('file is not open')
        # <-- hier sollte das tatsächliche Schließen der Datei stattfinden
        self._mode = None
        self._is_open = False

    def read(self):
        if not self._is_open:
            raise Exception('file is not open')
        if not self._mode == "r":
            raise Exception('file was not opened in read mode')
        # <-- hier sollte das tatsächliche lesen aus der Datei stattfinden
        return ...

    def write(self, data):
        if not self._is_open:
            raise Exception('file is not open')
        if not self._mode == "w":
            raise Exception('file was not opened in write mode')
        # <-- hier sollte das tatsächliche Schreiben in die Datei stattfinden
Die tatsächliche Schnittstelle von Dateien ist natürlich etwas komplexer. Mit Schnittstelle meine ich die Gesamtheit aller auf Dateiobjekten definierten Methoden zusammen mit ihren Parametern. Jedes Objekt, das dieselbe Schnittstelle definiert, kann dann an jeder Stelle verwendet werden, wo ein Datei-Objekt erwartet wird. Hier zB.:

Code: Alles auswählen

def write_some_state_to_file(state, out_file):
    data = str(state)
    out_file.open()
    out_file.write(data)
    out_file.close()
Wenn ich jetzt diese Klasse schreiben würde:

Code: Alles auswählen

class PseudoFileThatWritesToScreen:

    def open(self, mode):
        pass

    def close(self):
        pass

    def write(self, data):
        print(data)
dann könnte ich folgendes machen:

Code: Alles auswählen

some_number = 123
screen_with_file_interface = PseudoFileThatWritesToScreen()
write_some_state_to_file(some_number, screen_with_file_interface)
Diese Austauschbarkeit der Implementiertung unter Beibehaltung der öffentlichen Schnittstelle nennt man Polymorphismus. Sie ist das zentrale Konzept der Objektorientierten Programmierung.

Was sollte man alles zur Klasse machen? Üblicherweise das, was man mittels Nomina referenziert. Also zB. Kunde, Artikel, Bestellung, Warenkorb, usw. Der Code könnte vielleicht so aussehen:

Code: Alles auswählen

from datetime import datetime
from uuid import uuid4

class Order:

    def __init__(self, customer_id, line_items):
        self._id = uuid4()
        self._customer_id = customer_id
        self._line_items = tuple(line_items)
        self._date_placed = datetime.now()

class Customer:

     def __init__(self, id, name, address):
        self._id = id
        self._name = name
        self._address = address
        self._cart = []
        self._orders = []

    def add_line_item_to_cart(self, article_id, amount):
        if amount < 1:
            raise Exception('cannot add zero or negative amount to cart.')
        self._cart.append([article_id, amount])

    def place_order(self):
        self._orders.append(Order(self._id, self._cart))
        self._cart = []

    def get_all_ordered_articles(self, db):
        article_ids = set()
        for order in self._orders:
            for article_id, amount in order.line_items:
                article_ids.add(article_id)
        articles = ...  # <-- get articles by id from db
        return articles

class Article:
    ...

...

customer = Customer(uuid4(), 'Joe Shmoe', 'Somewhere Street 123 in Sometown')
article = Article(...)
customer.add_line_item_to_cart(article.id, 3)
customer.place_order()
Aller Code ist ungetestet und dient nur zur Illustration.
In specifications, Murphy's Law supersedes Ohm's.
Chris71
User
Beiträge: 9
Registriert: Mittwoch 9. September 2015, 13:18

Vielen Dank für eure ausführlichen Antworten!
Super Forum!
Antworten