Seite 1 von 1

Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 12:10
von NPC
Hallo Forum,
ich würde gerne Versuchen in Python meinen eigenen "overload"-Dekorator zu schreiben. Soweit so gut. Mein Problem tritt nun auf, wenn ich eine Methode überladen will und als Eingangsvariable ein Objekt der gleichen Klasse habe. Hat jemand eine Idee, wie ich das Problem umgehen kann (außer eine andere Sprache verwenden ;) ). Vielen Dank im voraus! (Code und Beispiel folgen)

Die Overload-Klasse

Code: Alles auswählen

class Overload:
    functions = {}

    def __init__(self, *types):
        self.types = types

    def wrapper(self, *args):
        if args:
            type_list = tuple(map(lambda x: type(x), args))
        else:
            type_list = (None,)

        if type_list in self.functions[self.name].keys():
            return self.functions[self.name][type_list](*args)
        else:
            raise AttributeError(
                "Overloaded method for Args:\n'" + "%s" * len(type_list) % type_list + "'\nDoes not exist")

    def __call__(self, f):
        self.name = f.__name__
        type_list = tuple(self.types[:])
        if self.name not in self.functions.keys():
            self.functions[self.name] = {}
        self.functions[self.name][type_list] = f
        return self.wrapper


und hier ein Beispiel bei welchem das Problem auftritt:

Code: Alles auswählen

class Dummy:

    def __init__(self, value):
        self.value = value
    
    @Overload(Dummy, Dummy)
    def __add__(self, other):
        return self.value + other.value
        
    @Overload(Dummy, int)
    def __add__(self, other):
        return self.value + other
Der auftrettende Fehler sagt mir, dass Dummy nicht definiert ist (was ja irgendwie Sinn mach). Was kann ich hier statt 'Dummy' eingeben?
Hat es hier einen Vorteil statt "type() == <type>", "isinstance(<var>, <type>)" zu benutzen?

Und hier noch schnell der Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "...\test.py", line 58, in <module>
    class Dummy:
  File "...\test.py", line 63, in Dummy
    @Overload(Dummy, Dummy)
NameError: name 'Dummy' is not defined

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 12:37
von Sirius3
@NPC: warum machst Du einen Unterschied zwischen Funktionen mit 0 Argumenten und einem Argument? Ein Lambda-Ausdruck mit nur einem Funktionsaufruf mit dem selben Argument ist etwas unnötig. Globale Klassenvariablen sollte man nicht benutzen. So kannst Du nur eine Klasse haben, die Deine __add__-Methode überlädt, sonst gibt es Chaos. Schau mal nach, wie es property mit Getter und Setter macht.
Das keys ist in `typelist in ...` unnötig, weil man direkt `in` auf Wörterbücher anwendet. Die ganze if-Abfrage ist aber ungeschickt, besser den KeyError abfangen. Und keinen ArithmeticError werfen, sondern einen TypeError.

Die unseligen Annotationen benutzen in Fällen wie Deinem, glaube ich, Strings mit dem Klassennamen.

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 14:10
von nezzcarth
NPC hat geschrieben: Samstag 23. Februar 2019, 12:10 Hat jemand eine Idee, wie ich das Problem umgehen kann (außer eine andere Sprache verwenden ;) ).
Vielleicht mit 'functools.singledispatch'? https://docs.python.org/3/library/funct ... ledispatch

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 15:38
von __blackjack__
@NPC: Typannotationen verwenden für solche Fälle einfach Zeichenketten.

Code: Alles auswählen

from typing import Any


class Node:
    
    def __init__(self, value: Any, next_node: 'Node' = None):
        self.value = value
        self.next = next_node
Was ich nicht so ganz verstehe ist warum Du auch `self` berücksichtigst? Hat das nicht *immer* den Klassennamen als Wert? Wobei an der Stelle dann auch der Unterschied zwischen `type()` und `isinstance()` interessant wird.

@nezzcarth: `singledispach()` berücksichtigt nur das erste Argument, was bei Methoden immer `self` ist und dabei dann immer den gleichen Typ hat, es sei denn Du willst in einer Basisklasse schon verschiedene Verhalten einer Methode bei Subklassen kodieren, was ich schräg finden würde. Und man hätte natürlich auch das Problem wie man in der Definition der Basisklasse die Subklassen angeben soll, die man erst definieren kann wenn die Definition der Basisklasse abgeschlossen ist. :-)

