Objektorientierte Programmierung

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.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
ich kämpfe im Moment mit der objektorientierten Programmierung. Wobei es kämpfen richtig gut trifft. :-)

Ich habe folgendes "Programm" gebaut.

Code: Alles auswählen

class pc:
    lagerstand = 10
    def verkaufen(self, menge):
        pc.lagerstand -= menge
        print pc.lagerstand

desktop = pc()
Mal schauen, ob ich das korrekt erklärt bekomme:
Ich definiere eine Klasse pc.
Die Klasse pc hat die Eigenschaft lagerstand = 10
Weiters definiere ich die Methode verkaufen

Meine Probleme:
Mir ist nicht klar, warum man bei der Definition der Methode immer das self benötigt? Kann mir das eventuell bitte jemand erklären?
Statt pc.lagerstand -= menge könnte ich auch self.lagerstand -= menge schreiben. Das pc.lagerstand erscheint mir logisch. (Greife auf den Wert lagerstand von pc zu), was das self.lagerstand aussagt ist mir wiederum schleierhaft. Self bezieht sich in dem Fall ja auf die Methode verkaufen, oder? In verkaufen gibt es aber gar keinen lagerstand, also warum dann self.lagerstand?

Vielen Dank im Voraus!

LG
Daniel
Zuletzt geändert von mcdaniels am Freitag 7. Oktober 2011, 09:26, insgesamt 1-mal geändert.
lunar

@mcdaniels: Dazu ein Beispiel:

Code: Alles auswählen

>>> class PC(object):
...     lagerstand = 10
...     def verkaufen(self, menge):
...         PC.lagerstand -= menge
... 
>>> desktop = PC()
>>> server = PC()
>>> desktop.verkaufen(10)
>>> desktop.lagerstand
0
>>> server.lagerstand
0
Dagegen:

Code: Alles auswählen

>>> class PC(object):
...     def __init__(self):
...         self.lagerstand = 10
...     def verkaufen(self, menge):
...         self.lagerstand -= menge
... 
>>> desktop = PC()
>>> server = PC()
>>> desktop.verkaufen(10)
>>> desktop.lagerstand
0
>>> server.lagerstand
10
Du wirfst Klassen und Exemplare von Klassen durcheinander. Die Zuweisung an "PC.lagerstand" ändert das Klassenattribut "lagerstand", welches alle Klassen betrifft. Im Gegensatz dazu beziehen sich Exemplarattribute immer nur auf ein einziges Exemplar einer Klasse. Diese Exemplarattribute kann man von außen über eine Zuweisung setzen (z.B. "desktop.lagerstand = 50"). Um Exemplarattribute einer Klasse innerhalb der Klasse selbst zu setzen, gibt es dann "self", welches das aktuelle Exemplar bereitstellt.
BlackJack

@mcdaniels: Statt Eigenschaft solltest Du besser Attribut sagen. Denn Eigenschaften gibt es *auch* → `property()`.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Danke! Es ist also quasi als eine Separierung zu sehen?

btw.

desktop = pc() erzeugt eine Instanz der Klasse ist das korrekt?
Rekrul
User
Beiträge: 78
Registriert: Dienstag 7. Dezember 2010, 16:23

@mcdaniels:
mcdaniels hat geschrieben:desktop = pc() erzeugt eine Instanz der Klasse ist das korrekt?
Ja.


Dein eigentliches Problem hat lunar ja schon erklärt. Was dich aber von vornherein vielleicht etwas irritiert haben könnte: Du hast eine Klasse PC und benötigst von der du dann vielleicht nur eine Instanz. Besser ist es allerdings eine etwas allgemeinere Klasse zu machen. Beispiel:

Code: Alles auswählen

from itertools import count
            
class Artikel(object):
    #an die Klasse gebunden
    id_generator = count(0)
    gesamt_bestand = 0
    
    def __init__(self, artikel_typ, bestand):
        #an Instanzen der Klasse gebundene Attribute erzeugen
        self.bestand = bestand
        self.artikel_typ = artikel_typ
        
        #weise jedem Artikel eine eindeutige ID zu
        self.id = Article.id_generator.next()
        
        #an die Klasse gebundenes Attribut aktualisieren
        Artikel.gesamt_bestand += bestand

    def verkaufen(self, menge):
        self.bestand -= menge
        Artikel.gesamt_bestand -= menge

