Forum: PC-Programmierung Python: Problem mit Objekt-Variable


von Asterix (Gast)


Lesenswert?

Hallo,

ich bin mittlerweile am Verzweifeln: ich möchte in einer for-Schleife in 
einem Python-Programm einzelne Bits eines Bildes manipulieren:
1
import numpy as np
2
import cv2
3
import os
4
bildx = 47
5
bildy = 47
6
class Spalte0:
7
    def __init__(self):
8
        self.original = np.zeros([bildy, bildx, 1], dtype =  np.uint8)
9
        self.zaehler = 0
10
        self.namezaehl = 0
11
klass = Spalte0()
12
for filename in os.listdir('Fotos grau zugeschnitten\\'):
13
    img = cv2.imread(os.path.join('Fotos grau zugeschnitten\\',filename),0)
14
    klass.original = cv2.resize(img,(47,47))
15
    bld = klass.original
16
17
    for i in range(bildy):
18
        klass.original[klass.zaehler][klass.zaehler] = 150
19
    klass.zaehler +=1
20
    print(klass.zaehler)
21
        
22
cv2.imwrite('Radontransform\\radontest'+str(klass.namezaehl)+'.png',klass.original)
23
print('!')
Mein Problem sitzt in Zeile 17; wenn ich als Index von klass.original 
konkrete Zahlen angebe, oder auch die Variable i, die in der 
for-Schleife inkrementiert wird, funktioniert alles.
Auch klass.zaehler kann ich problemlos auslesen. Wenn ich aber in der 
for-Schleife die Objektvariable klass.zahler so nutze, wie hier 
angegeben, ändert sich nichts an dem Bild, ich bekomme aber auch keine 
Fehlermeldung. Woran kann das liegen? Ich habe schon sehr viel probiert, 
aber jeder Teil des Programmes funktioniert so, wie er soll, nur an 
dieser Stelle ist es, als würde ein Geist sein Unwesen treiben...

von Sascha W. (sascha-w)


Lesenswert?

viel kann sich ja nicht ändern, Zeile 17 wird 47 mal aufgerufen, da sich 
dabei in 17 nichts ändert würde wohl nur 1 Pixel gesetzt. Scheint keinen 
Sinn zu ergeben.

Sascha

von Asterix (Gast)


Lesenswert?

Hallo Sascha, die Variable klass.zaehler wird ja nach der for-Schleife 
um den Wert 1 erhöht, und da 47 Dateien in dem Verzeichnis sind, 
geschieht dies genau so oft. Das klappt auch, das sehe ich dann, wenn 
ich mir die Variable ausgeben lasse. Eigentlich sollte also eine 
Diagonale auf dem Bild erscheinen, aber das funktioniert eben nicht.

von Heiner (Gast)


Lesenswert?

Asterix schrieb:
> Eigentlich sollte also eine
> Diagonale auf dem Bild erscheinen

Sascha hat Recht: Du setzt in der ersten Datei 47mal den Punkt 0/0, in 
der zweiten Datei 47mal den Punkt 1/1 usw.

Jetzt kommt aber der entscheidende Fehler: Du schreibst nur das Ergebnis 
des letzten Schleifendurchgangs. Zeile 21 wird nur ein einziges mal 
ausgeführt.

Dieses ganze Chaos wäre viel leichter erkennbar, wenn du vernünftige 
Variablenbezeichner wählen und diese Nonsense-Klasse rausschmeißen 
würdest.

von Sascha W. (sascha-w)


Lesenswert?

Für was ist dann die Schleife in 16 überhaupt da?
Wie soll da eine Diagonale entstehen wenn nach dem Laden eines Bildes 
ein Pixel gesetzt wird?
Dann liest du nacheinander 47 Bilder in die selbe Variable und 
speicherst am Ende nur das letzte wieder ab.
Vielleicht beschreibst du mal was das werden soll.

Sascha

von Asterix (Gast)


Lesenswert?

Um den eigentlichen Sinn des Programmes einmal zu skizzieren:
Ich möchte aus einer Menge von 47 Bildern jeweils die x. Zeile nehmen, 
die die x. Spalte des Ergebnisses ergibt.
Also Ergebnis Spalte 1 = Bild 1 Zeile 1.
Ergebnis Spalte 2 = Bild 2 Zeile 1.
Ergebnis Spalte 3 = Bild 3 Zeile 1.
Das gleiche für die weiteren Zeilen der Ausgangsbilder.
Die Funktion soll also stark vereinfacht das machen:
1
for i in range(47):
2
 for p in range(47):
3
  ergebnis[i][p] = ausgangsbild_nr_p[p][i]

von Heiner (Gast)


Lesenswert?

Asterix schrieb:
> Ich möchte aus einer Menge von 47 Bildern jeweils die x. Zeile nehmen,
> die die x. Spalte des Ergebnisses ergibt.

Dann ist außerdem noch Zeile 14 falsch, weil dort jeglicher 
"Fortschritt" überschrieben wird.

von Asterix (Gast)


Lesenswert?

So wie das Programm hier steht, erfüllt es natürlich nicht diesen Zweck, 
aber es geht mir darum, dass in der entsprechenden Zeile irgendetwas 
nicht so läuft, wie ich es erwarten würde.
Ich will zu diesem Testzweck deshalb auch vorerst gar nicht jedes Bild 
bearbeiten (die Ausgangsbilder werden ja ohnehin nicht dauerhaft 
bearbeitet und gespeichert), aber die Struktur des Programmes, 
insbesondere das Öffnen der Dateien, habe ich drin gelassen, weil es ja 
mit dem Fehler zu tun haben könnte.

von Karl (Gast)


Lesenswert?

Asterix schrieb:
> Eigentlich sollte also eine
> Diagonale auf dem Bild erscheinen, aber das funktioniert eben nicht.

Nein, du überschreibst bei jedem Schleifendurchlauf klass.original mit
1
klass.original = cv2.resize(img,(47,47))

von Asterix (Gast)


Lesenswert?

Zunächst einmal Danke für eure Antworten!
Karl schrieb:
> Nein, du überschreibst bei jedem Schleifendurchlauf klass.original
> mitklass.original = cv2.resize(img,(47,47))

Stimmt, das ist wirklich Quatsch.
Zunächst dazu, weshalb ich die Klasse erstellt habe: ich habe Probleme, 
globale Variablen aus Funktionen heraus zu verändern, irgendwie bekomme 
ich das auch mit global oder nonlocal nicht hin, und habe gelesen, dass 
man für solche Fälle eine Klasse erstellen soll, deren Variablen man aus 
Funktionen heraus verändern kann. Wenn das doch anders geht, schmeiße 
ich die Klasse sofort raus!
Ich habe jetzt das Programm noch einmal bearbeitet, sodass ich erwarten 
würde, dass es den gewünschten Zweck erfüllt.
So sieht es aus (abzüglich der Definition der Klasse und der imports):
1
for p in range(47):
2
    for filename in os.listdir('Fotos grau zugeschnitten\\'):
