Verständissfrage zu OOP (Anfänger)

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.
TrayserCassa
User
Beiträge: 97
Registriert: Donnerstag 2. Mai 2013, 19:11

Freitag 3. Januar 2014, 11:58

Da ich die Basis von OOP noch nicht so wirklich verstanden haben und es grade versuche habe ich Fragen zu der __init__ Methode. Was ich schon weiß das __init__ ein Konstruktor ist, was das allerdings ist geht über mein Fachwissen hinaus. Allerdings kann ich mir das durch vollgendem Code denken.

Code: Alles auswählen


class Mathe:
	def __init__(self):
		print("Hallo")
		global h
		h = 1
		global v2
		v2 = 2
	def rechnung(self):
		print("x =", h, " + ", v2)
		x = h + v2
		print(x)

x = Mathe()
#h = 1
#v2 = 2
x.rechnung()
Ich weiß das dieser Code völlig blödsinnig ist aber mir geht es hier ums Verständnis, denn ich wende ungerne etwas an was ich nicht verstehe ;)

Erste Frage:
Also mit def __init__(self) bewirke ich doch, dass egal welche Methode ich Aufrufe (hier halt rechnung()), immer alles was als __init__ definiert wird, erst ausführe? Oder habe ich das falsch?

Zweite Frage:
was genau bewirkt self? Zitat "Der Parameter self erscheint nur bei der Definition einer Methode. Beim Aufruf wird er nicht angegeben." Das man self einsetzten muss bei einer Methode ist mir bewusst, nur weshalb? :) Ich habe auch schon Code gesehen wo Variabel mit self definiert werden.. Beispiel:

Code: Alles auswählen


class Person:
	def __init__(self, name):
		self.name = name
	def sagHallo(self):
		print('Hallo, mein Name ist', self.name)

p = Person('Swaroop')
p.sagHallo()
Hier wird doch sozusagen eine globale variabel gesetzt, die dann übergeben wird bei p = Person(variabel)(Also global = nur für diese Klasse) oder?
Es ist nicht so als hätte ich nicht gegoogelt, allerdings für Anfänger die noch nie OOP programmiert haben finde ich Persönlich keine einfach Erklärung. Wohlgemerkt das ich die Destruktor Methode vernachlässigen soll (schon zu oft gelsen aus diesem Forum :D). Ich wurde auch schon drauf hingewiesen einfach sachen zu schlucken, bloß brauch ich dieses verständnis, ansonsten kann ich mir Code zusammen ergoogeln und dadurch lerne ich es nicht..

Und hier im Forum kann man auf mich eingehen und ich kann nochmal fragen fals noch etwas Unklar ist, was bei einem Tutorial ehr weniger geht und wenn diese zb. die Destruktor Methode anwenden, dann ist das ja auch nicht im sinne vom richtigen Programmieren. Worauf hier im Forum wert gelegt wird.

Ich bedanke mich schonmal fürs Lesen und vielleicht auch für meine verständnis Fragen.
mfg
Trayser
Benutzeravatar
Hyperion
Moderator
Beiträge: 7472
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Freitag 3. Januar 2014, 12:26

Puh... das ist alles so wirr bei Dir, dass Du das am besten wegwirfst und auch Dein Hirn resettest :-D

Hast Du Dir denn mal das Python-Tutorial durchgelesen und durchgearbeitet?

@1.) Jein. Ein Konstruktor ist es eigentlich nicht, denn Du kannst eine Klasse *ohne* ``__init__``-Methode schreiben und davon natürlich Exemplare erstellen. Andererseits erledigt man in ``__init__`` tatsächlich viele Dinge, die man in anderen Sprachen im Konstruktor tun würde. Wie der Name sagt, passieren darin *Initialisierungen*; du kannst und solltest darin alle Objekt-Variablen (die Dinger mit dem ``self.`` vorne dran) initialisieren.

