Überschreiben der Python-Methode, spielt die Signatur eine Rolle?

76

Sagen wir, ich habe

class Super():
  def method1():
    pass

class Sub(Super):
  def method1(param1, param2, param3):
      stuff

Ist das richtig? Gehen Aufrufe von method1 immer an die Unterklasse? Mein Plan ist es, 2 Unterklassen zu haben, die jeweils Methode1 mit unterschiedlichen Parametern überschreiben

asdasasdsa
quelle

Antworten:

58

In Python sind Methoden nur Schlüssel-Wert-Paare im Wörterbuch, das an die Klasse angehängt ist. Wenn Sie eine Klasse von einer Basisklasse ableiten, sagen Sie im Wesentlichen, dass der Methodenname im ersten abgeleiteten Klassenwörterbuch und dann im Basisklassenwörterbuch angezeigt wird. Um eine Methode zu "überschreiben", deklarieren Sie die Methode einfach erneut in der abgeleiteten Klasse.

Was ist, wenn Sie die Signatur der überschriebenen Methode in der abgeleiteten Klasse ändern? Alles funktioniert ordnungsgemäß, wenn sich der Aufruf auf der abgeleiteten Instanz befindet. Wenn Sie den Aufruf jedoch auf der Basisinstanz ausführen, wird eine Fehlermeldung angezeigt, da die Basisklasse für denselben Methodennamen eine andere Signatur verwendet.

Es gibt jedoch häufige Szenarien, in denen die abgeleitete Klassenmethode zusätzliche Parameter haben soll und der Methodenaufruf auch ohne Fehler auf der Basis funktionieren soll. Dies wird als "Liskov-Substitutionsprinzip" (oder LSP ) bezeichnet, das garantiert, dass Personen, die von der Basisinstanz zur abgeleiteten Instanz oder umgekehrt wechseln, ihren Code nicht überarbeiten müssen. Um dies in Python zu tun, müssen Sie Ihre Basisklasse mit der folgenden Technik entwerfen:

class Base:
    # simply allow additional args in base class
    def hello(self, name, *args, **kwargs):
        print("Hello", name)

class Derived(Base):
      # derived class also has unused optional args so people can
      # derive new class from this class as well while maintaining LSP
      def hello(self, name, age=None, *args, **kwargs):
          super(Derived, self).hello(name, age, *args, **kwargs) 
          print('Your age is ', age)

b = Base()
d = Derived()

b.hello('Alice')        # works on base, without additional params
b.hello('Bob', age=24)  # works on base, with additional params
d.hello('Rick')         # works on derived, without additional params
d.hello('John', age=30) # works on derived, with additional params

Oben wird gedruckt:

    Hallo Alice
    Hallo Bob
    Hallo Rick
    Dein Alter ist Keine
    Hallo John
    Ihr Alter ist 30
. Spielen Sie mit diesem Code

Shital Shah
quelle
1
Vielen Dank für dieses Update einige Jahre später mit einer viel klareren und umsetzbareren Diskussion und einem funktionierenden Beispiel plus Laufstall!
Nealmcb
1
Sollen wir das Alter in die Unterschrift von Hallo nach setzen *args? Wenn nicht, d.hello("John", "blue", age=30)funktioniert Code wie nicht. Ich meine, im Allgemeinen sollten Positionsargumente immer vor kwargs definiert werden
Quickbeam2k1
1
Ich denke, die Antwort auf Ihre Frage hängt davon ab, ob wir zulassen möchten, dass Parameter mit Standardoption ( agehier) auch nach Position oder nur als kwarg festgelegt werden. Beachten Sie, dass Ihr Vorschlag nur in Python 3 funktioniert, wo Sie nur Argumente für Schlüsselwörter angeben können *. Siehe zB das .
Nerxis
1
Gute Antwort, aber in "LSP, das garantiert, dass der Teil" umgekehrt "falsch ist, wenn eine Person von der Basisinstanz zur abgeleiteten Instanz oder umgekehrt wechselt.
Michał Jabłoński
43

Python lässt dies zu, aber wenn method1()es aus externem Code ausgeführt werden soll, sollten Sie dies möglicherweise noch einmal überdenken, da es gegen LSP verstößt und daher nicht immer ordnungsgemäß funktioniert.

Ignacio Vazquez-Abrams
quelle
1
Liegt die Verletzung von LSP an der Tatsache, dass Sub.method1 3 Argumente akzeptiert, während Super.method1 keine akzeptiert, wodurch sie tatsächlich unterschiedliche Schnittstellen sind?
Unode
2
@Unode: Richtig. Dies könnte gelöst werden, indem die Argumente der Methode der Unterklasse alle Standardwerte haben, aber dann erfahren Sie, welche Standardeinstellungen angemessen wären.
Ignacio Vazquez-Abrams
3
Aha. Aber dann nur zur Klarstellung. Wenn die übergeordnete Methode1 so definiert wurde, dass Super.method1(param1=None, param2=None, param3=None)sie immer noch gegen LSP verstößt, wenn sie in Unterklassen als Sub.method1(param1, param2, param3)richtig definiert ist ? Da Attribute in einem Fall obligatorisch sind, im anderen nicht. Nach meinem Verständnis wäre die einzige Möglichkeit, LSP nicht zu verletzen, ohne die Unterklassenschnittstelle zu ändern, die Parameter ohne Standardwerte auf dem übergeordneten Element zu haben. Bin ich richtig oder überinterpretiere ich den LSP?
Unode
3
@Unode: Auch richtig. Wenn der Vertrag in der Unterklasse weniger restriktiv wird, verstößt dies gegen LSP.
Ignacio Vazquez-Abrams
außer wann method1ist __init__, wo LSP nicht gilt
Joel
2

In Python sind alle Klassenmethoden "virtuell" (in Bezug auf C ++). Wenn Sie also im Fall Ihres Codes eine Superklasse aufrufen method1()möchten, muss dies sein:

class Super():
    def method1(self):
        pass

class Sub(Super):
    def method1(self, param1, param2, param3):
       super(Sub, self).method1() # a proxy object, see http://docs.python.org/library/functions.html#super
       pass

Und die Methodensignatur spielt eine Rolle. Sie können eine Methode wie diese nicht aufrufen:

sub = Sub()
sub.method1() 
Zaur Nasibov
quelle
2

Sie könnten so etwas tun, wenn es in Ordnung ist, Standardargumente zu verwenden:

>>> class Super():
...   def method1(self):
...     print("Super")
...
>>> class Sub(Super):
...   def method1(self, param1="X"):
...     super(Sub, self).method1()
...     print("Sub" + param1)
...
>>> sup = Super()
>>> sub = Sub()
>>> sup.method1()
Super
>>> sub.method1()
Super
SubX
Zitrax
quelle
-2

Ja. Aufrufe von "method1" gehen immer in die Unterklasse. Die Methodensignatur in Python besteht nur aus dem Namen und nicht aus der Argumentliste.

Elektito
quelle
2
Falsche Antwort. Die Methodensignatur ist immer wichtig, da in C / C ++ keine Funktionsüberladung auftritt.
Zaur Nasibov
Ich meine, Python berücksichtigt die Argumentliste nicht, um zu entscheiden, welche Methode aufgerufen werden soll, aber ich denke, Sie haben Recht, wenn Sie sie aus diesem Blickwinkel betrachten!
Elektito