Nun kannst du Instanzen von unterschiedlichen Artikeln machen:

Code: Alles auswählen

>>> lager = {}
>>> lager['PC'] = Artikel('PC', 10)
>>> lager['Maus'] = Artikel('Maus', 100)
>>> lager['Monitor'] = Artikel('Monitor', 25)
>>>
>>> Artikel.gesamt_bestand
135
>>> #Greift ebenfalls auf das Klassenattribut zu
>>> lager['Maus'].gesamt_bestand
135
>>> lager['Maus'].bestand
100
>>> lager['Monitor'].verkaufen(3)
>>> #Das Klassenattribut wurde aktualisiert
>>> Artikel.gesamt_bestand
132
>>> lager['Monitor'].bestand
22

Hoffentlich hat dieses Beispiel ein wenig Licht in den "Objekt-Dschungel" gebracht.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
danke für die Beispiele. Das Thema ist ganz schön komplex. Habe grade wieder was versucht und festgestellt, dass ich es offenbar noch nicht verstanden hab. Grundsätzlich sind mir die Beispiele klar.

Wenn ich nun aber eine eigene Lösung entwickeln müsste, würde das ganz schön schwer werden. Ich denke aber, dass ich es nur durch versuchen, versuchen und nochmals versuchen auf die Reihe bringe, oder?

LG
Daniel
lunar

@mcdaniels: Sicher, Übung macht den Meister. Das gilt auch und gerade fürs Programmieren :)
BlackJack

@mcdaniels: Vielleicht an realen oder zumindest realeren Beispielen üben und nicht an irgend welchen total Künstlichen. Und lass Klassenattribute (ausser Methoden :-)) erst einmal weg. Die werden gar nicht so häufig benötigt und wenn dann meistens nur um Konstanten zu definieren, die zu der Klasse gehören. So etwas wie `Artikel.gesamt_bestand` ist IMHO ein schlechtes Beispiel, weil das in der Praxis so niemand macht, da es für alle Artikel dann nur *einen* Gesamtbestand geben kann. Was unnötig unflexibel ist. Das führen eines Gesamtbestandes gehört zu den Aufgaben eines Container-Objektes für `Artikel` und nicht zum `Artikel` selbst.

Klassen fassen Daten und Funktionen die darauf operieren zu einem Objekt zusammen. Das heisst man kann sich dem auch von einer Lösung mit Funktionen nähern. Wenn man mehrere Funktionen hat, welche die gleichen Argumente übergeben werden, und wenn diese Argumente zusammen den Zustand von einem ”Ding” beschreiben, dann kann das ein Hinweis darauf sein, dass man das mit einer Klasse modellieren kann oder vielleicht sogar sollte.

So als Übergang könntest Du Dir vielleicht vorstellen, dass die Daten, die den Funktionen übergeben werden in einem Wörterbuch zusammen gefasst werden, welches dann immer als erstes Argument übergeben wird. Dann ist der Schritt zu einer Klasse mit Methoden nicht mehr weit.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
folgendes hab ich mir jetzt mal -ohne Rücksicht auf Verluste - zusammen "gebastelt". Es werden noch nicht alle Fehler abgefangen!
Frage:
Muss man hier bei den Variablen, die innerhalb der Methoden verwendet werden, immer ein self. voranstellen? Ausnahme wäre, wenn die Variable direkt von der Methode entgegen genommen wird: Also zb. notebooks.erfassen(5). Dann könnte man die Variable in der Methode direkt ohne self ansprechen?

Code: Alles auswählen

# -*- coding: latin1 -*-
class artikel:

    def erfassen (self):
              
        try:
            self.lagerstand += int(raw_input('Einkauf Stückzahl: '))
        except AttributeError:
            self.lagerstand = int(raw_input ('Einkauf Stückzahl (Lager Bestand = 0!): '))
    
    def verkaufen(self):
        self.verkaufen = int (raw_input('Verkauf Stückzahl: '))
        self.lagerstand = self.lagerstand - self.verkaufen
        print 'Lagerstand: ', self.lagerstand

notebook = artikel()
desktop = artikel ()

