Seite 1 von 1

Virtuelle methoden in Python?

Verfasst: Mittwoch 21. Januar 2009, 15:24
von madfrog
Gibt es in Python eine vergleichbare Struktur zu dem üblichen C++-Syntax: "virtual void draw()" um zu erzwingen, daß diese Methode von Subklassen implementiert werden muß?

Verfasst: Mittwoch 21. Januar 2009, 15:31
von Rebecca
Du kannst die Methode einen NotImplementedError werfen lassen.

Verfasst: Mittwoch 21. Januar 2009, 15:33
von DasIch
Mit Abstact Base Classes erreichst du wohl am ehesten was du willst.

Verfasst: Mittwoch 21. Januar 2009, 15:58
von madfrog
Danke! Hier die erste Implementation die den Vorschlag von Rebecca umsetzt:

Code: Alles auswählen

class virtualmethod(object):
    def __init__(self, func):
        assert callable(func)
        self._func = func

    def __call__(self, *args, **kwargs):
        msg = "Invalid call of %s (virtual method)." % self._func.__name__
        raise NotImplementedError(msg) # --- verbessert
    
class VirtualDraw(object):
    @virtualmethod
    def draw(self):
        pass
    #draw = virtualmethod(draw) --- verbessert durch Dekorator

class ImplementedDraw(VirtualDraw):
    def draw(self):
        print "WORKS!"

test1 = VirtualDraw()
#test1 = ImplementedDraw()
test1.draw()
Leider komm ich nicht an den Namen der Klasse ran, wo die virtuelle Methode de facto noch nicht implementiert ist.

Verfasst: Mittwoch 21. Januar 2009, 16:21
von DasIch
Was spricht gegen

Code: Alles auswählen

class VirtualDraw(object):
    def draw(self):
        raise NotImplementedError
?

Außerdem würde man statt

Code: Alles auswählen

raise NotImplementedError, 'my cool message'
# folgendes machen
raise NotImplementedError('my cool message')
Außerdem Pythons Dekoratoren kennst du?

Verfasst: Mittwoch 21. Januar 2009, 16:31
von madfrog
DasIch hat geschrieben:Was spricht gegen

Code: Alles auswählen

class VirtualDraw(object):
    def draw(self):
        raise NotImplementedError
?
Ich würde sagen im Sinne von Erweiterungen macht es Sinn flexibler auf solche Situationen reagieren zu könnnen, oder? So finde ich ja vielleicht noch nen Weg die Klasse rauszukriegen dessen virtuelle Methode aufgerufen wurde. Dann weiß ich besser wo ich schauen muß.
DasIch hat geschrieben: Außerdem würde man statt

Code: Alles auswählen

raise NotImplementedError, 'my cool message'
# folgendes machen
raise NotImplementedError('my cool message')
Ja das ändere ich. Bin noch nicht so weit was Python angeht.
DasIch hat geschrieben:Außerdem Pythons Dekoratoren kennst du?
Und das ist auch ein guter Tip. Dekoratoren habe ich gerade im abc Modul kennengelernt.


Hier die zweite Umsetzung mit abc:

Code: Alles auswählen

from abc import ABCMeta, abstractmethod

class VirtualDraw(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def draw(self):
        pass

class ImplementedDraw(VirtualDraw):
    def draw(self):
        print "WORKS!"

test1 = VirtualDraw()
#test1 = ImplementedDraw()
test1.draw()
Wobei ich hier nicht mal eine Instanz von VirtualDraw() erstellen kann, sondern er mir gleich nen Fehler wirft.


Kann mir noch wer sagen, wie Python den Aufruf der Klassenmethode genau aufruft? Wird bei instanz.funktion() direkt Klasse.funktion.__call__(instanz) aufgerufen oder passiert da mehr?

Verfasst: Mittwoch 21. Januar 2009, 17:29
von Darii
madfrog hat geschrieben:Kann mir noch wer sagen, wie Python den Aufruf der Klassenmethode genau aufruft? Wird bei instanz.funktion() direkt Klasse.funktion.__call__(instanz) aufgerufen oder passiert da mehr?
Da Pyton zur Kompilierzeit keine Informationen über die Datenstrukturen hat kann Klasse.funktion im Allgemeinen nicht direkt aufgerufen werden. Es wird also zunächst ein ``gettattr(instanz, "funktion")`` ausgeführt, das im Falle einer Methode eine "bound method" zurückgibt(sowas wie ``functools.partial(Klasse.funktion, instanz)``. Diese wird dann wie eine normale Funktion aufgerufen.

Wenn du dich für solche Internas interessierst, hilft dir vielleicht das Modul "dis" weiter.

Code: Alles auswählen

In [46]: def foo(): return instanz.funktion()
   ....: 
         
In [47]: dis.dis(foo)
  1           0 LOAD_GLOBAL              0 (instanz)
              3 LOAD_ATTR                1 (funktion)
              6 CALL_FUNCTION            0
              9 RETURN_VALUE