Es gibt aber das `multipledispatch`-Modul im Package Index, das kann auch mit Methoden umgehen.

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 16:40
von NPC
@Sirius3
"warum machst Du einen Unterschied zwischen Funktionen mit 0 Argumenten und einem Argument?"
Ich dachte es sei schöner, wenn ich explizit None angeben muss, falls die Methode kein Argument bekommen soll. Weiterhin wollte ich es so schreiben, dass sich auch Funktionen überladen lassen. Außerdem benutze ich doch *args also unterscheide ich zwischen 0 und 1+ Argumenten oder irre ich mich?

"Globale Klassenvariablen sollte man nicht benutzen. So kannst Du nur eine Klasse haben, die Deine __add__-Methode überlädt, sonst gibt es Chaos. "
Ahh, verdammt! Danke für den Tipp. Ich wollte umgehen, dass man eine Instanz erzeugen und dann der Instanz die Methoden übergeben muss aber jetzt sehe ich das Problem.

"Das keys ist in `typelist in ...` unnötig, weil man direkt `in` auf Wörterbücher anwendet. Die ganze if-Abfrage ist aber ungeschickt, besser den KeyError abfangen. Und keinen ArithmeticError werfen, sondern einen TypeError."

Ok ich dachte es sei prinzipiell besser ifs zu nutzen anstelle das Programm in einen Fehler laufen lassen. Kannst du mir erklären warum das besser ist? (ehrlich gemeinte Frage :) )

Das ArithmeticError war auch dumm... Da sollte eigentlich ein AttributeError stehen um zu zeigen, dass die Methode so nicht existiert.

"Die unseligen Annotationen benutzen in Fällen wie Deinem, glaube ich, Strings mit dem Klassennamen."
Sorry ich verstehe den Satz leider nicht. Kannst du mir das kurz genauer erklären bitte.

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 16:46
von NPC
@__blackjack__

"Was ich nicht so ganz verstehe ist warum Du auch `self` berücksichtigst? Hat das nicht *immer* den Klassennamen als Wert? Wobei an der Stelle dann auch der Unterschied zwischen `type()` und `isinstance()` interessant wird."
Das hat zwei Gründe:
1. Wollte ich das auch auf Funktionen verwenden können
2. Gibt es ja @staticmethod , dann habe ich kein self mehr

Leider verstehe ich nicht ganz was du mir mit den Typnotationnen sagen willst. (Ich steh aber auch oft mal auf der Leitung :) )
Kannst du mir das kurz genauer erklären. Das Typing an sich kenne ich so aber wie mir das im Problem hilft oder was ich unschön gelöst habe verstehe ich leider gerade nicht. Danke schonmal :)

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 17:00
von __blackjack__
@NPC: Dann würde ich ja im Code Methoden und andere aufrufbare Objekte unterscheiden, denn den Typ von `self` immer angeben zu müssen, zudem immer irgendwie speziell weil es den Typ selbst ja erst nach der Klassendefinition gibt, ist irgendwie unschön.

Mit den „unseligen Annotationen“ meinte Sirius3 wohl das gleiche wie ich mit dem Typannotationsbeispiel. Das braucht in der `Node`-Klasse für die Typannotation in der `__init__()` ja schon die Klasse selbst, die ja aber erst verfügbar ist, *nachdem* die Klasse definiert wurde. Also genau das Problem was Du auch hast, Du brauchst `Dummy` bevor es `Dummy` gibt. Und bei Typannotationen ist das halt so gelöst, dass man statt eines Typs auch eine Zeichenkette mit dem Namen des Typs angeben kann. Siehe `next_node`-Argument in der Typsignatur in meinem Beispiel.

Andere Projekte machen das auch so. SQLAlchemy hat bei ORM-Deklarationen die rekursiv sind, ja das gleiche Problem und auch da wird das mit Zeichenketten gelöst die den Klassennamen enthalten.

Re: Methoden mittels Dekoratoren überladen

Verfasst: Samstag 23. Februar 2019, 18:39
von Sirius3
Exceptions benutzt man dann, wenn es sich um eine Ausnahme handelt, wenn der Fall eintritt, und bei Dir sollte ja normalerweise eine passende Funktion definiert sein.
Mit den ganzen Anpassungen würde sich ›wrapper‹ so vereinfachen:

Code: Alles auswählen

    def wrapper(self, *args):
        type_list = tuple(map(type, args))
        try:
            func = self.functions[self.name][type_list]
        except KeyError:
            raise TypeError(
                "Overloaded method for Args\n   %s\ndoes not exist.'" % ','.join(type_list)
            )
        return func(*args)