while True:
    print ''
    wahl = raw_input ('== ERFASSEN == \n 1. Desktop     2. Notebook\n== VERKAUFEN\n 3. Desktop    4. Notebook\n Wahl: ')
    print ''
    if wahl == "1":
        desktop.erfassen()
    elif wahl == "2":
        notebook.erfassen()
    elif wahl == "3":
        desktop.verkaufen()
    elif wahl == "4":
        notebook.verkaufen()
    else:
        print 'Ende!'
        print 'Notebooks: ', notebook.lagerstand
        print 'Desktops: ', desktop.lagerstand
        break
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

mcdaniels hat geschrieben:Muss man hier bei den Variablen, die innerhalb der Methoden verwendet werden, immer ein self. voranstellen? Ausnahme wäre, wenn die Variable direkt von der Methode entgegen genommen wird: Also zb. notebooks.erfassen(5). Dann könnte man die Variable in der Methode direkt ohne self ansprechen?
Das hängt davon ab, was du erreichen möchtest. Wenn du ein Attribut des Objekts ändern willst, dann geht der Zugriff über self.

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self.bar = 1
    def change_value(self):
        self.bar = 2
    def try_to_change_value(self):
        bar = 3
foo = Foo()
print foo.bar
foo.change_value()
print foo.bar
foo.try_to_change_value()
print foo.bar
Probier das mal aus und schau dir das Ergebnis an.

Noch ein Hinweis, ehe sich unschöne Dinge einschleichen. Schau dir mal den Style Guide for Python Code an.
Rekrul
User
Beiträge: 78
Registriert: Dienstag 7. Dezember 2010, 16:23

Ich bin mir nicht ganz sicher, ob ich deine Frage verstanden habe, werde dennoch ein Versuch starten diese zu beantworten:

Nein, du musst nicht vor jede Variable ein self. voranstellen. Dies musst du nur dann machen, wenn du die Variable an deine Klasseninstanz binden möchtest.

In deinem Bespiel heißt dies, dass du die Verkaufszahl mit self.verkaufen als Attribut an dein Objekt bindest und somit fortan mit bspw. desktop.verkaufen adressieren kannst.

Wenn du dies nicht machst, dann ist dies zwar kein Fehler, aber dein Objekt 'vergisst' die letzte Verkaufszahl, da die Variable nach verlassen der Methode nicht mehr vorhanden ist (etwas fahrlässig ausgedrückt).

Kleines Besipiel:

Code: Alles auswählen

class Artikel:
    def __init__(self):
        self.lagerstand = 0
        
    def erfassen (self, lagerbestand):
        self.lagerstand += lagerbestand
   
    def verkaufen(self, anzahl):
        self.anzahl = anzahl
        self.lagerstand = self.lagerstand - self.anzahl

Code: Alles auswählen

>>> desktop = Artikel()
>>> desktop.erfassen(10)
>>> desktop.verkaufen(3)
>>> desktop.anzahl
3
Bindest du die Anzahl nicht an dein Objekt (entfernst also die erste Zeile aus der Methode verkaufen), dann passiert folgendes:

Code: Alles auswählen

>>> desktop = Artikel()
>>> desktop.erfassen(10)
>>> desktop.verkaufen(3)
>>> desktop.anzahl
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Artikel instance has no attribute 'anzahl'
Zwei Dinge sind mir noch aufgefallen an deiner Klasse.
1. Du solltest dir angewöhnen einen Konstruktor zu definieren (mittels __init__(self ,...)).

2. Du bindest an dein Objekt eine Methode 'verkaufen' die du beim Aufruf dieser Methode mit dem Attribut verkaufen überschreibst. Das heißt, dass du deine Methode nur ein mal aufrufen kannst. Probier es am besten selbst mal aus.
BlackJack

@mcdaniels: Ob da ein `self.` davor stehen muss oder nicht, hängt davon ab ob es ein Attribut auf dem Objekt sein soll oder nicht. Ich glaube Dir ist immer noch nicht ganz klar was `self` eigentlich ist. Das ist das Objekt auf dem die Methode aufgerufen wurde. Also in Deinem Beispiel ein konkretes Exemplar von `artikel`. Nachdem man Dein `verkaufen()` aufgerufen hat, existiert auf dem `artikel`-Exemplar das Attribut `verkaufen` — was da nichts zu suchen hat. Du könntest in Deiner Hauptschleife dann zum Beispiel ``print desktop.verkaufen`` schreiben und es würde eine Zahl ausgegeben. Das ist aber kein sinnvolles Attribut von einem Artikel sondern nur ein Wert der lokal während des Aufrufs von `verkaufen` eine Bedeutung hatte.