@2.) wird im offiziellen Tutorial erklärt. ``self`` ist lediglich ein Name für das Exemplar-Objekt, welches der Methode übergeben wird. Du kannst es auch ``albernes_objekt_dings`` nennen, für das Funktionieren eines Python-Programms spielt es keine Rolle. Stell Dir einfach vor, dass eine Methode tatsächlich nur einmal im Speicher existiert, es aber zig verschiedene Exemplare einer Klasse geben kann. Damit eine Methode bei der Ausführung nun weiß, auf welchem Exemplar sie arbeitet, muss sie dieses übergeben bekommen. Das muss der Programmierer natürlich nicht händisch tun, sondern Python selber ermittelt das richtige Objekt und fügt es dem Aufruf automatisch hinzu.

Bei Deinem Beispiel wird intern in etwa folgendes gemacht:

Code: Alles auswählen

p = Person('Swaroop')
p.sagHallo()
# -> Person.sagHallo(p)
Das kannst Du auch testen:

Code: Alles auswählen

class Person:
    def __init__(self, name):
        self.name = name
    def hello(self):
        print("hello, ", self.name)

p = Person("Hyperion")
other = Person("TrayserCassa")

p.hello()
> hello,  Hyperion

Person.hello()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-33-13c387e99f03> in <module>()
----> 1 Person.hello()

TypeError: hello() missing 1 required positional argument: 'self'

Person.hello(other)
> hello,  TrayserCassa

Person.hello(p)
>hello,  Hyperion
Durch das idR. *implizit* übergebene Exemplar-Objekt weiß die Methode, auf welche zugehörigen Werte sie zugreifen muss. Denn die Attribute werden tatsächlich für jedes Exemplar einer Klasse separat gespeichert.

So, und nun kommen bestimmt die Koryphäen und korrigieren mich... :mrgreen:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3))
assert encoding_kapiert
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 3. Januar 2014, 12:28

``__init__`` ist kein Konstruktor, sondern eher ein "Initialisierer" wie man aus dem Namen vielleicht erkennen kann. Nachdem eine Instanz der Klasse erstellt wird, also in deinen Beispielen ``x`` oder ``p`` wird diese Methode automatisch von Python einmal aufgerufen um eben den Zustand zu initialisieren, quasi als würde man ``x.__init__(name)`` aufrufen.

Wozu man das ``self`` braucht: man braucht ja innerhalb der Methode eine Möglichkeit auf die aktuelle Instanz zuzugreifen. Etwa in ``sagHallo`` benötigst du ja eine Referenz auf ``p`` um dort das Attribut ``name`` zu erreichen. Genau so eine Referenz bietet dir ``self`` und es wird automatisch übergeben wenn du ``p.sagHallo()`` aufrufst (was äquivalent zu ``Person.sagHallo(p)`` ist, wo du siehst dass ``p`` als erster Parameter übergeben wird).
My god, it's full of CARs! | Leonidasvoice vs Modvoice
TrayserCassa
User
Beiträge: 97
Registriert: Donnerstag 2. Mai 2013, 19:11

Freitag 3. Januar 2014, 13:12

@Hyperion:
Hast Du Dir denn mal das Python-Tutorial durchgelesen und durchgearbeitet?
Meine Englisch Kenntnisse sind genauso gut, wie meine Deutsch Kenntnisse :oops:

zu 1:
Ok also benutzt man die ``__init__`` Methode, um die Methoden innerhalb einer Klasse mit Variabel zu versorgen :)

zu 2:
Hmm also wird ``self`` als zuweiser für Methoden aber auch Variabeln genutzt, damit sie weiß welcher Klasse (Exemplar?) sie zugehöhren?
ok moment.. ich glaub ein wenig hab ich das verstanden :mrgreen:

Mit dem Aufruf p = Person("Hyperion")
speicherst du "Hyperion" als Variabel im gesamten Klassen raum, sprich für alle Methoden und kannst dann mit p die variabel self.name an alle Methoden übergeben? (Hab ich das so richtig erklärt? :D) Beispiel was aber ein Taberror auswirft :K

Code: Alles auswählen


class Person:
    def __init__(self, name, name2):
        self.name2 = name2
        self.name = name
    def hello(self):
        print("hello, ", self.name)

	def greet(self):
		print("gruß zurück von", self.name2)

