Subklasse einer Subklasse

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.
Antworten
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Liebes Forum,
ich habe ein irgendwie verzwicktes Problem, bei dem ich nicht weiterkomme: ich habe drei Klassen in "normaler" Hierarchie:

Code: Alles auswählen

class Immobilienseite:
	def __init__(self, url):
		self._url = url
		self._suchtxt = ''
		self.__seite = urllib.request.urlopen(self._url)
		self.__sourcecode = self.__seite.read().decode('utf8')
		self._soup = BeautifulSoup(self.__sourcecode, 'html.parser')
	
	def bs4_aufbereiten(self):
		for JS in self.__soup(['style','script','path']):				#Um Zeit zu sparen werden erst alle Scripte und Styles entfernt. BS tut das nicht automatisch.
			JS.extract()
			
	def fliesstext_auswerten(self):
		self.__suchtxt = self.__soup.body.get_text()
		self.__suchtxt = re.sub('\r|\n', ' ',self.__suchtxt)	#Text aufbereiten: um später bessere Suchergebnisse zu erzielen (also nicht Zeilenumbrüche oder doppelte Leerzeichen berücksichtigen zu müssen) werden diese in zwei Stufen entfernt und der Suchtext auf einen einzigen Textblock normalisiert.
		self.__suchtxt = re.sub('\s{2,}', ' ',self.__suchtxt)
		neue_suche = Suchmechnismus(self.__suchtxt)
		neue_suche.suchwort_abgleich()


class Immobilienscout (Immobilienseite):
	def __init__(self, url):
		Immobilienseite.__init__(self, url)
		self._neue_suche = Suchmechnismus(self._suchtxt)
		self.ist_landingpage()
	
	def ist_landingpage(self):
		if 'expose' not in self._url:
			Landingpage(self)

class Landingpage():
	def __init__(self, Scoutobj):
		self.__Scout = Scoutobj
		self.typ_pruefen()
	
	def typ_pruefen(self):
		if self.__Scout._soup.find_all('a', {'title':'Projektinformationen'}):	#Landingpage Typ1, erzeugt mit JS.
			merkm_liste = self.__Scout._soup.find('ul', class_='list-check')
			listenpunkte = merkm_liste.find_all('li')
			counter = 1												
			for tag in li_tags:
				tag = tag.text
				if re.search(r'\d\s?-?\d?\s?Zimmer', tag, re.IGNORECASE):
					self._neue_suche.keywords.append(tag)
				elif re.search('\d{1,3}\s?m\u00B2', tag, re.IGNORECASE):
					self._neue_suche.keywords.append(tag)
				elif re.search(r'Ausstattung', tag, re.IGNORECASE):
					self._neue_suche.keywords.append(tag)
				elif counter == len(li_tags):
					self._neue_suche.keywords.append('Anschrift: ' + tag)
				counter += 1
		print(self._neue_suche.keywords)
Die Klasse Immobilienscout wird in einem speziellen Fall direkt aufgerufen, statt der Basisklasse Immobilienseite. In diesem Fall klappt der Aufruf der Subklasse "Landingpage" aber nicht korrekt, da auf die Methoden der obersten Basisklasse nicht zugegriffen werden kann. Ich kann aber doch beim Aufruf von "Landingpage" nicht nochmal ein "Immoscout" Objekt aufrufen, das ich gar nicht brauche... Irgendwie hab ich nen Knoten im Hirn. :K
Falls das "if" in ist_landingpage() wahr ist, soll einfach ein Aufruf der Subklasse erfolgen, die dann bestimmte Checks durchführt und die entsprechenden Attribute der Basisklasse bzw. der Basisbasisklasse setzt. Irgendwie bekomme ich das aber nicht hin... wo ist mein Denkfehler - könnt ihr mir auf die Sprünge helfen?
DAnke!
Zuletzt geändert von Anonymous am Sonntag 19. Juni 2016, 22:15, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@T.T. Kreischwurst: Das ist alles ein bisschen verwirrend. Unter anderem weil Du Dich nicht an Schreibweisen hältst. Und ganz wirre Sachen mit unterstrichen machst. Verwende *einen* für Attribute die nicht zur öffentlichen API gehören. Verwende keine *zwei*. Du hast da teilweise so Sachen wie `_suchtext` und `__suchtext` in ein und dem selben Objekt. Wo ist denn da der Unterschied?

Was ist denn in Deinen Augen eine ”normale Hierarchie”? Eine Vererbungshierarchie hast Du jedenfalls nicht über drei Klassen.

Es wird zu viel an die Objekte gebunden was da nicht dran gehört. Auf der anderen Seite hast Du `Suchmechanismus()` und `Landingpage` deren Verwendung nach einem „code smell“ aussehen weil das, zumindest wie es dort steht, nach Funktionen und nicht nach Klassen aussieht.

Und falls Dein Problem sein sollte, dass Du in `Landingpage.typ_pruefen()` nichts an `self._neue_suche` anhängen kannst: `Landingpage` hat so ein Attribut nun mal nicht. Übrigends auch keine der anderen beiden Klassen.

Du kannst in `Landingpage`-Objekten per ``self.__Scout`` an das `Immobilienscout`-Exemplar herankommen. Aber dann kannst Du die Funktionalität auch gleich in `Immobilienscout` verschieben, denn die sind eh verdammt eng gekoppelt, so dass ich jetzt nicht sehe warum Du die `Landingpage` da so komisch modellierst.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@T.T. Kreischwurst: was für ein Problem willst Du eigentlich lösen? Aus Deinem Code und Deiner Beschreibung werde ich nicht wirklich schlau. Vererbung ist doch gerade dazu da, dass man die abgeleiteten Klassen anstatt der Basisklasse verwenden kann. Das sollte doch also kein Problem sein.

Du mußt nicht alles an Attribute binden. __seite oder __sourcecode sind doch eigentlich nur lokale Variablen. Deine Methoden haben für mich auch keine klare Aufgabe. Die doppelten Unterstriche machen auch keinen wirklichen Sinn hier und bei einer Methode die mit is_... anfängt, erwarte ich, dass die einen Wahrheitswert zurückliefert.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Zuerst mal vielen Dank für die schnellen Antworten - und entschuldigt das Durcheinander. Es ist etwas schwierig, mein Problem zu beschreiben, denn so ganz bin ich noch nicht durchgestiegen, wo es eigtl. liegt. Es ist irgendwas mit der Vererbung, was ich vermutlich falsch verstanden habe, aber ich tue mich schwer, es zu präzisieren. Ich bin noch ein ziemlich blutiger Anfäger, daher auch viele der unschönen/falschen Schreibweisen. Die kommen andererseits auch daher, dass ich Sachen ausprobiert und damit verändert habe, was den ursprünglichen Code verändert und durcheinandergebracht hat. Ich bring mir das grade selber bei und das ist etwas viel/verwirrend teilweise.
Also:
Verwende *einen* für Attribute die nicht zur öffentlichen API gehören. Verwende keine *zwei*.
OK, mach ich. Wann verwende ich denn eigtl. zwei? In den Lehrbüchern findet man meist die Schreibweise mit zwei Unterstrichen für "private" Sachen.
Was ist denn in Deinen Augen eine ”normale Hierarchie”? Eine Vererbungshierarchie hast Du jedenfalls nicht über drei Klassen.
Interessant, wusste ich nicht. :idea: Ich dachte immer, man kann eine Klasse auch von einer Klasse erben lassen, die selber Subklasse ist.
Es wird zu viel an die Objekte gebunden was da nicht dran gehört. Auf der anderen Seite hast Du `Suchmechanismus()` und `Landingpage` deren Verwendung nach einem „code smell“ aussehen weil das, zumindest wie es dort steht, nach Funktionen und nicht nach Klassen aussieht.
Mir ist nicht ganz klar, wieso das nach Funktionen und nicht nach Klassen aussieht - meinst du modellierungstechnisch?
Hintergrund ist (auch @Sirius3), dass "Landingpage" noch weiter ausgebaut wird mit eigenen Methoden, die nur auf diese Klasse zutreffen. Es wird später auch noch weitere Klassen à la "Immoscout" geben, eben für andere Portale. Jedes große Portal bekommt einen eigenen Suchmechnismus, kleine Seiten (z.B. von Maklern) laufen über die allgemeine "Immobilienseite". Der hier gezeigte Code ist nur eine Momentaufnahme und auch nur ein Ausschnitt des Programms. Mir ging es darum, dass irgendwo in der gezeigten Vererbungshierarchie ein (Denk)Fehler liegt, daher hab ich nur diesen Teil des Scripts gepostet.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
In den Lehrbüchern findet man meist die Schreibweise mit zwei Unterstrichen für "private" Sachen.
Was aber (auch) daran liegt, dass zumindest viele deutschsprachige (Lehr-) Bücher das falsch erklären / darstellen. Im Gegensatz zu andere Sprachen kennt Python keine echten privaten Attribute / Methoden. Wenn man will kommt man an alles dran.
Ich dachte immer, man kann eine Klasse auch von einer Klasse erben lassen, die selber Subklasse ist.
Kann man auch. Nur in deinem Code hast du das eben nicht. `Immobilienscout` erbt von `Immobilienseite` und das war's. Du vererbst also nur 1x.
Mir ist nicht ganz klar, wieso das nach Funktionen und nicht nach Klassen aussieht - meinst du modellierungstechnisch?
Die Klasse `Landingpage` hat aktuell ein Attribut und eine Funktion. Da macht die Klasse halt wenig Sinn, weil eine einfache Funktion `typ_pruefen(Scoutobj)`das gleiche liefern würde.

Gruß, noisefloor
BlackJack

@T.T. Kreischwurst: Zwei führende Unterstriche verwendet man eigentlich gar nicht. Die sind für Mixin-Klassen, also Mehrfachvererbung, und tiefe Vererbungshierarchien, und dazu da um Namenskollisionen zu verhindern. Aber da man in Python in der Regel weder Mehrfachvererbung noch tiefe Vererbungshierarchien hat, braucht man doppelte führende Unterstriche sehr selten tatsächlich. Literatur die das als ”private” anpreist ist von Leuten die kein Python schreiben sondern eine andere Programmiersprache im Kopf haben und die versuchen in Python-Syntax umzusetzen.

Natürlich kann man eine Klasse von einer anderen Klasse erben lassen die selber wieder von einer anderen Klasse erbt. Das tust Du allerdings nicht. Du hast da nur `Immobilienseite` und `Immobilienscout` die in einer Vererbungsbeziehung stehen. `Landingpage` erbt von `object`.

Du hast das hier:
[codebox=text file=Unbenannt.txt]+-----------------+ +-----------------+ +--------+ +-------------+
| Immobilienscout | --> | Immobilienseite | --> | object | <-- | Landingpage |
+-----------------+ +-----------------+ +--------+ +-------------+[/code]

Schreibst im Text aber als hättest Du das hier:
[codebox=text file=Unbenannt.txt]+-------------+ +-----------------+ +-----------------+ +--------+
| Landingpage | --> | Immobilienscout | --> | Immobilienseite | --> | object |
+-------------+ +-----------------+ +-----------------+ +--------+[/code]

Was aber auch Unsinn wäre, denn dann würdest Du der `Landingpage` beim erstellen ein `Immobilienscout`-Objekt übergeben obwohl die `Landingpage` auch vom Typ `Immobilienscout` ist. Warum wäre sie dass denn dann?

In `Immobilienscout` und `Landingpage` machst Du zu viel von der `__init__()` aus, und zwar so dass es aussieht als wäre der Hauptzweck der Klasse das man sie einmal aufruft und dann alles passiert ist was so passieren soll. Und das ist eigentlich etwas was man mit Funktionen macht. Das Beispiel mit `Suchmechnismus` sieht auch komisch aus. Immer wenn man eine Klasse hat, die nur aus der `__init__()` und *einer* weiteren Methode besteht, sollte man dringend prüfen ob man da nicht einfach eine Funktion zu umständlich als Klasse formuliert hat.

Du solltest vielleicht mehr Funktionen benutzen und erst dann Klassen verwenden wenn die wirklich Sinn machen. Und auch ein bisschen vorsichtiger mit Vererbung umgehen. Nur wenn das Sinn macht, wenn sich aus mehreren Klassen durch Vererbung gemeinsames Verhalten herausziehen lässt. Das sehe ich in Deinem Beispiel nicht. Andererseits funktioniert Dein Beispiel aber auch so ganz und gar nicht.

Was ich da als erstes angehen würde ist das mit der URL. Für diese Seitenobjekte sind schlecht Tests zu schreiben wenn die alle eine URL bekommen und mit dem Internet reden wollen. Das würde ich herausziehen und diesen Objekten nur das geben was sie wirklich zur Verarbeitung benötigen. Also zum Beispiel das `soup`-Exemplar. Herunterladen und Vorverarbeitung die alle Auswertungen brauchen, kann man in eine oder mehrere Funktionen schreiben. Es müssen nicht immer Klassen sein.

Du willst anscheinend von einen Datentyp, das er sich selbst zu einem spezielleren, abgeleiteten Datentyp macht. Das ist so nicht vorgesehen. Das kann man Beispielsweise durch eine Fabrikfunktion lösen, die entscheidet welcher Typ erstellt wird. Dafür müsste man aber wie schon geschrieben die Vorverarbeitung aus der Initialisierung der Klassen heraus halten, weil man `url` und/oder `soup` ja schon benötigt, um zu entscheiden ob es sich um eine Landingpage handelt oder nicht.

Wobei ich schon das nächste Problem kommen sehe: Falls Du dann irgendwann eine `Immobilienscout`-Seite von der `Landingpage` unterscheiden willst und dann fragst wie man denn bei ``if`` auf den Typ testest: Das ist ein „code smell“ weil man solche Entscheidungen in OOP normalerweise nicht braucht.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Ah, ich glaube, langsam wird mir die Sache klarer. Super Support hier, für Anfänger extrem wertvoll!
Zunächst zu den Büchern und den Unterstrichen: ich muss fairerweise sagen, dass darauf durchaus hingewiesen wurde. Ich glaube sogar, dass die Codebeispiele ohne sind (hab es grade nicht hier), aber häufig liest man andernorts, z.B. wenn man was googelt, die Schreibweise mit doppeltem Unterstrich. So schleicht sich das dann ein.
Was aber auch Unsinn wäre, denn dann würdest Du der `Landingpage` beim erstellen ein `Immobilienscout`-Objekt übergeben obwohl die `Landingpage` auch vom Typ `Immobilienscout` ist. Warum wäre sie dass denn dann?
Das trifft, glaube ich, des Pudels Kern. Landingpage erbt im geposteten Fall deswegen nicht von Immoscout, weil ich dann bei der Instanziierung des "Landigpage" Objektes noch ein Immoscout-Objekt mit erstellen würde - was redunant ist, denn es existiert bereits eines. Folglich liegt der Fehler im Design: Landingpage ist keine Subklasse, ich komme auch mehr und mehr davon ab, sie als eigene Klasse zu modellieren, denn:
In `Immobilienscout` und `Landingpage` machst Du zu viel von der `__init__()` aus, und zwar so dass es aussieht als wäre der Hauptzweck der Klasse das man sie einmal aufruft und dann alles passiert ist was so passieren soll. Und das ist eigentlich etwas was man mit Funktionen macht. Das Beispiel mit `Suchmechnismus` sieht auch komisch aus. Immer wenn man eine Klasse hat, die nur aus der `__init__()` und *einer* weiteren Methode besteht, sollte man dringend prüfen ob man da nicht einfach eine Funktion zu umständlich als Klasse formuliert hat.

Du solltest vielleicht mehr Funktionen benutzen und erst dann Klassen verwenden wenn die wirklich Sinn machen. Und auch ein bisschen vorsichtiger mit Vererbung umgehen. Nur wenn das Sinn macht, wenn sich aus mehreren Klassen durch Vererbung gemeinsames Verhalten herausziehen lässt. Das sehe ich in Deinem Beispiel nicht. Andererseits funktioniert Dein Beispiel aber auch so ganz und gar nicht.
Das ist nicht ganz von der Hand zu weisen, vor allem für "Landingpage". Die anderen Klassen haben m.E. durchaus ihren Sinn, denn es kommt jeweils noch eine Reihe weiterer Funktionen hinzu, die auf diese Portale jeweils zugeschnitten sind. Entwürfe dafür hab ich schon, sie sind aber hier noch nicht implementiert. Die "Immobilienseite"-Klasse ist sozusagen die absolut eingedampfte Version, die ein paar grundlegende Dinge tut, die wiederum allen fraglichen Seiten gemeinsam sind.
Ich werde das ganze also umstellen/umschreiben - danke für die Hinweise diesbezüglich!
Antworten