3
        img = cv2.imread(os.path.join('Fotos grau zugeschnitten\\',filename),0)
4
        img = cv2.resize(img,(47,47))
5
        for i in range(bildy):
6
            klass.ergebnis[i][p] = img[p][i]
7
        
8
    klass.zaehler +=1
Es liefert mir aber nur ein rotiertes Bild meines Originals.

von Asterix (Gast)


Lesenswert?

PS: Zeile nicht mitkopiert:
1
cv2.imwrite('Radontransform\\radontest'+str(klass.zaehler)+'.png',klass.ergebnis)

von Sheeva P. (sheevaplug)


Lesenswert?

Asterix schrieb:
> Zunächst einmal Danke für eure Antworten!
> Karl schrieb:
>> Nein, du überschreibst bei jedem Schleifendurchlauf klass.original
>> mitklass.original = cv2.resize(img,(47,47))
>
> Stimmt, das ist wirklich Quatsch.
> Zunächst dazu, weshalb ich die Klasse erstellt habe: ich habe Probleme,
> globale Variablen aus Funktionen heraus zu verändern, irgendwie bekomme
> ich das auch mit global oder nonlocal nicht hin, und habe gelesen, dass
> man für solche Fälle eine Klasse erstellen soll, deren Variablen man aus
> Funktionen heraus verändern kann. Wenn das doch anders geht, schmeiße
> ich die Klasse sofort raus!

Bitte sei mir nicht böse, aber Dein ganzer Code ist... merkwürdig, um 
das mal ganz vorsichtig zu sagen... und wenn Du die Schlüsselworte 
"global", "nonlocal" und die Verwendung von Klassen noch nicht 
verstanden hast und nur deswegen Klassen benutzt, obendrein in einer... 
sehr seltsamen Art und Weise... die Du am Liebsten aber gleich wieder 
"rausschmeißen" würdest... Bitte, es ist nicht böse gemeint, aber ich 
habe bislang nicht das Gefühl, daß Du eine ansatzweise Ahnung hast, was 
Du da tust.

Das fängt schon bei den einfachsten Einfachheiten an, zum Beispiel 
os.path.join. Da benutzt Du zum Zusammenfügen von Pfadelementen aus 
welchen Gründen auch immer den betriebssystemspezifischen Pfadtrenner in 
Deinen Pfadelementen -- in Deinem Falle, der Du offensichtlich mit 
Windows unterwegs bist, ist das der Backslash '\', den Du obendrein 
maskieren mußt ("\\"), weil er das übliche Maskierungszeichen ist. Den 
kannst Du einfach weglassen, denn os.path.join nimmt ein Array von str- 
oder bytes- Argumenten, die dann mit os.sep konkateniert werden.

Und dann diese Klasse "Spalte0"... bitte tu' Dir selbst einen Gefallen 
und schau' mal, wie Objektorientierung funktioniert. Klassen sind in 
keiner Programmiersprache der Welt ein Ersatz oder irgendwas ähnliches 
für globale Variablen -- und so, wie Dein Code aussieht, würdest Du Dir 
auch mit globalen Variablen äußerst erfolgreich ins eigene Knie 
schießen.

Laß' die Klasse doch machen, was eine Klasse tun soll: übergibt ihr den 
Pfad zu der Datei, der Konstruktor kann sie dann selbständig einlesen 
und eine Methode -- die natürlich auch von Konstruktor ausgeführt werden 
kann -- macht dann das komische Zeugs, das Du mit Deinen Grafiken 
veranstalten willst.

Wie dem auch sei: ich empfehle Dir wärmstens ein gutes Buch über Python 
und eines über Objektorientierung. Viel Glück und Erfolg! ;-)

von Asterix (Gast)


Lesenswert?

Danke für die ausführliche Antwort, Sheeva.
Du hast völlig Recht damit, dass ich Python nicht so gut kann, wie ich 
es gerne würde. Vor allem die Objektorientierung finde ich schwierig, 
denn bis jetzt habe ich nur 8-bit Atmel uC in C programmiert, und 
Objektorientierung ist da für mich ein totaler Paradigmenwechsel.
Ich werde mir ein Buch über Python bestellen, um genau solche Sachen zu 
lernen.
Der Grund, weshalb ich mir dieses Programm vorgenommen habe ist der, 
dass ich ungeduldig bin, einen Simulator eines CT-Scanners zu bauen, der 
rein optisch funktioniert. Die "Rohdaten" eines Prototypen dazu habe ich 
bereits, und jetzt wollte ich unbedingt die Bilder zu entsprechenden 
Sinogrammen zusammensetzen, sodass ich die Rückprojektion ausprobieren 
kann. Aber hier hat sich eben meine Ungeduld gerächt :(
Was das Programm selber angeht: Ich dachte, ich hätte zumindest die 
Syntax und Grundregeln zu Klassen verstanden, und hatte auch in anderen 
Programmen, in denen ich zum Beispiel mit opencv gearbeitet habe, kein 
Problem damit, Objekte zu erstellen und mit den Methoden zu bearbeiten. 
Und bei dem Programm, so wie ich es hier reingestellt habe, weiß ich 
natürlich, dass es keinen Sinn ergibt. Es ging mir eigentlich nur darum, 
weshalb es nicht genau das macht, was ich von ihm erwarten würde.
Ich finde es grundsätzlich schwierig, autodidaktisch eine 
Programmiersprache zu lernen, denn vermeintlich kleine Probleme mit 
eigentlich einfacher Antwort lassen sich ohne kundigen Ansprechpartner 
sehr schwer lösen.

von Micha (Gast)


Lesenswert?

Asterix schrieb:
> Ich möchte aus einer Menge von 47 Bildern jeweils die x. Zeile nehmen,
> die die x. Spalte des Ergebnisses ergibt.

Asterix schrieb:
1
for p in range(47):
2
    for filename in os.listdir('Fotos grau zugeschnitten\\'):
3
        img = cv2.imread(os.path.join('Fotos grau zugeschnitten\\',filename),0)
4
        img = cv2.resize(img,(47,47))
5
        for i in range(bildy):
6
            klass.ergebnis[i][p] = img[p][i]
7
8
    klass.zaehler +=1

Ersetze mal die beiden ersten Zeilen durch
1
     for p, filename in enumerate(os.listdir('Fotos grau zugeschnitten\\')):

von Micha (Gast)


Lesenswert?

Die Einrückung stimmt dann natürlich nicht mehr und
klass.zaehler +=1
muss wahrscheinlich um eine Ebene tiefer sitzen.

von Asterix (Gast)


Lesenswert?

Micha schrieb:
> Ersetze mal die beiden ersten Zeilen durch     for p, filename in
> enumerate(os.listdir('Fotos grau zugeschnitten\\')):

Hallo Micha, das werde ich ausprobieren, sobald ich wieder zuhause bin.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

Asterix schrieb:
> Danke für die ausführliche Antwort, Sheeva.

Nichts zu danken -- dafür ist so ein Forum doch da, oder? ;-)

> Du hast völlig Recht damit, dass ich Python nicht so gut kann, wie ich
> es gerne würde. Vor allem die Objektorientierung finde ich schwierig,
> denn bis jetzt habe ich nur 8-bit Atmel uC in C programmiert, und
> Objektorientierung ist da für mich ein totaler Paradigmenwechsel.

Damit hast Du doch hervorragende Voraussetzungen. Zunächst einmal ist so 
eine Klasse im Prinzip so etwas Ähnliches wie ein "typedef struct" in C, 
also eine Sammlung von zusammengehörigen Daten. Nehmen wir an, wir 
wollen eine C-Struktur und Funktionen für einen Personennamen schreiben, 
dann ist das vermutlich so etwas wie:
1
typedef struct {
2
    char* vorname;
3
    char* nachname;
4
} name;
5
6
name* createName(char* vorname, char* nachname);
7
void printName(name* n);
8
void freeName(name* n);

So weit, so... mittelgut. Denn Dein Kollege hat gerade Code für 
Maschinennamen entwickelt, und... seine Funktionen heißen genau wie 
Deine! Deswegen benutzen viele C-Entwickler Präfixe für Funktionsnamen, 
um deren Eindeutigkeit sicherzustellen.

Objektorientierte Programmierung hingegen löst dieses Grundproblem recht 
einfach, indem es die Funktionen nicht im globalen Namensraum erzeugt, 
sondern sie direkt an die Datenstruktur bindet, die sie bearbeiten -- 
und das löst gleich eine Reihe von Problemen, weit über die 
Namenskollisionen hinaus.

Dann ist Dir bei der Lektüre des Code oben sicherlich aufgefallen, daß 
es dort drei Funktionen gibt, von denen eine, "createName()", eine 
Instanz vom Typ "name" erzeugt  und allokiert und einen Zeiger ("name*") 
darauf zurückgibt, während die anderen beiden jeweils einen Zeiger auf 
einen "name" als ersten Parameter haben. Das macht im Prinzip jede (mit 
bekannte) Objektorientierung so, mal implizit wie in C++ mit einem 
eigenen Schlüsselwort ("this"), mal explizit wie in Python mit dem 
Parameter "self" (der aber nicht "self heißen muß, am Rande bemerkt, das 
ist nur eine sehr beliebte Konvention). Nicht zuletzt deswegen heißen 
die Funktionen, die an eine Klasse gebunden sind, in der OOP nicht mehr 
"Funktion", sondern "Methode".

Nun, die Funktion "createName()" ist natürlich etwas ganz Besonderes, 
und genauso (wenngleich nicht ganz so offensichtlich) auch die Funktion 
"freeName()". Warum ist das so? Genau: die Funktion "createName()" 
erzeugt eine Struktur vom Typ "name" und gibt einen Zeiger darauf 
zurück. Dafür gibt es in der Objektorientierung wieder ein nettes 
Feature; eine Methode, die eine Instanz einer Klasse erzeugt, nennt sich 
dort "Konstruktor", und eine Methode, die diese Instanz wieder zerstört, 
ist folgerichtig natürlich ein "Destruktor".

Wie das mit dem Konstruktor geht, machen verschiedene 
Programmiersprachen wieder etwas unterschiedlich. C++ zum Beispiel hat 
eine (oder mehrere) Methode(n), die genauso heißen wie die Klasse, in 
der sie deklariert sind, die Destruktoren heißen genauso und bekommen 
das Präfix "~" vorangestellt, während Python hingegen einen(!) 
reservierten Methodennamen namens "__init__()" benutzt. Achtung: eine 
Klasse in C++ kann mehrere Konstruktoren haben, die sich in der Anzahl 
und / oder den Datentypen ihrer Methodensignatur (Parameter) 
unterscheiden, während Python es sehr leicht macht, fixe und optionale 
Parameter anzugeben und daher nur einen Methodennamen (nämlich 
"__init__()") braucht.

(Exkurs: im Grunde genommen ist das nicht ganz richtig, denn in Python 
gibt es noch einen "versteckten" Konstruktur namens "__new__()" und 
sogenannte "statische Methoden", die als "Frontend" für "__init__()" und 
/ oder "__new__()" verwendet werden können. Aber für hier sollen diese 
fortgeschrittenen bis esotherischen Features von Python keine Rolle 
spielen.)

Nun, schauen wir uns vielleicht zunächst eine Implementierung in C++ an:
1
struct Name {
2
    char* vorname;
3
    char* nachname;
4
5
    Name(char* vorname, char* nachname);
6
    void printName(void);
7
    ~Name(void);
8
};

Du siehst: unsere Funktion "createName()" ist jetzt zur 
Konstruktor-Methode "Name()" geworden, die Funktion "freeName()" zur 
Destruktor-Methode "~Name()", und dadurch, daß die Methoden unserer 
Klasse eh wissen, welche Daten sie bearbeiten sollen, kann der Name der 
Funktion "printName()" zur Methode "print()" gekürzt werden.

Diese Sache mit den Konstruktoren ist ziemlich cool, denn sie sind im 
Prinzip der einzige Weg, um Instanzen einer Klasse zu erzeugen. Anders 
gesagt: immer, wenn eine Instanz von einer Klasse erzeugt wird, wird ein 
Konstruktor aufgerufen, und wenn der Entwickler bei der Implementierung 
der Konstruktoren keinen Unsinn macht, gehören uninitialisierte 
Variablen damit der Vergangenheit an. ;-)

Okay, schauen wir uns nun eine Implementierung in Python3 an:
1
class Name:
2
    def __init__(self, vorname, nachname):
3
        self.vorname = vorname
4
        self.nachname = nachname
5
6
    #def __del__(self):
7
    #    del self.vorname
8
    #    del self.nachname
9
        
10
    def print(self):
11
        print("%s %s"%(self.vorname, self.nachname))

Wir sehen den Konstruktor "__init__()", den Destruktor "__del__()" und 
die Methode "print()", wie zuvor. Den Destruktor "__del__()" können wir 
problemlos weglassen, daher habe ich ihn auskommentiert, denn der 
Python-Interpreter ist (genau wie der C++-Compiler) klug genug, um das 
selbst hinzubekommen. Ach so: das Beispiel klappt natürlich nur mit 
Python3, denn in Python2 war "print" noch ein Schlüsselwort und hätte 
dort zu einem Syntaxfehler geführt. Aber Python2 ist ohnehin deprecated 
und wird nicht mehr gepflegt, und wir wollen ja keinen alten Mist 
benutzen, oder? ;-)

Nun, wie dem auch sei... es ist nicht ganz richtig, aber das hier war 
und ist der wesentliche Kern der OOP: Datenstrukturen und die 
Funktionen, die sie bearbeiten, in konsistenten Einheiten 
zusammenzufassen. Damit lassen sich dann die Objekte unserer realen Welt 
abbilden; so kann ein Flugzeug die Methode "flieg()" haben, die bei 
einem Auto (bislang jedenfalls) eher sinnlos wäre, aber beide können die 
Methoden "beschleunige()" und "bremse()" haben.

Und, Achtung: mit Vererbung können Flugzeug und Auto sogar dieselben 
Methoden und Implementierungen benutzen -- und das Flugzeug sogar 
abhängig von seinem eigenen aktuellen Zustand und womöglich beide 
abhängig vom Wetter und anderen Parametern. Dieses Mal ging es mir aber 
nur darum, den Kern der ganzen Sache zu erklären, und wenn meine 
Ausführungen für Dich halbwegs verständlich waren, freue ich mich über 
eine Rückmeldung, gerne auch mit Fragen und Anregungen. Wenn ich es 
geschafft habe, Dir die grundsätzlichen Zusammenhänge ein bisschen näher 
zu bringen, schreibe ich natürlich sehr gerne auch noch etwas über 
Vererbung.

> Ich werde mir ein Buch über Python bestellen, um genau solche Sachen zu
> lernen.

Wenn Du... wirklich verstehen willst, wie Objektorientierung 
funktioniert, möchte ich Dir wärmstens das Buch "The Design and 
Evolution of C++" von Bjarne Stroustrup empfehlen, dem Entwickler der 
Programmiersprache C++. Ich hab' es vor über zwanzig Jahren gelesen, und 
es prägt mein Verständnis und mein Entwicklerleben bis heute, was ich 
dabei gelernt, erkannt und verstanden habe.

Ach, übrigens: wenn man sich ein bisschen auskennt und auf einige 
"teure" Features von C++ verzichtet (zum Beispiel Exceptions und 
virtuelle Methoden), dann kostet C++ auch auf winzigen AtTiny13 
überhaupt gar nichts, macht Deine Programme aber deutlich lesbarer, 
übersichtlicher, und Deinen Code besser wiederverwendbar, ohne daß es 
nur den geringsten Einfluß auf die Programmgröße oder -Performance 
hat... auch wenn die Auguren in diesem Forum Dir etwas anderes 
suggerieren wollen. ;-)

> Der Grund, weshalb ich mir dieses Programm vorgenommen habe ist der,
> dass ich ungeduldig bin, einen Simulator eines CT-Scanners zu bauen, der
> rein optisch funktioniert. Die "Rohdaten" eines Prototypen dazu habe ich
> bereits, und jetzt wollte ich unbedingt die Bilder zu entsprechenden
> Sinogrammen zusammensetzen, sodass ich die Rückprojektion ausprobieren
> kann. Aber hier hat sich eben meine Ungeduld gerächt :(

Naja, nur eine Anregung:
1
from argparse import ArgumentParser
2
import cv2
3
4
class Image:
5
    def __init__(self, path, size=47):
6
        self.path = path
7
        self.size = size
8
        self.original = cv2.imgread(path)
9
        self.resized = cv2.resize(self.original, (self.size, self.size))
10
        #[...]
11
    def show(self):
12
        return cv2.imshow(self.resized)
13
14
if __name__ == '__main__':
15
    parser = ArgumentParser(description='Mach Zeug mit Bildern')
16
    parser.add_argument('path', help='where my images are')
17
    args = parser.parse_args()
18
19
    images = list()
20
    for p in os.listdir(args.path):
21
        images.append(Image(p))

Am Ende hast Du eine Liste von Image-Instanzen, mit denen Du... genau. 
Ach so, übrigens: ich lese meine privaten E-Mails nicht so oft, wie ich 
sollte, aber wenn Du Fragen hast und ein bisschen Geduld aufbringen 
kannst: you're welcome.

Abschließend wünsche ich Dir erstmal viel Spaß und Erfolg mit Python und 
der OOP. Nur die Ruhe, das ist im Grunde gar nicht schwierig, erlernt 
sich aber wohl auch nicht an einem Tag -- und um ein guter OO-Entwickler 
zu werden, braucht es primär etwas Erfahrung und Herumspielerei. Aber 
schwierig ist das nicht, Entwickler sind bekanntlich faul (das nennt 
Larry Wall, der Erfinder von Perl, als eine der vier Kardinaltugenden 
von Entwicklern, jedenfalls der guten), und wir neigen dazu, uns die 
Arbeit so einfach wie möglich zu machen. ;-)

(edit) PS: Oh, ach so, ganz vergessen... es ist schön, mal wieder einen 
TO hier zu sehen, der Interesse an der Sache hat und konstruktiv 
mitarbeitet. Danke dafür! ;-)

: Bearbeitet durch User
von Asterix (Gast)


Lesenswert?

Sheeva P. schrieb:
> Nichts zu danken
-Einspruch, euer Ehren!

Ich bin gerade ziemlich weggehauen davon, wie viel Zeit du dir genommen 
hast, um mir die OOP zu erklären und den Rücken zu stärken! Allein die 
Mühe hat ein großes "Danke" verdient, zumal du mir tatsächlich damit 
sehr weitergeholfen hast! Vor allem, dass du erst das Problem der nicht 
objektorientierten Programmierung beschreibst, das dann mit der OOP 
gelöst wird, macht es sooo viel einfacher, den eigentlichen Sinn 
dahinter zu verstehen.

Tatsächlich glaube ich, dass ich jetzt die Verwendung von 'self' erst 
richtig verstanden habe; vorher ging für mich nicht richtig zusammen, 
dass der Variablenname als solcher nur Konvention ist, aber der Name des 
Objekts immer als erstes Argument der Klasse eingelesen wird... wenn ich 
jetzt darüber nachdenke, kommt es mir gar nicht so schwer vor, nur ist 
das im Rückblick ja immer so bei solchen Sachen ;)

Überhaupt habe ich jetzt eine viel konkretere Vorstellung davon, wie ein 
Objekt erstellt wird, und es scheint auf einmal viel greifbarer als 
bisher.
Den Buchtipp werde ich beherzigen!

Ich muss jetzt erst einmal das neu Verstandene etwas sacken lassen, 
bevor ich die ersten auftretenden Fragen in den Äther blöke; nur 
folgende Frage erlaube ich mir jetzt schon: Beim Erstellen eines Objekts 
einer Klasse sind ja die Argumente für alle Methoden des Objekts 
verfügbar (einschließlich 'self'). Wieso aber kann ich mit einer Methode 
eine Variable (in deinem Beispiel self.vorname = vorname) erstellen, und 
in einer anderen Methode, die auf der selben "Ebene" sitzt, auf diese 
Variable zugreifen; sollte self.vorname in der ersten Methode nicht 
lediglich eine lokale Variable sein, und in der Folge nicht mehr von 
anderen Methoden les-/beschreibbar?

Und wenn du noch etwas über Vererbung schreibst, freue ich mich sehr, 
wenngleich ich schon jetzt genug Gedankenfutter getankt habe, um eine 
Weile das Gelernte zu festigen.
Leider ist im Moment die Freizeit bei mir knapp bemessen und schon gut 
gefüllt, aber ich konnte deinen Beitrag einfach nicht bis morgen 
unkommentiert stehen lassen. Also noch einmal besten Dank!

von Asterix (Gast)


Lesenswert?

PS: Dein Vorschlag bezüglich meines ursprünglichen Problems wird 
natürlich ebenfalls bei nächster Gelegenheit (hoffentlich bald...) 
ausprobiert!

von Sheeva P. (sheevaplug)


Lesenswert?

Asterix schrieb:
> Sheeva P. schrieb:
>> Nichts zu danken
> -Einspruch, euer Ehren!
>
> Ich bin gerade ziemlich weggehauen davon, wie viel Zeit du dir genommen
> hast, um mir die OOP zu erklären und den Rücken zu stärken! Allein die
> Mühe hat ein großes "Danke" verdient, zumal du mir tatsächlich damit
> sehr weitergeholfen hast! Vor allem, dass du erst das Problem der nicht
> objektorientierten Programmierung beschreibst, das dann mit der OOP
> gelöst wird, macht es sooo viel einfacher, den eigentlichen Sinn
> dahinter zu verstehen.

Pssst! ;-) Ich freue mich sehr, daß es Dir nützt, dann hat es sich 
gelohnt!

> Tatsächlich glaube ich, dass ich jetzt die Verwendung von 'self' erst
> richtig verstanden habe; vorher ging für mich nicht richtig zusammen,
> dass der Variablenname als solcher nur Konvention ist [...]

Naja, die Konvention ist so weit verbreitet, daß mein Editor GNU Emacs 
das "self" sogar wie ein Schlüsselwort darstellt. Aber das hier
1
class Dings:
2
    def __init__(klaus): klaus.name = 'Sheeva'
3
    def bums(self): print('in Dings.bums(): name = "{}"'.format(self.name))
4
    def da(karl): print('in Dings.da(): name = "{}"'.format(karl.name))
5
6
if __name__ == '__main__':
7
    dings = Dings()
8
    dings.bums()
9
    dings.da()

ist vollkommen legaler Python-Code und läuft problemlos sowohl mit der 
veralteten Version 2 als auch mit der aktuellen Version 3. Aber wenn man 
so etwas in realem Programmcode schriebe, sollte man damit rechnen, daß 
ein wütender Mob mit Fackeln und Mistgabeln auftaucht und man am Ende 
geteert und gefedert wird... ;-)

> Den Buchtipp werde ich beherzigen!

Bitte bedenke: das ist schon ziemlich nerdiger Kram für Menschen, die 
wirklich wissen wollen, wie es funktioniert und wie es in C++ 
implementiert wurde...

> Ich muss jetzt erst einmal das neu Verstandene etwas sacken lassen,
> bevor ich die ersten auftretenden Fragen in den Äther blöke; nur
> folgende Frage erlaube ich mir jetzt schon: Beim Erstellen eines Objekts
> einer Klasse sind ja die Argumente für alle Methoden des Objekts
> verfügbar (einschließlich 'self'). Wieso aber kann ich mit einer Methode
> eine Variable (in deinem Beispiel self.vorname = vorname) erstellen, und
> in einer anderen Methode, die auf der selben "Ebene" sitzt, auf diese
> Variable zugreifen; sollte self.vorname in der ersten Methode nicht
> lediglich eine lokale Variable sein, und in der Folge nicht mehr von
> anderen Methoden les-/beschreibbar?
>
> Und wenn du noch etwas über Vererbung schreibst, freue ich mich sehr,
> wenngleich ich schon jetzt genug Gedankenfutter getankt habe, um eine
> Weile das Gelernte zu festigen.

Alles gut, nur keinen Streß -- aus meiner eigenen Erfahrung weiß ich, 
daß Neues erst einmal ein bisschen Zeit zum Sacken und womöglich zum 
Rumspielen braucht. Kommen wir zunächst zu Deiner Frage...

Also, die meisten (mir bekannten) Skriptsprachen kennen ein Feature, das 
nennt sich "Autovivikation". Variablen werden dabei nicht deklariert, 
sondern einfach durch eine Zuweisung erzeugt. Wo Du in C also
1
int i;
2
i = 0;

und damit die Variable i mit dem Datentyp Integer ("int") deklarierst 
und dann mit einer Zuweisung initialisierst, ist die Deklaration der 
Variablen -- also, Deinem Compiler zu sagen, daß Du Speicherplatz für 
eine Variable haben möchtest und diese den Datentyp Integer haben soll) 
in den meisten (mir bekannten) Skriptsprachen überflüssig, dort reicht 
eine einfache Zuweisung:
1
i = 0

Dabei unterscheiden sich die Skriptsprachen allerdings: während Perl und 
PHP für Variablen keinen Datentyp festlegen und also Code wie '"3" + 4;' 
die Zahl 7 ergibt, sind Ruby und Python tatsächlich stark typisiert -- 
und '"3" + 4' ergibt dort einen Fehler. Im Unterschied zu kompilierten 
Sprachen wie C, C++ oder Java ist der Typ einer Variablen in diesen 
Sprachen aber nicht an den Variablennamen, sondern an ihren Wert 
gebunden, so daß eine Variable, die gerade eben noch eine Ganzzahl war, 
durch eine einfache Zuweisung eine Fließkommazahl werden kann:
1
i = 0
2
i = 0.01

Okay, nach dieser Vorrede kommen wir jetzt auf Deine Frage zurück. Die 
Zuweisung "self.vorname = vorname" in dem Beispiel aus dem vorhierigen 
Beitrag erzeugt per Autovivikation eine Variable, die an die konkrete 
Instanz des Objektes gebunden ist -- also: nicht an die Klasse, sondern 
an die Instanz der Klasse.  Die Klasse ist dabei lediglich eine Art 
Vorschrift, wie ein Etwas aussehen soll (also in C eine struct), und die 
Instanz ist dann ein konkretes Etwas -- von dem es auch mehrere geben 
kann:
1
class Dings:
2
    def __init__(self, name):
3
        self.name = name
4
        
5
    def say(self):
6
        print('Der Moderator heißt "{}".'.format(self.name))
7
8
if __name__ == '__main__':
9
10
    # mit Zwischenvariable
11
    mods = list()
12
    for mod in ['Andreas', 'Rufus', 'Yalu']: m = Dings(mod); mods.append(m)
13
    for mod in mods: mod.say()
14
15
    # ohne Zwischenvariable
16
    mods = list()
17
    for mod in ['Andreas', 'Rufus', 'Yalu']:
18
        mods.append(Dings(mod))
19
    for mod in mods: mod.say()
20
21
    # list comprehension
22
    mods = [Dings(mod) for mod in ['Andreas', 'Rufus', 'Yalu']]
23
    for mod in mods: mod.say()

(Um den Code kurz zu halten, habe ich die Oneliner hier mal in eine 
Zeile gepackt, was absolut legaler Python-Code ist -- ja, auch die 
Verbindung mehrerer Anweisungen mit dem ";" -- aber normalerweise sollte 
man so etwas nicht machen. Man muß sich ja nicht immer an den Python 
Style Guide in PEP-8 [1] halten, aber... besser ist das, weil es den 
Code besser lesbar macht, und das ist in Python eines der wichtigsten 
Entwicklungsziele...)

(Dieser letzte Code zeigt schon ein "fortgeschrittenes" Feature von 
Python, die es meines Wissens von Lisp abgeschaut hat: die List 
Comprehension. Das wollte ich nur kurz gezeigt haben, hat aber im Grunde 
nichts mehr mit Objektorientierung zu tun.)

Nun, kommen wir zu Deiner Frage nach dem "self.vorname" zurück: damit 
wird eine neue Variable erzeugt, die an die Instanz der Klasse gebunden 
ist -- und bleibt. Wieder ein kleines Beispiel:
1
class Dings:
2
    def __init__(self, name):
3
        n = name
4
        self.name = name

Wenn von dieser Klasse eine Instanz erzeugt wird, dann ist "name" eine 
Variable, die dem Konstruktor __init__() übergeben werden muß (weswegen 
ihr Wert logischerweise auch außerhalb des Konstruktors bekannt sein 
muß, sonst könnte man ihm diesen Wert natürlich nicht übergeben), aber 
trotzdem ist der Name lokal, also nur in dem Konstruktor bekannt. "n" 
ist ebenfalls eine lokale Variable im Gültigkeitsbereich (Scope) des 
Konstruktors; "name" und "n" sind dem restlichen Programm nicht mehr 
bekannt, sobald die Konstruktor-Methode beendet ist. Dagegen ist 
"self.name" eine Variable, die beim Aufruf des Konstruktors erzeugt 
(Autovivikation) und an "self", also: an die konkrete Instanz der Klasse 
gebunden. und in der Instanz dieser Klasse gespeichert wird. Sie gehört 
durch das "self.<variablenname>" also zur Instanz der Klasse, und 
deswegen können alle Methoden der Klasse "Dings" darauf zugreifen. (Der 
Abschnitt ist vermutlich bis hierher der Wichtigste, wenn Du dazu noch 
Fragen hast, bitte scheu' Dich nicht, sie zu stellen.)

Nebenbei bemerkt, funktioniert das mit der Autovivikation allerdings 
nicht nur im Konstruktor, und das ist gerade bei Einsteigern und auch 
beim Testen und Debuggen eine sehr beliebte Fehlerquelle:
1
class Ding:
2
    def __init__(self, name): 
3
        self.name = name
4
5
    def setAge(self, age): 
6
        self.age = int(age)
7
8
    def say(self): 
9
        print('"%s" ist %d Jahre alt'%(self.name, self.age))

Schau Dir den Code bitte genau an, und wenn Du magst, spiel' ein 
bisschen damit herum. Was passiert, wenn Du die Methode "say()" 
aufrufst, bevor die Methode "setAge()" aufgerufen wurde? Und warum 
passiert das? Bitte probier' das mal aus -- es ist beim Erlernen einer 
Sprache auch immer gut, mal die Fehlermeldungen sehen und interpretieren 
zu lernen, Python-Tracebacks lesen sich übrigens von unten nach oben.

Dann gibt es in Python aber auch noch sogenannte Klassenvariablen, die 
dann zur Klasse gehören und nicht zur Instanz, und obendrein class- und 
staticmethogs, aber da wird es dann wirklich sehr fortgeschritten und 
ein bisschen... tricky, und würde deswegen hier zu weit führen... kommen 
wir also zur Vererbung. ;-)


Nun, die Vererbung bietet dem Entwickler die Möglichkeit, Klassen zu 
verändern, um sie beispielsweise um neue Methoden (und Variablen) zu 
erweitern. Dabei geht es im Kern um die Wiederverwendbarkeit und 
Erweiterung von vorhandenem Code. Weil es mir gerade als halbwegs 
brauchbares Beispiel erscheint, bei dem Fortbewegungsmitteln aus meinem 
vorigen Beitrag zu bleiben... machen wir das doch einfach.

Nehmen wir an, unser Chef hat ein Auto und möchte gerne ausrechnen, wie 
schnell er nach n Sekunden voller Beschleunigung ist. (Ja, das ginge 
auch mit einer einfachen Funktion, aber...) Weil unser Chef aber leider 
nicht Jutta Kleinschmidt ist, steht das Auto zu Anfang immer... ;-)
1
class Auto:
2
    def __init__(self, marke='Porsche', beschleunigung=0):
3
        self.marke = marke
4
        self.beschleunigung = beschleunigung
5
        self.geschwindigkeit = 0
6
7
    def beschleunige(self, zeit):