p = Person("Hyperion", "TrayserCassa")

p.hello()
p.greet()



-------------------------------------------
  File "test.py", line 9
    def greet(self):
                   ^
TabError: inconsistent use of tabs and spaces in indentation
Ok komisch.. Im Editor (Genay Portabel) sind die Methoden untereinander hier werden die aber versetzt Angezeigt :| Aber würde das so gehen? :)
Erstmal danke für die Hilfe und die Mühen :) hat mir schonmal weitergeholfen, als die Tutorials die Google ausspucken ;)

@Leonidas
Ok ich kann es zwar nicht mit meinen eigenen Worten ausdrücken, aber ich glaube ich hab es verstanden ;) Das ist der Punkt wo es mehr oder weniger Klick macht ;)
``p.sagHallo()`` was äquivalent zu ``Person.sagHallo(p)``
Auch danke für deine Hilfe und die Mühen :)

Edit:

Kommt davon wenn man Code kopiert :oops:
der Funktioniert:

Code: Alles auswählen

class Person:

	def __init__(self, name, name2):
		self.name2 = name2
		self.name = name

	def hello(self):
		print("hello, ", self.name)
	

	def greet(self):
		print("gruß zurück von", self.name2)

p = Person("Hyperion", "TrayserCassa")

p.hello()
p.greet()
Benutzeravatar
/me
User
Beiträge: 3192
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Freitag 3. Januar 2014, 13:29

TrayserCassa hat geschrieben:Mit dem Aufruf p = Person("Hyperion")
speicherst du "Hyperion" als Variabel im gesamten Klassen raum, sprich für alle Methoden und kannst dann mit p die variabel self.name an alle Methoden übergeben?
Nicht ganz. Durch die Zuweisung in __init__ wird der Wert an ein spezielles Exemplar (Instanz) der Klasse gebunden, nicht an die Klasse selber.
TrayserCassa
User
Beiträge: 97
Registriert: Donnerstag 2. Mai 2013, 19:11

Freitag 3. Januar 2014, 13:47

/me hat geschrieben: Nicht ganz. Durch die Zuweisung in __init__ wird der Wert an ein spezielles Exemplar (Instanz) der Klasse gebunden, nicht an die Klasse selber.
Ok ich glaub die Funktion hab ich Begriffen :)
Danke für die korrekte Formulierung ;)

mfg Trayser
Benutzeravatar
/me
User
Beiträge: 3192
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Freitag 3. Januar 2014, 14:43

TrayserCassa hat geschrieben:Ok ich glaub die Funktion hab ich Begriffen :)
Dann demonstrieren wir noch mal kurz den Unterschied zwischen Klassenattributen und Instanzattributen.

first_names ist hier auf Klassenebene deklariert. Wenn dann beim Zugriff der Name nicht in der Instanz gefunden wird, dann wird in der Klasse nachgeschaut ob es ihn gibt.

Code: Alles auswählen

>>> class Thing(object):
	first_names = ['Eric']
	def __init__(self):
		self.last_names = ['Idle']
		
>>> x = Thing()
>>> x.first_names.append('Terry')
>>> x.last_names.append('Jones')
>>> x.first_names
['Eric', 'Terry']
>>> x.last_names
['Idle', 'Jones']
>>> y = Thing()
>>> y.first_names
['Eric', 'Terry']
>>> y.last_names
['Idle']
TrayserCassa
User
Beiträge: 97
Registriert: Donnerstag 2. Mai 2013, 19:11

Freitag 3. Januar 2014, 17:36

@ /me

Ok verstanden :)

dann Frage ich mich allerdings warum man eine Instanz macht und nicht gleich auf Klassenebene. Da muss ja noch ein entscheidener unterscheid sein :D

Danke für den Code, den speicher ich mir erstmal ab :D

mfg
Trayser
EyDu
User
Beiträge: 4871
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Freitag 3. Januar 2014, 17:52

TrayserCassa hat geschrieben:dann Frage ich mich allerdings warum man eine Instanz macht und nicht gleich auf Klassenebene. Da muss ja noch ein entscheidener unterscheid sein :D
Instanzen/Exemplare sind Ausprägungen einer Klasse. Stelle dir das einfach an einem konkreten Beispiel vor: Du hast eine Auto-Klasse, welche die möglichen Eigenschaften eines Autos beschreibt: PS, Gewicht, Farbe, etc.

Code: Alles auswählen

class Auto(object):
    def __init__(self, ps, gewicht, farbe):
        self.ps = ps
        self.gewicht = gewicht
        self.farbe = farbe
Aber natürlich gibt es nicht nur ein Auto, sonder jede Menge verschiedene. Für die verschiedenen Fahrzeuge erstellst du dann eine Instanz der Auto-Klasse:

Code: Alles auswählen

dein_auto = Auto(42, 1000, "rot")
mein_auto = Auto(100, 800, "blau")
Nun existieren zwei verschiedene Fahrzeuge. Änderst du nun etwas ein deinem Auto, dann hat das keine Auswirkung auf meins.
Das Leben ist wie ein Tennisball.
TrayserCassa
User
Beiträge: 97
Registriert: Donnerstag 2. Mai 2013, 19:11

Samstag 4. Januar 2014, 17:10

@EyDu

Ok jetzt hab ich es begriffen :D

Viel Dank an alle. Das war mehr wert als ein Tutorial ;)

mfg
Trayser
neovanmatix
User
Beiträge: 19
Registriert: Samstag 28. Dezember 2013, 20:52

Samstag 4. Januar 2014, 18:39

Hi,

ich beschäftige mich seit einigen Stunden mit Python und habe noch ein paar Tutorial-Tabs offen. Vielleicht hilft dir der hier weiter?
http://www.python-kurs.eu/python3_klassen.php
BlackJack

Samstag 4. Januar 2014, 19:09

Mit der Beschreibung von `__del__()` habe ich immer noch ein wenig Bauchschmerzen. Das ``del`` nur dann `__del__()` startet wenn… ist immer noch irreführend. ``del`` und `__del__()` haben nur sehr entfernt miteinander zu tun. ``del`` löscht Namen aus Namensräumen oder führt zum Aufruf einer Methode auf einem Objekt (*nicht* `__del__()`!) und irgendwo könnte in der Folge davon ein Objekt freigegeben werden, muss es aber nicht, weil das eben nicht hauptsächlich von dem ``del`` abhängt, sondern ob es noch referenzen gibt. IMHO sollte man `__del__()` in Anfänger-Tutorials überhaupt nicht erwähnen, oder nur als „*implementiere das nicht!*”. Oder man versieht das mit einer Warnung die mindestens so stark ist wie die in der Python-Dokumentation. Und immer noch mit dem Hinweis das man die Methode nur in äussersten Spezialfällen braucht und auch dann gut wissen sollte welche Folgen das haben kann.

Datenkapselung präsentiert doppelte führende Unterstriche als „private”, was nicht stimmt. Dazu ist der Mechanismus nicht gedacht und „unsichtbar” und von aussen nicht benutzbar sind die Attribute auch nicht, man kann sie sowohl sehen, als auch darauf zugreifen, sie heissen nur anders. Dieses Name-Mangling ist aber dokumentiert und kein Implementierungsdetail. Nicht-öffentliche Attribute werden einfach mit einem einzelnen führenden Unterstrich gekennzeichnet.

Der `object_zaehler` unter „Statische Member” (sollte in Python-Lingo eigentlich „Klassenattribute” heissen, Member ist ehr C++) hat wegen `__del__()` so seine Problemchen. Und auch wenn das Thema Vererbung erst danach kommt: Die scheinbar schlaue Implementierung mit `type()` richtet lustiges Chaos an wenn man Unterklassen bildet und da nicht auch das Klassenattribut `objekt_zaehler` explizit definiert. Das erste Exemplar von einer Unterklasse startet dann nämlich nicht zwingend bei 0 sondern mit dem Zählerstand der Basisklasse plus eins.