Vielleicht noch einmal anders: Die Zeile ``desktop.erfassen()`` ist in diesem Fall äquivalent zu ``artikel.verkaufen(desktop)``. Nur dass `artikel` nicht fest kodiert ist, sondern zur Laufzeit von Python erfragt wird. Also so etwas wie ``type(desktop).verkaufen(desktop)``. Das Objekt, dass an den Namen `desktop` gebunden ist, wird als erstes Argument an die (ungebundene) Methode auf dem Klassen-Objekt übergeben.

Attribute sollte man in aller Regel alle in einer `__init__()` bekannt machen, also an Werte binden. In anderen Methoden sollte man keine Attribute auf dem Objekt einführen. Das ist unübersichtlich und kann dazu führen, dass man die Methoden in einer bestimmten Reihenfolge aufrufen muss. In Deinem Beispiel kann man zum Beispiel nicht `verkaufen()` als erstes Ausführen. Die Methode sollte aber nicht mit einem `AttributeError` enden, oder den behandeln müssen, sondern einen `lagerbestand` von zum Beispiel 0 vorfinden wenn noch nichts mit `erfassen()` erfasst wurde. Nach dem ein Objekt erstellt wurde, also nach der `__init__()` sollte sich das Objekt in einem sinnvoll initialisierten Zustand befinden.

Ansonsten sollte in so einem Objekt keine Benutzerinteraktion erfolgen. Damit verbaut man sich die Möglichkeiten den Code wieder zu verwenden, zum Beispiel in einer GUI- oder Webanwendung. Und automatisiertes Testen wird ebenfalls schwieriger.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Immer wieder überrascht, wie schnell ihr hier auf Posts reagiert. :-) Vielen Dank dafür!

Also, im Moment macht sich grade etwas Ernüchterung breit. Ich weiß nun (wiedermal ;-) ), dass ich offenbar nichts weiß und ich würde lügen, wenn ich behaupte, dass ich da im Moment wirklich durch sehe...

War der Schritt zur objekt orientierten Programmierung doch ein Schritt, den ich zu früh getätigt habe? Mir kommt vor, ich stelle mich extrem doof an. Nun, ich werd versuchen, mir das einzutrichtern. Optimal wäre es, wenn es "Klick" macht ;-)

@Rekrul:
Du bindest an dein Objekt eine Methode 'verkaufen' die du beim Aufruf dieser Methode mit dem Attribut verkaufen überschreibst.
Korrekt, ich kann es nur 1x eingeben. Allerdings ist mir nicht klar, weshalb das so ist...

LG
Daniel
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

mcdaniels hat geschrieben:War der Schritt zur objekt orientierten Programmierung doch ein Schritt, den ich zu früh getätigt habe? Mir kommt vor, ich stelle mich extrem doof an. Nun, ich werd versuchen, mir das einzutrichtern. Optimal wäre es, wenn es "Klick" macht ;-)
Vielleicht klickt es ja hierbei: Re: OOP, Klassen, Unterklassen, Vererbung.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@mcdaniels: So ein `artikel`-Exemplar hat ein Attribut mit dem Namen `verkaufen`. Und der Wert davon ist die Methode. Das Exemplar wird dieser Methode als erstes Argument übergeben und an den Namen `self` gebunden. Und dann bindest Du das Attribut an eine Zahl. Und damit kann es ja schlecht immer noch an die Methode gebunden sein.

Technisch ist es noch etwas anders: Das Exemplar hat anfänglich eigentlich kein Attribut `verkaufen`. Wenn man von einem Exemplar ein Attribut abfragt, was es dort nicht gibt, wird aber nicht sofort ein `AttributeError` ausgelöst, sondern in der Klasse nachgeschaut, ob die ein entsprechendes Attribut hat. Beim ersten Aufruf wird das Attribut also zu dieser Methode aufgelöst. Nach dem Aufruf hat das Exemplar dann aber ein `verkaufen`-Attribut, das an die eingegebene Zahl gebunden ist.

