Funktionsparameter ist Klasse oder Variable

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
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Hallo zusammen!

Ich habe eine Klasse mit einem Attribut, das irgendeine Zahl ist. Nun möchte ich diese Zahl verarbeiten. Dazu kann ich eine Methode definieren, die etwas mit dem Attribut macht. Der Nachteil ist, dass ich dann immer ein Objekt dieser Klasse erzeugen muss, um diese Methode zu verwenden. Ich würde diese Methode aber gerne auch mit beliebigen anderen Zahlen verwenden, ohne dass mir die Zahl über die Klasse geliefert wird. Daher nun folgender Ansatz:

Code: Alles auswählen

class Beispielklasse:
    def __init__(self, zahl):
        self.zahl = zahl
    
def quadrieren(zahl):
    if isinstance(zahl, Beispielklasse):
        zahl = zahl.zahl
    return(zahl**2)

klasse = Beispielklasse(5)
variable = 6

print("Klassenattribut quadrieren: ", quadrieren(klasse))
print("Klassenattribut quadrieren, Attribut explizit angeben: ", quadrieren(klasse.zahl))
print("Eine Variable quadrieren: ", quadrieren(variable))
Ich schreibe eine Funktion, die die Zahl auf gewünschte Art verwenden kann. Als Parameter kann ich eine beliebige Zahl eingeben oder die Zahl zum Beispiel durch direkte Ansprache des Klassenattributs holen. Oder aber ich zeige der Funktion, wo sie das richtige Attribut findet, wenn sie das Objekt einer Klasse übergeben bekommt. Das wäre mein bevorzugter Ansatz, weil sich der Nutzer keinerlei Gedanken darüber machen muss, wie er an die benötigten Informationen aus der Klasse kommt, er gibt der Funktion einfach das gewünschte Objekt mit und diese erledigt den Rest.

Funktionen tut es ja schon mal. Meine Frage: Ist das sinnvoll gelöst?
Zuletzt geändert von Anonymous am Sonntag 13. November 2016, 18:11, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Ich hab den Eindruck dass du auf das Expression Problem gestoßen bist.

Grundsätzlich gibt es zwei Möglichkeiten, eine hast du gefunden. Die andere wäre jedem Objekt eine entsprechende Methode zu verpassen. Beide sind sinnvoll haben aber unterschiedliche Vor- und Nachteile, die sich darauf beziehen neue Objekte/Funktionen einzuführen.

Grundsätzlich ist dies also nicht verkehrt. Du kannst es allerdings eventuell noch eleganter lösen, indem du functools.singledispatch verwendest, erfordert allerdings mindestens Python 3.4.

Das ganze ist allerdings in deinem Fall nur dann wirklich sinnvoll wenn sich Instanzen von deiner Beispielsklasse auch wirklich wie Zahlen verhalten. Wenn du anfängst überall solche Typprüfungen zu machen ist dies überhaupt nicht sinnvoll. Python hat zwar ein dynamisches Tipsystem aber man sollte damit nicht zu weit gehen.
BlackJack

Ich schliesse mich DasIch an, dass solche Typprüfungen wohl überlegt sein sollten. Du könntest Deiner `Beispielklasse` auch einfach eine `__pow__()`-Methode verpassen. Sollte das semantisch keinen Sinn machen, dann sollte man Exemplare von dieser Klasse vielleicht auch nicht einer `quadrieren()`-Funktion übergeben können.

Alternativ könnte es auch Sinn machen `Beispielklasse` die `__float__()` und/oder `__int__()`-Methode zu spendieren. Dann kann der Aufrufer ``quadrieren(int(klasse))`` oder ``quadrieren(float(klasse))`` schreiben. Er braucht dann nicht wissen wie genau die Umwandlung funktioniert, also zum Beispiel ob/dass es ein `zahl`-Attribut gibt, und für den Leser sieht es weniger nach Magie aus. (`klasse` ist übrigens ein sehr irreführender Name weil keine *Klasse* an den Namen gebunden wird. Was in Python ja möglich ist, weil auch Klassen Werte/Objekte sind.)

Bezüglich `singledispatch`: Das ist zwar erst ab Python 3.4 Teil der Standardbibliothek, aber das gibt es als Backport für frühere 3er und auch für aktuelle 2er Versionen. Und falls man mal über mehr als nur den Typ des ersten Arguments ”dispatchen” möchte, gibt es auch ein `multidispatch`-Modul das man installieren kann.
aug_lager
User
Beiträge: 26
Registriert: Sonntag 4. Januar 2015, 12:01

Vielen Dank für die Tipps! Ich habe mir singledispatch mal angesehen und habe ziemlich Mühe, das zu verstehen. Bezogen auf mein Beispiel bin ich zu folgender Lösung gekommen:

Code: Alles auswählen

from functools import singledispatch

class Beispielklasse:
    def __init__(self, zahl):
        self.zahl = zahl
        
@singledispatch
def quadrieren(zahl):
    return(zahl**2)

@quadrieren.register(Beispielklasse)
def _(arg):
    return arg.zahl**2
 