Das Beispiel in „Vererbung” ist semantisch schlecht, denn mit Vererbung werden „ist-ein(e)”-Beziehungen modelliert. Und ein Konto *ist* kein (Konto)zähler. Überhaupt sind Klassenattribute jenseits von Konstanten total selten, weil das globaler Zustand und damit in aller Regel unsauber ist. Wie man so etwas als erstes Beispiel für Vererbung hernehmen kann ist mir ein Rätsel.

Bei „Mehrfachvererbung” bricht es dann recht abrupt ab. Hier hätten noch erwähnt werden sollen, dass man das sehr selten nutzt, weil das diverse Probleme mit sich bringt, vielleicht auch mit Beispielen für selbige. *Wenn* es benutzt wird, dann meistens für „Mix-in”-Klassen. Und hier hätten dann auch die doppelten führenden Unterstriche hingegehört.
tholle
User
Beiträge: 29
Registriert: Donnerstag 7. Juli 2011, 13:51

Mittwoch 7. Februar 2018, 21:57

Hi Leute,

ich beschaeftige mich nun mit dem Thema seit mehreren Tagen. Immer wenn ich das Gefuehl habe, ich habe das Instanzieren, bzw. Initialisieren verstanden, faellt bei dem einen oder anderen Beispiel meine Vorstellung von Verstaendnis wie ein Kartenhaus in sich zusammen.

Explizit geht's mir um die Frage, warum man eine Instanzierung ueberhaupt braucht. :K

Wenn ich von einem Objekt zwei verschiende Instanzen erschaffe, z.B. InstanzA und InstanzB und Attribute in der Klasse (ohne __init__) anliege, die ich beim Instanzieren veraendere, hat das keine Auswirkung auf die jeweiligen Attribute der beiden Instanzen.

Beispiel (ich konzentrieren mich mal nur an dem Attribut breite):

Code: Alles auswählen