Hier mal eine kleine Sitzung mit einem Deiner `artikel`-Exemplare:

Code: Alles auswählen

In [173]: a.verkaufen
Out[173]: <bound method artikel.verkaufen of <__main__.artikel instance at 0xa3385ac>>

In [174]: a.verkaufen()
Verkauf Stückzahl: 5
Lagerstand:  5

In [175]: a.verkaufen
Out[175]: 5

In [176]: a.erfassen
Out[176]: <bound method artikel.erfassen of <__main__.artikel instance at 0xa3385ac>>

In [177]: a.erfassen = 42

In [178]: a.erfassen()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/bj/<ipython console> in <module>()

TypeError: 'int' object is not callable
Ist Dir klar warum man nach [177] bei ``a.erfassen`` die Ausnahme bekommt? Falls ja, müsstest Du eigentlich auch verstehen warum nach dem ersten Aufruf von `verkaufen()` vergleichbares passiert.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Guten Abend!

Code: Alles auswählen

def verkaufen(self):
   self.verkaufen = int (raw_input('Verkauf Stückzahl: '))
Ich überschreibe quasi also meine methode mit einem Integer und dieser Integer ist dann nicht aufrufbar (das ist mir klar)
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo!
Wieder modifiziert:

Code: Alles auswählen

class artikel:
    def __init__(self):
        self.lagerstand = 0

    def erfassen (self, menge):
        self.lagerstand = self.lagerstand + menge
    
    def verkaufen(self, menge):
        self.lagerstand = self.lagerstand - menge
        print 'Lagerstand: ', self.lagerstand

notebook = artikel()
desktop = artikel ()

while True:
    menge = int(raw_input('Menge einkaufen: '))
    desktop.erfassen(menge)
Die while-Schleife soll einfach nur ermöglichen, Mengen einzugeben - testhalber. Ist die Verwendung von Klassen / Methoden so korrekt?

Betreffend __init__: Ist das __init__ also bei Klassen generell so zu handhaben? D.h. Jedes Attribut der Klasse ist mit __init__ zu initialisieren? Was wäre, wenn ich kein Attribut lagerstand = 0 angeben würde? Dann gäbe es auch kein __init__. Somit wäre die Klasse dann also nicht korrekt initialisiert und würde keinen sinnvollen Startwert erhalten?

LG
Daniel
Rekrul
User
Beiträge: 78
Registriert: Dienstag 7. Dezember 2010, 16:23

Na, sieht schon besser aus. Jetzt heißt es üben, üben, üben. Überlege / Suche dir am am besten Bespiele, überlege wie eine sinnvolle Klasseneinteilung aussehen könnte, wie diese in Beziehung stehen, welche Attribute sie besitzen und was für Operationen auf ihnen ausgeführt werden sollen.

Nach und nach kannst du dann weitere Aspekte der OOP einfließen lassen (z. B. Vererbung, Eigenschaften ;-), etc.). Viel Erfolg noch.

Betreffend __init__: Wenn du keinen Anfangszustand herstellen musst, dann musst du auch kein __init__(self, ...) definieren. Allerdings fällt mir gerade kein sinnvolles Beispiel für so etwas ein.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo rekrul!
Danke, allerdings war das hart erkämpft ;-).

Der Konstruktor __init__ bezieht sich in dem Fall auf die Klasse artikel?

Sollte man JEDER Klasse so einen Konstruktor "spendieren"?

LG
Daniel
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mcdaniels hat geschrieben: Der Konstruktor __init__ bezieht sich in dem Fall auf die Klasse artikel?

Sollte man JEDER Klasse so einen Konstruktor "spendieren"?
Genau genommen ist das kein Konstruktor ;-) (Das wäre `.__new__()`). Natürlich ist das aber eine Methode Deiner Klasse `artikel`.

Es mag (sinnvolle) Klassen geben, bei denen man auf eine `__init__`-Methode verzichten kann - aber sobald Du ein Instanzattribut deklarieren willst, solltest Du darauf nicht verzichten.

Der Name ist übrigens nicht PEP8 konform - das wäre `Artikel`; CamelCase halt. Zudem sollten Klassen in Python 2.x von `object` erben - k.A. ob Dir das schon gesagt wurde.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten