Seite 1 von 2

Objektorientierte Programmierung

Verfasst: Freitag 7. Oktober 2011, 09:10
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Freitag 7. Oktober 2011, 09:21
von 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.

Re: Objektorientierte Programmierung

Verfasst: Freitag 7. Oktober 2011, 09:24
von BlackJack
@mcdaniels: Statt Eigenschaft solltest Du besser Attribut sagen. Denn Eigenschaften gibt es *auch* → `property()`.

Re: Objektorientierte Programmierung

Verfasst: Freitag 7. Oktober 2011, 09:35
von mcdaniels
Danke! Es ist also quasi als eine Separierung zu sehen?

btw.

desktop = pc() erzeugt eine Instanz der Klasse ist das korrekt?

Re: Objektorientierte Programmierung

Verfasst: Freitag 7. Oktober 2011, 10:15
von Rekrul
@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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 10:40
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 10:57
von lunar
@mcdaniels: Sicher, Übung macht den Meister. Das gilt auch und gerade fürs Programmieren :)

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 11:06
von 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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 12:47
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 13:15
von /me
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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 13:47
von Rekrul
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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 13:59
von 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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 14:21
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 15:38
von pillmuncher
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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 16:06
von 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.

Re: Objektorientierte Programmierung

Verfasst: Montag 10. Oktober 2011, 18:40
von mcdaniels
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)

Re: Objektorientierte Programmierung

Verfasst: Dienstag 11. Oktober 2011, 10:15
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Dienstag 11. Oktober 2011, 14:08
von Rekrul
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.

Re: Objektorientierte Programmierung

Verfasst: Dienstag 11. Oktober 2011, 15:11
von mcdaniels
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

Re: Objektorientierte Programmierung

Verfasst: Dienstag 11. Oktober 2011, 20:15
von Hyperion
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.