[class Kiste:
    breite = 0
    hoehe = 0
    tiefe = 0
    def getVolumen(self):
        vol = self.breite*self.hoehe*self.tiefe
        return vol
        
// InstanzA    
meinekiste = Kiste()
meinekiste.breite = 5
meinekiste.hoehe = 10
meinekiste.tiefe = 15
print(meinekiste.breite)

//InstanzB
meineneuekiste = Kiste()
meineneuekiste.breite = 25
meineneuekiste.hoehe = 35
meineneuekiste.tiefe = 45
print(meineneuekiste.breite)

print(meinekiste.breite)

//Ergebnis

5
25
5

Wie kann das sein? Beim letzten Aufruf von meinekiste.breite muesste doch, wenn Attribute ohne Initialisierung manipuliert werden koennen, so wie EyDu es anhand von den Autobeispielen gesagt hat, 25 und nicht 5 heraus kommen.

Es kommt aber immer noch fuer die Instanz meinekiste.breite 5 heraus. Das heisst, dass InstanzB konnte die Attribute von InstanzA nicht manipulieren.

An dieser Stelle frage ich mir wieder, wofuer Initialisieren, wenn eine Instanz die Attribute, die in der Klasse definiert sind, nicht veraendern kann. :K

Entweder habe ich einen Denkfehler oder mir fehlt bei dem ganzen Thema noch eine entscheidende Information oder aber, ich bin viel schlauer als alle anderen hier. aehhhmmm... nope..

Koennte mir das bitte mal einer erklaeren. Ich verzweifle sonst an dem Thema.

Vielen Dank schon mal im Voraus.

Viele Gruesse,
tholle
Sirius3
User
Beiträge: 8116
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 7. Februar 2018, 22:40

@tholle: Sinn von Instanzen ist, dass sie unabhängig von anderen Instanzen sind. Von daher sollte es Dich nicht verwundern, dass wenn Du Attribute von »meineneuekiste« änderst, Attribute von »meinekiste« gleich bleiben.

Dein Problem ist, dass Du Klassenattribute hast und diese mit Instanzattributen gleichen Namens überschreibst. Klassenattribute sind selten sinnvoll (und dann meist nur als Konstanten) und am Anfang sollte man sie gar nicht benutzen.

Und jetzt kommt »__init__« ins Spiel. Eine Klasse sollte nach der Initialisierung einsatzbereit sein; Deine Instanzen sind erst einsatzbereit, wenn Du auch noch die drei Attribute gesetzt hast. Das korrekte Beispiel ohne Klassenattribute und mit __init__ sähe so aus:

Code: Alles auswählen

class Kiste:
    def __init__(self, breite, hoehe, tiefe):
        self.breite = breite
        self.hoehe = hoehe
        self.tiefe = tiefe

    def get_volumen(self):
        return self.breite * self.hoehe * self.tiefe
        
// InstanzA    
meinekiste = Kiste(5, 10, 15)
print(meinekiste.breite)

//InstanzB
meineneuekiste = Kiste(25, 35, 45)
print(meineneuekiste.breite)

print(meinekiste.breite)
PS: Methoden werden wie Variablen klein_mit_unterstrich geschrieben.
Benutzeravatar
pillmuncher
User
Beiträge: 1106
Registriert: Samstag 21. März 2009, 22:59
Wohnort: München

Mittwoch 7. Februar 2018, 22:48

Du bringst die Terminologie völlig durcheinander.

Klassen haben Instanzen, auch Exemplare genannt. Der Text der Klasse definiert diese. Die Klasse ist gewissermaßen ein Bauplan für ihre Instanzen.

Um eine Instanz zu erhalten, muss der Konstruktor der Klasse aufgerufen werden. Diesen schreibt man entweder selbst (mittels __init__ bzw. selten auch mal mit __new__), oder man verwendet den bereits bestehenden der Basisklasse, die meistens object ist. object ist der Name einer Klasse. Ein Objekt ist alles, was Instanz einer Klasse ist, die object als Basisklasse hat.

Objekte haben keine Instanzen, sondern sind Instanzen. Genauso wie jemand der Speisen zubereitet ein Koch ist, aber nicht "einen Koch hat". Oder, angenommen ich habe einen Neffen Stefan, dann bin ich Stefans Onkel, aber ich habe nicht Stefans Onkel.

Jetzt wird es etwas interessanter. In Python3 sind alle Objekte Instanzen von object, selbst Klassen, Funktionen, Methoden und Module. Klassen können also auch Attribute haben. Wenn du eine Klassendefinition hast wie diese:

Code: Alles auswählen

class Kiste:
    breite = 0
    hoehe = 0
    tiefe = 0
Dann sind breite. hoehe und tiefe Attribute der Klasse, nicht ihrer Instanzen. Dass man trotzdem auf sie zugreifen kann, als wären sie Instanzattribute, hängt mit Pythons Zugriffsmodell zusammen, macht sie aber nicht zu Instanzattributen. Wenn du eine Instanz hast, sagen wir k1, dann kannst du schreiben:

Code: Alles auswählen

k1.breite = 123
k1.hoehe = 456
k1.tiefe = 789
Nun sind diese Attribute welche der Instanz. Sie "verdecken" die gleichnamigen Attribute der Klasse, überschreiben diese aber nicht. Schau dir das hier mal an:

Code: Alles auswählen

>>> class Kiste:
...    breite = 0
...    hoehe = 0
...    tiefe = 0
... 
>>> k1 = Kiste()
>>> k1.breite = 123
>>> k1.hoehe = 456
>>> k1.tiefe = 789
>>> k1.breite, k1.hoehe, k1.tiefe
(123, 456, 789)
>>> Kiste.breite, Kiste.hoehe, Kiste.tiefe                                      
(0, 0, 0)
>>> del k1.breite
>>> del k1.hoehe
>>> del k1.tiefe
>>> k1.breite, k1.hoehe, k1.tiefe                                               
(0, 0, 0)
>>> del Kiste.breite                                                            
>>> del Kiste.hoehe                                                             
>>> del Kiste.tiefe                                                             
>>> k1.breite, k1.hoehe, k1.tiefe
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Kiste' object has no attribute 'breite'
>>> Kiste.breite, Kiste.hoehe, Kiste.tiefe                                      
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Kiste' has no attribute 'breite'
In specifications, Murphy's Law supersedes Ohm's.
Antworten