Seite 1 von 2

Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Dienstag 13. November 2018, 23:16
von sls
Hallo,

ich arbeite gerade ein Buch zur OOP durch und bin auf folgenden Code gestoßen:

Code: Alles auswählen

class Contact:
    all_contacts = []

    def __init__(self, name, email):
        self.name = name
        self.email = email
        Contact.all_contacts.append(self)


class MailSender:
    def send_email(self, message):
        print("Sending mail to ", self.email)
        # Add code logic here


class EmailableContact(Contact, MailSender):
    pass
`message` wird nicht weiter verwendet, das ganze soll auch nur als Beispiel für Mixins dienen. Ich bezweifle zwar, dass ich multiple Vererbung jemals brauchen werde, frage mich aber, wie man hier mit `self.email` "besser" umgehen kann. Ist es in Python üblich in Basisklassen Attribute zu verwenden, die im Namespace der Basisklasse eigentlich überhaupt nicht bekannt sind, und erst in der Subklasse verwendet werden? Sollte man o.g. Beispiel nicht besser mit abc als eine abstrakte Klasse ausbilden, damit man zumindest beim Erstellen einer Subklasse erkennt dass man diese und jene Attribute/Methoden implementieren muss?

Ich *weiß* jetzt zwar, dass ich `MailSender` so nicht direkt verwenden kann, mag mir aber vorstellen dass das in komplexeren Projekten schnell unübersichtlich werden kann.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Dienstag 13. November 2018, 23:39
von __deets__
Da ein Mixin selbst keinen Zustand haben soll, und gleichzeitig ein Attribut ohne Unterstrich davor eine public API darstellt, folgt daraus: ja, so macht man das. Wo da nun eine Abstrakte Klasse irgendetwas verbessern wuerde, erschliesst sich mir nicht.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Dienstag 13. November 2018, 23:58
von __blackjack__
Randbemerkung: Was an dem Beispiel wirklich schlecht ist, ist `all_contacts`. Das ist globaler Zustand. Und verhindert beispielsweise das man mehr als ein Adressbuch mit `Contact`-Objekt hat, beziehungsweise wenn man das braucht ist diese Liste einfach nur blödsinniger Ballast.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 08:57
von Sirius3
`all_contacts` ist sogar noch mehr blödsinnig, weil auch jede temporär gebrauchte Instanz darin gespeichert wird und man Mühe hat, die da wieder raus zubekommen.

@sls: Contact an sich ist ja nicht abstrakt. Es kann durchaus Anwendungsfälle geben, wo man einen Kontakt hat, der nicht per Email erreichbar ist (also in einem realen Kontext könnte das Sinn machen). Sollte es sich tatsächlich um eine abstrakte Klasse handeln, würde ich nichts mit abc, etc. machen, sondern einfach die Klasse AbstractXY nennen, dann ist dem Leser viel klarer, dass es sich eine abstrakte Klasse ist.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 13:15
von sls
Hier ist nochmal eine entsprechende Beschreibung des Autors zur Contact-Klasse:
How do we apply inheritance in practice? The simplest and most obvious use of
inheritance is to add functionality to an existing class. Let's start with a simple
contact manager that tracks the name and e-mail address of several people. The
contact class is responsible for maintaining a list of all contacts in a class variable,
and for initializing the name and address for an individual contact:

Code: Alles auswählen

class Contact:
	all_contacts = []
	def __init__(self, name, email):
		self.name = name
		self.email = email
		Contact.all_contacts.append(self)
This example introduces us to class variables. The all_contacts list, because it is
part of the class definition, is shared by all instances of this class. This means that
there is only one Contact.all_contacts list, which we can access as Contact.all_
contacts . Less obviously, we can also access it as self.all_contacts on any object
instantiated from Contact . If the field can't be found on the object, then it
will be found on the class and thus refer to the same single list.
Be careful with this syntax, for if you ever set the variable using
self.all_contacts, you will actually be creating a new instance
variable associated only with that object. The class variable will still
be unchanged and accessible as Contact.all_contacts.