8
        self.geschwindigkeit += self.geschwindigkeit + (zeit * self.beschleunigung)
9
10
    def anzeige(self):
11
        return f'Der {self.marke} fährt jetzt {self.geschwindigkeit}'
12
13
if __name__ == '__main__':
14
    a = Auto(beschleunigung=8.0)
15
    print(a.anzeige())
16
    a.beschleunige(1)
17
    print(a.anzeige())
18
    a.beschleunige(1)
19
    print(a.anzeige())
20
    a.beschleunige(10)

So weit, so bekannt (mit Ausnahme der dritten Möglichkeit, Strings mit 
variablen Inhalten zu erzeugen: den f-Strings). Außerdem habe ich mir 
nun mal die Freiheit genommen, etwas zu tun, was eigentlich guter Stil 
ist: print()-Funktionen gehören eigentlich nicht in eine Methode, sowas 
kann man nur mal zum Debugging machen, im realen Leben würde man dazu 
aber lieber Pythons extrem leistungsfähiges und sehr flexibles Modul 
"logging" benutzen. Aber im Kern solltest Du halbwegs verstehen, was 
dieser Code da macht... spätestens wenn Du damit herumgespielt hast, 
wozu ich Dich an dieser Stelle allerwärmstens ermuntern möchte. ;-)

Nun... wie das nunmal so ist: wir haben unglaublich guten Python-Code 
entwickelt, und unser Chef konnte damit Aufträge auf der ganzen Welt 
akquirieren. Der hat nun aber ohnehin eine Pilotenlizenz und weiß daher, 
daß er die Kunden mit dem eigenen Flugzeug viel schneller und billiger 
abklappern kann, und legt sich deswegen eine alte Saab Jaktviggen  zu -- 
in der er, weil er das ja so gewohnt ist, gerne seine altbekannte 
Geschwindigkeitsanzeige haben will.

Hm, alles neu schreiben? I wo, nach Larry Wall (dem Erfinder von Perl) 
ist Faulheit bekanntlich eine Kardinaltugend guter Entwickler. Also 
erweitern wir einfach unsere Auto-Klasse zu einer Flugzeugklasse... und 
nein: im realen Leben würde man das nie so machen, aber ich wollte bei 
meinem Beispiel bleiben und kann daran ein paar ganz nette Features 
zeigen... ;-)
1
#!/usr/bin/env python3
2
from enum import Enum
3
4
5
class Auto:
6
    def __init__(self, marke='Porsche', accel=0):
7
        self.marke = marke
8
        self.accel = accel
9
        self.speed = 0
10
11
    def beschleunige(self, zeit):
12
        self.speed += self.speed + (zeit * self.accel)
13
14
    def anzeige(self):
15
        return f'Der {self.marke} hat jetzt {self.speed} m/s'
16
17
    def __str__(self):
18
        return f'Der {self.marke} hat jetzt {self.speed} m/s'
19
20
21
class Zustand(Enum):
22
    BODEN = 'fährt'
23
    LUFT = 'fliegt'
24
25
class RuebennaseError(Exception): pass
26
27
28
class Flieger(Auto):
29
    def __init__(self, marke='Saab', accel=0, flugaccel=0):
30
        super().__init__(marke, accel)
31
        self.flugaccel = flugaccel
32
        self.zustand = Zustand.BODEN
33
34
    def beschleunige(self, zeit):
35
        if self.zustand == Zustand.LUFT:
36
            self.speed += self.speed + (zeit * self.flugaccel)
37
        elif self.zustand == Zustand.BODEN:
38
            self.speed += self.speed + (zeit * self.accel)
39
        else:
40
            raise RuebennaseError('Du Hurrajeck!')
41
42
    def starte(self):
43
        self.zustand = Zustand.LUFT
44
        self.speed = 100.0
45
46
    def lande(self):
47
        self.zustand = Zustand.BODEN
48
        self.speed = 1.0
49
50
        
51
    
52
if __name__ == '__main__':
53
    a = Auto(accel=8.0)
54
    print(a.anzeige())
55
    a.beschleunige(1)
56
    print(a.anzeige())
57
    a.beschleunige(1)
58
    print(a.anzeige())
59
    a.beschleunige(10)
60
61
    f = Flieger(accel=3, flugaccel=25)
62
    f.beschleunige(1)
63
    print(f.anzeige())
64
    f.beschleunige(1)
65
    print(f.anzeige())
66
    f.starte()
67
    f.beschleunige(1)
68
    print(f.anzeige())
69
    f.beschleunige(1)
70
    print(f.anzeige())
71
    f.lande()
72
    f.beschleunige(1)
73
    print(f.anzeige())
74
    f.beschleunige(-4)
75
    print(f.anzeige())

Wow, das war viel Code -- nicht zuletzt auch wegen des etwas unglücklich 
gewählten Beispiels, bitte entschuldige das. Nun, unsere Auto-Klasse 
kennen wir schon, nur: was macht diese neue Methode "__str()__" da? Wenn 
Du genau hinschaust, siehst Du, daß sie genau dasselbe tut wie unsere 
bisherige Methode "Auto.anzeige()". Warum? Weil es eine sogenannte 
"Magic Method" ist, solche Methoden sind meines Wissens ein spezielles 
Feature von Python -- aber dazu später.

Naja, da ist jetzt schon ein bisschen Fortgeschrittenes dabei, aber... 
fangen wir einfach mal mit der Klasse "Flieger" an. Bei der Definition 
der Klassen siehst Du schon, daß sie von der Klasse "Auto" erbt, 
deswegen das "(Auto)" hinter dem Namen der Klasse. Was heißt das, sie 
'erbt'? Genau: sie übernimmt dadurch alle Methoden und Eigenschaften 
(Variablen) ihrer Elternklasse "Auto". Im Grunde ist der Flieger also 
erstmal genau dasselbe wie ein Auto, und hat nur einen anderen 
Klassennamen.

Jetzt hat die Klasse "Flieger" aber ein paar Methoden, die genauso 
heißen wie die Methodennamen von unserer Klasse "Auto", aber etwas 
anderes tun. Klar, ein Flieger ist halt kein Auto... also jedenfalls, 
wenn er fliegt, was die meisten Autos ja bekanntlich (noch) nicht so gut 
können ("Grizzy und die Lemminge" mal ausgenommen, natürlich). Das 
heißt: die "Flieger"-Klasse überschreibt die Methoden das Klasse "Auto" 
und ersetzt deren Funktionalität durch eine neue.

Schon beim Konstruktor siehst Du, daß die Fliegerklasse andere Argumente 
annehmen kann als die Auto-Klasse. (Und ganz nebenbei siehst Du auch, 
daß die Argumente der Konstruktoren Default-Werte haben, also: angegeben 
werden können, aber nicht müssen. Das ist ähnlich wie die Varargs unter 
C/C++...). Die Klasse "Flieger" kann also neben dem "accel"-Argument für 
die Beschleunigung am Boden zusätzlich auch noch ein 
"flugaccel"-Argument annehmen.

Nun sind wir aber -- wiedermal und immer wieder gerne -- faul. Müssen 
wir den Code des Konstruktors unserer Parent-Klasse "Auto" nochmal neu 
schreiben? Ach Quatsch, wir rufen den einfach auf. Das macht der Aufruf 
von super().__init__(), natürlich nur mit den Argumenten, die unsere 
Parent-Klasse kennt. Und danach müssen wir uns nur noch um die 
Initialisierung unserer Child-Klasse "Flieger" kümmern -- den Rest hat 
der Konstruktor der Parent-Klasse ja schon für uns erledigt. ;-)

Dann ist da in unserer Klasse "Flieger" auch noch die die Methode 
"beschleunige()". Da habe ich die "beschleunige()"-Methode unserer 
Elternklasse "Auto" einfach mal überschrieben, ohne eine Funktion der 
Elternklasse "Auto" aufzurufen. Eine Instanz von "Flieger" verhält sich 
also beim Aufruf der Methode "beschleunige" ganz anders als eine Instanz 
der Klasse "Auto", auch wenn sie -- abhängig davon, ob der Flieger in 
der Luft ist oder am Boden -- ganz verschiedene Dinge tut. Ansonsten hat 
unser "Flieger" noch weitere Methoden, weil ein Flugzeug eben kein Auto 
ist und deswegen starten und landen kann...

Aber jetzt kommen wir zum wesentlichen Kernpunkt der Vererbung: die 
Methoden "anzeige()" und "__str__()" übernimmt unsere Klasse "Flieger" 
völlig unverändert, genauso die Eigenschaften "marke" und 
"beschleunigung", die nur an den Konstruktor der Elternklasse übergeben 
und von den Methoden benutzt werden. Jetzt (oder morgen, möglicherweise) 
verstehst Du vielleicht auch, warum ich den Teil zur Autovivikation 
vorangestellt habe... ;-)

Mit diesem Wissen kannst Du jetzt auch sehen, daß ich an zwei weiteren 
Stellen was vererbe, bei der Definition der Klassen "Zustand" und 
"RuebennaseError". Nun, das Konzept von Enum kennst Du von C (auch wenn 
Python da wesentlich mehr kann), dabei geht es um die Festlegung von 
Konstanten. Beim "RuebennaseError" dagegen geht es lediglich darum, 
einen neuen Namen zu erzeugen... über Exceptions kann ich gerne 
weitersprechen, wenn Du Zeit und Lust hast, und das Bisherige (ich weiß, 
ein sehr dicker Brocken diesmal) halbwegs verinnerlichen konntest.

Bitte versteh' mich nicht falsch: ich halte Dich keinesfalls für dumm, 
dann würde ich mir die Mühe nicht machen, um ehrlich zu sein. Ich weiß 
sehr gut, daß das viel Neues und zum Teil womöglich auch etwas 
verstörend ist für jemanden, der sich bis dato nur mit prozeduraler 
Entwicklung beschäftigt hat; Deinen Weg bin ich selbst gegangen, es hat 
auch bei mir eine Weile gebraucht, und mein Vorteil ist nur, daß diese 
Sachen bei mir schon drölfzig Jahre her sind.

Du sagst es absolut richtig, und das ist auch meine Erfahrung als 
passionierter Segellehrer bei der Segel- und Motorbootschule Lord Nelson 
[2] -- was viel mehr hands-on ist als es Beiträge in einem Forum sein 
können -- man muß es ersteinmal sacken lassen. Ich möchte Dich an dieser 
Stelle auch ebenso wärmstens wie herzlich dazu ermuntern, einfach mal 
ein bisschen herumzuspielen. Python ist großartig und der einzige 
Skriptspracheninterpreter, bei dem ich es noch nie geschafft habe, ihn 
abstürzen zu lassen, obwohl schon eine Reihe C- und C++-Libraries dafür 
entwickelt habe.

Wenn Du noch dabei bist, können wir nächstes Mal gerne über das 
Überschreiben von Operatoren mit Pythons "Magic Methods", und über die 
Übergabe und Übernahme von variablen Argumenten mit "args" und "kwargs" 
reden, über Comprehensions für Dicts und Lists... oder gerne auch über 
Tkinter für GUIs oder Flask für Webapplikationen, ganz wie Du magst. ;-)

Liebe Grüße, viel Erfolg und viel Spaß,
Sheeva!


[1] https://www.python.org/dev/peps/pep-0008/
[2] https://lord-nelson.com/

von Uwe D. (monkye)


Lesenswert?

Sheeva P. schrieb:
> Nehmen wir an, unser Chef hat ein Auto und möchte gerne ausrechnen, wie
> schnell er nach n Sekunden voller Beschleunigung ist. (Ja, das ginge
> auch mit einer einfachen Funktion, aber...) Weil unser Chef aber leider
> nicht Jutta Kleinschmidt ist, steht das Auto zu Anfang immer... ;-)
> 1class Auto:
> 2    def __init__(self, marke='Porsche', beschleunigung=0):
> 3        self.marke = marke
> 4        self.beschleunigung = beschleunigung
> 5        self.geschwindigkeit = 0
> 6
> 7    def beschleunige(self, zeit):
> 8        self.geschwindigkeit += self.geschwindigkeit + (zeit *
> self.beschleunigung)

Ist in Zeile 8 nicht der „self.geschwindigkeit“ nach der Zuweisung „+=“ 
überflüssig?

von Sheeva P. (sheevaplug)


Lesenswert?

Uwe D. schrieb:
> Sheeva P. schrieb:
>> 7    def beschleunige(self, zeit):
>> 8        self.geschwindigkeit += self.geschwindigkeit + (zeit *
>> self.beschleunigung)
>
> Ist in Zeile 8 nicht der „self.geschwindigkeit“ nach der Zuweisung „+=“
> überflüssig?

Oh... ja, da hast Du selbstverständlich Recht -- es muß natürlich 
entweder
1
self.geschwindigkeit += (zeit * self.beschleunigung)

oder
1
self.geschwindigkeit = self.geschwindigkeit + (zeit * self.beschleunigung)

heißen. Gutes Auge, vielen Dank für den Hinweis! ;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.