klasse = Beispielklasse(5)
variable = 6
 
print("Klassenattribut quadrieren: ", quadrieren(klasse))
print("Klassenattribut quadrieren, Attribut explizit angeben: ", quadrieren(klasse.zahl))
print("Eine Variable quadrieren: ", quadrieren(variable))
War das so gemeint? Wenn ja, inwiefern ist das eleganter? Was genau habe ich da eigentlich gemacht? Ich habe eine Funktion "quadrieren" und eine Funktion "_" geschrieben. Die Funktion "_" ist über "@quadrieren.register(Beispielklasse) an die Funktion "quadrieren" gebunden. Jedes Mal, wenn eine Instanz der Klasse Beispielklasse an "quadrieren" übergeben wird, wird die Funktion "_" aufgerufen. So richtig?

Ich finde mein erstes Beispiel verständlicher, was aber einfach meinen Pythonkenntnissen geschuldet sein mag. Aber was mich mehr irritiert ist, dass ich doch in meinem Fall zweimal die gleiche Funktion schreiben würde. Gerade bei ausführlicheren Funktionen fällt doch dann viel doppelter Code an oder?
BlackJack

@aug_lager: Wenn dabei Funktionen mit viel doppeltem Code anfallen, dann hast Du was falsch gemacht. Mal angenommen die erste Funktion wäre umfangreicher, dann würde die zweite Funktion trotzdem nur aus einer Zeile bestehen: Nämlich dem Aufruf der ersten:

Code: Alles auswählen

from functools import singledispatch

class Beispielklasse:
    def __init__(self, zahl):
        self.zahl = zahl
       
@singledispatch
def quadrieren(zahl):
    return zahl**2  # Hier könnte auch deutlich mehr stehen.

@quadrieren.register(Beispielklasse)
def _(arg):
    # Das ist alles, egal wieviel Code in quadrieren stehen mag!
    return quadrieren(arg.zahl)
Was Du mit `singledispatch` im Grunde machst ist für alle Objekten (bei denen das Sinn macht) eine `quadrieren()`-”Methode” zu definieren und diese ”Methode” bei `Beispielklasse` anders/speziell zu implementieren.

Edit: Ich würde den Funktionen übrigens sinnvolle Namen geben, denn irgendwann liest man `_` vielleicht auch mal in einem Traceback und das ist nicht hilfreich. Also zum Beispiel `beispielklasse_quadrieren()` statt `_`.
Benutzeravatar
__LC__
User
Beiträge: 32
Registriert: Dienstag 1. März 2011, 14:08
Wohnort: 127.0.0.1
Kontaktdaten:

Wäre es nicht wie BlackJack schon andeutete einfach sinnvoller der Klasse einfach ein Interface zu verpassen, welche den built-in Typen ähnelt (zu mindestens das was man davon braucht)?

Code: Alles auswählen

class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Number(self.value + other)

    def __sub__(self, other):
        return Number(self.value - other)

    def __mul__(self, other):
        return Number(self.value * other)

    def __pow__(self, x):
        return Number(self.value ** x)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Es kommt IMHO auf den tatsächlich vorliegenden Code an. Für eine Fallunterscheidung von 2 Datentypen (bzw Spezialisierung bei einem bestimmten Typen) würde ich kein `singledispatch` bemühen. Das würde ich erst nehmen, wenn die Fallunterscheidungen wirklich unübersichtlich werden. Falls man das Prüfen auf eine konkrete (Ober-)Klasse nicht im Code haben möchte, dann lässt sich das auch so als Einzeiler erledigen:

Code: Alles auswählen

# Nutzung eines `default`-Wertes
zahl = getattr(arg, 'zahl', arg)

# Sprechendere Form
zahl = arg.zahl if hasattr(arg, 'zahl') else arg
Die Idee, eine von Pythons `__xxx___()`-Methoden zu definieren, falls etwas passendes dabei ist, finde ich auch gut. Aber dann eher so wie BlackJack es vorgeschlagen hat. Dies aber auch nur, wenn ich Einfluss auf die Klasse habe. Falls das ein Datentyp einer fremden API ist, dann würde ich einfach den oben gezeigten Einzeiler benutzen. Das komplette (Neu-)Definieren aller Rechenoperationen hingegen würde ich lieber vermeiden, da mir dies viel zu aufwändig und fehleranfällig wäre.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Frage, die ich mir stellen würde, ist ja die nach dem Sinn. Ist es sinnvoll eine Funktion zu haben, die sowohl Zahlen als auch Instanzen einer bestimmten Klasse als Argument akzeptiert. Was ist der Mehrwert, diese Klasse zu verwenden? Ist es logisch, an einer Stelle sowohl die Klasse als auch Zahlen zu haben? In diesem Beispiel offensichtlich nicht. Dann ist es immer besser, möglichst explizit zu sein: Ich möchte die Zahl-Eigenschaft meiner Instanz benutzen: quadriere(variable.zahl) oder quadriere(float(variable))
Antworten