This is a simple class that allows us to track a couple pieces of data about each
contact. But what if some of our contacts are also suppliers that we need to order
supplies from? We could add an order method to the Contact class, but that would
allow people to accidentally order things from contacts who are customers or family
friends. Instead, let's create a new Supplier class that acts like our Contact class,
but has an additional order method:

Code: Alles auswählen

class Supplier(Contact):
	def order(self, order):
		print("If this were a real system we would send "
			"'{}' order to '{}'".format(order, self.name))
In einem späteren Beispiel wird dann das list-built-in erweitert:

Code: Alles auswählen

class ContactList(list):
	def search(self, name):
		'''Return all contacts that contain the search value
		in their name.'''
		matching_contacts = []
		for contact in self:
			if name in contact.name:
				matching_contacts.append(contact)
		return matching_contacts
		
class Contact:
	all_contacts = ContactList()
	def __init__(self, name, email):
	....

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 15:28
von __blackjack__
`list` würde ich in der Regel auch nicht beerben, nur wenn die vorhandenen Methoden auch sinnvolle Ergebnisse liefern und gebraucht werden.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 15:32
von __deets__
Ich schwanke hier ein bisschen. Ich stimme mit __blackjack__ ueberein, dass das ableiten von einem builtin ungewwoehnlich ist, und eigentlich nicht passieren sollte. Nicht zuletzt weil man sich bei sowas fast immer irgendwelche "slicing"-Probleme einfaengt (so zumindest heisst das in C++, wenn man einen Wert der Basisklasse mit der abgeleiteten belegt, und dabei alles, was die kann, verliert).

Da wuerde ich immer auf Komposition setzen.

Das man Klassenattribute, auch veraenderliche, mal vorfuehrt, finde ich prinzipiell aber auch ok. Es gab durchaus Faelle, wo ich das auch gemacht habe aus guten Gruenden. ZB via Metaklassen alle Subklassen registrieren etc. Nur sollte davor natuerlich gewarnt werden.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 15:37
von Sirius3
@sls: ja, das ist eine typische Fehlinterpretation von Klassenattributen, dass sie dafür da sind, Instanzen zu sammeln. Wenn man soetwas tatsächlich möchte, liefert der Garbage-Collector bessere Methoden (was aber wirklich nur zu Debugging-Zwecken genutzt werden sollte). Klassenattribute verwende ich eigentlich fast ausschließlich als Klassen-Konstanten.

Um __blackjack__s Punkt nochmal deutlich zu machen, warum eine schlechte Idee ist, von `list` zu erben:

Code: Alles auswählen

class ContactList(list):
    pass

a = ContactList([1,2,3])
b = ContactList([7,6,8])
print(a + b)
# [1, 2, 3, 7, 6, 8]
print(type(a), type(b), type(a + b))
# <class '__main__.ContactList'>, <class '__main__.ContactList'>, <type 'list'>
Bei einfachen Operationen ändert sich der Typ der Klasse.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 23:11
von sls
Sirius3 hat geschrieben: Mittwoch 14. November 2018, 08:57 Sollte es sich tatsächlich um eine abstrakte Klasse handeln, würde ich nichts mit abc, etc. machen, sondern einfach die Klasse AbstractXY nennen, dann ist dem Leser viel klarer, dass es sich eine abstrakte Klasse ist.
Bei abstrakten Klassen *müssen* alle Methoden und Attribute in der erbenden Klasse implementiert sein, oder sehe ich das falsch? Ich werde gerade nicht schlau aus der Beschreibung von python-course.eu:
Abstract classes may not be instantiated, and require subclasses to provide implementations for the abstract methods. Subclasses of an abstract class in Python are not required to implement abstract methods of the parent class.
EDIT: so, ich meine ich hab's verstanden. Wenn eine erbende Klasse nicht alle Methoden / Attribute einer abstrakten Klasse implementiert, würde sie "theoretisch" selbst auch nur wieder eine irgendwie-so-halb-abstrakte Klasse darstellen.

Wenn du das abc-Modul nicht verwendest, wie machst du es dann? Einen `NotImplementedError` schmeißen, oder verzichtest du darauf den Benutzer zu zwingen den ganzen Krempel der abstrakten Klasse in der erbenden Klasse zu implementieren?

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Mittwoch 14. November 2018, 23:28
von Sirius3
@sls: Python ist dynamisch Typisiert. Da haben abstrakte Klassen rein dekorative Funktion, also eigentlich gar keine.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 00:03
von sls
Alles klar, vielen Dank an alle. Eine letzte Frage habe ich noch. Ich verwende öfter das Tornado-Webframework und dort gibt es diese Klasse:

Code: Alles auswählen

class RequestHandler(object):
    """Base class for HTTP request handlers.

    Subclasses must define at least one of the methods defined in the
    "Entry points" section below.
    """
    SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
                         "OPTIONS")

    _template_loaders = {}  # type: typing.Dict[str, template.BaseLoader]
    _template_loader_lock = threading.Lock()
    _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")

    def __init__(self, application, request, **kwargs):
        super(RequestHandler, self).__init__()

        self.application = application
        self.request = request
        ...
Wozu ist eigentlich super(RequestHandler, self).__init__() gut? In RequestHandler wird doch ein eigener initializer definiert, wozu dann den aus object dazu holen, oder was verstehe ich hier nicht?

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 00:23
von Sirius3
Das ist nötig, um bei Mehrfachvererbung sicherzustellen, dass alle __init__-Methoden aufgerufen werden.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 21:05
von sls
Sorry, ich hab's noch nicht ganz begriffen. Ich hab' dazu folgenden Test probiert:

Code: Alles auswählen

class A:
    def __init__(self):
        print("A has been called.")
        super().__init__()


class B(A):
    def __init__(self):
        print("B has been called.")
        super().__init__()


class C(B):
    def __init__(self):
        print("C has been called.")
        super().__init__()


c = C()
Selbst wenn super().__init__() in `A` fehlt, wird A.__init__() über B.__init__() aufgerufen. Aktuell verstehe ich das so, dass super() Methoden und Eigenschaften der Elternklasse übernimmt, von der man direkt erbt.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 21:16
von __deets__
Dann lade mal B NICHT von A ab, aber dann C von A & B. Ohne super in A oder B wird eines der beiden eben nicht aufgerufen.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 21:52
von sls
Ok, ich hab weiter herumprobiert:

Code: Alles auswählen

class A:
    def __init__(self):
        print("A has been called.")


class B:
    def __init__(self):
        print("B has been called.")


class C(A, B):
    def __init__(self):
        print("C has been called.")
        super().__init__()


c = C()
Ergebnis:

C has been called.
A has been called.

Tausche ich die Reihenfolge zu C(B, A):

C has been called.
B has been called.

füge ich nun super().__init__() in `B` hinzu:

C has been called.
B has been called.
A has been called.

Für mich ergibt sich daraus, dass die MRO mich erstmal ziemlich mürbe gemacht hat. Ich merke mir einfach, bei Mehrfachvererbung immer super().__init__() in den Basisklassen zu implementieren. Das macht wohl alles sinn, wenn C(A, B) ist, *muss* super().__init__() in A vorkommen, damit auch B.__init__() aufgerufen wird. Ist die Reihenfolge C(B,A) muss super().__init__() in B vorkommen, damit auch A.__init__() aufgerufen wird.

Danke für die Hilfe!

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 21:57
von __deets__
Na du musst halt immer super aufrufen. Eigentlich in jeder Klasse. Weißt ja nicht, ob die mal abgelitten wird. Mache ich zugegeben aber auch nicht. Man muss es halt wissen.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 22:54
von sls
Verstehe, ich glaube da muss ich erst noch ein Gefühl für bekommen. Um's mir heute nochmal richtig zu geben habe ich noch ein weiteres Beispiel bearbeitet, ich glaube jetzt habe ich mal ein Grundverständnis für den ganzen Zauber:

Code: Alles auswählen

class BaseClass:
    num_base_calls = 0

    def call_me(self):
        print(4)
        print("Calling method on Base Class")
        self.num_base_calls += 1


class LeftSubClass(BaseClass):
    num_left_sub_calls = 0

    def call_me(self):
        print(3)
        super().call_me()
        print("Calling method on Left Sub Class")
        self.num_left_sub_calls += 1


class RightSubClass(BaseClass):
    num_right_sub_calls = 0

    def call_me(self):
        print(2)
        super().call_me()
        print("Calling method on Right sub Class")
        self.num_right_sub_calls += 1


class SubClass(LeftSubClass, RightSubClass):
    num_sub_calls = 0

    def call_me(self):
        print(1)
        super().call_me()
        print("Calling method on Sub Class")
        self.num_sub_calls += 1


s = SubClass()
s.call_me()

print("base: ", s.num_base_calls)
print("left sub: ", s.num_left_sub_calls)
print("right sub: ", s.num_right_sub_calls)
print("sub: ", s.num_sub_calls)
Ausgabe:

1
3
2
4
Calling method on Base Class
Calling method on Right sub Class
Calling method on Left Sub Class
Calling method on Sub Class
base: 1
left sub: 1
right sub: 1
sub: 1

Jetzt weiß ich auch, warum mein ursprünglicher Ansatz via A.__init__(), B.__init__() in der __init__()-Methode von C potentiell in die Hose gehen könnte, bei obigen Beispiel fiel mir auf, dass dabei BaseClass doppelt aufgerufen wird. Jetzt werde ich von super() träumen %)

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Donnerstag 15. November 2018, 23:15
von __blackjack__
Also ich finde super alles andere als super und in meinem Code habe ich einfach grundsätzlich keine Mehrfachvererbung ausser in seltenen Fällen Mixins wo das nicht nötig ist. Das macht mir nur Kopfschmerzen für etwas was ich noch nie gebraucht habe und auch nicht brauchen werde. Ich empfehle zu dem Thema diese Lektüre: Python's Super is nifty, but you can't use it.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Montag 19. November 2018, 23:36
von sls
@__blackjack__: ich muss gestehen dass mich super() ziemlich verunsichert. Laut dem von dir verlinkten Artikel soll man super() in Subklassen immer implementieren, wenn es denn die Basisklasse auch tut, ich hatte ja ein wenig die Hoffnung dass ich super() einfach meiden könnte, für das folgende Problem habe ich allerdings keine alternative, wenn `A` oder `B` super() implementiert:

Code: Alles auswählen

class A:
    def __init__(self, a1, a2, *args, **kwargs):
        print("A has been called.")
        super().__init__(*args, **kwargs)
        self.a1 = a1
        self.a2 = a2

class B:
    def __init__(self, b1, b2, *args, **kwargs):
        print("B has been called.")
        super().__init__(*args, **kwargs)
        self.b1 = b1
        self.b2 = b2


class C(A, B):
    def __init__(self, a1, a2, b1, b2, c1, c2, *args, **kwargs):
        super().__init__(a1, a2, b1, b2, *args, **kwargs)
        self.c1 = c1
        self.c2 = c2


c = C(1, 2, 3, 4, 5, 6)
print(c.a1, c.a2, c.b1, c.b2, c.c1, c.c2)
Gerade die ganzen Webframeworks mit denen ich öfter herummache nutzen munter super, teilweise ist die Beschreibung nicht ganz so toll, so dass ich bei Mehrfachvererbung teilweise Schwierigkeiten habe überhaupt zu erkennen *woher* welche Argumente kommen sollen.

Re: Vererbung: Attribute aus Subklassen in Basisklassen

Verfasst: Montag 19. November 2018, 23:41
von __deets__
Es ist in der Realität ein ziemliches nicht-Problem. Es zu verstehen hilft wenn man es mal braucht. Doch das ist selten. Wir sind ja nicht Java.