Sollte Python alle callables gleich behandeln?

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
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Neulich bin ich über die Tatsache gestolpert, dass in Python callable nicht gleich callable ist. In Klassendefinitionen werden nämlich nur „echte“ Funktionen zu Methoden. Gegeben seien folgende unschuldige Klassen:

Code: Alles auswählen

class Value(object):
    from_str = None
    to_str   = str
    def __init__(self, value):
        self.value = self.from_str(value)
    def __str__(self):
        return self.to_str(self.value)
class Integer(Value):
    from_str = int
Funktioniert super:

Code: Alles auswählen

>>> assert str(Integer("42")) == "42"
Dann bin ich aber auf die doofe Idee gekommen für meine beiden „Factory-Funktionen“ wirklich Funktionen zu verwenden:

Code: Alles auswählen

class Boolean(Value):
    from_str = lambda value: bool(int(value))
    to_str   = lambda value: str(int(value))

Code: Alles auswählen

>>> assert str(Boolean("1")) == "1"
TypeError: <lambda>() takes exactly 1 argument (2 given)
Upps, da hab ich wohl ausversehen eine Methode definiert(hätte ich mir denken können). Aber dann frage ich mich, warum das vorher geklappt hat. Wäre es nicht wesentlich konsistenter wenn Python nicht zwischen zwei callables unterscheiden würde? Vor allem da das nicht nur mit Typen funktioniert, sondern auch mit allen anderen Objekten die „callable“ aber keine echte Funktion sind.

Code: Alles auswählen

class Integer(Value):
    from_str = functools.partial(base=8)
Funktioniert wider erwarten, aber auch nur, weil partial als Instanz einer Klasse und nicht als closure implementiert wurde(edit: war das ein Trugschluss, ich stelle gerade fest, dass das Ergebnis von partial wirklich den selben Typ wie eine Funktion hat). Ich finde das sehr unintuitiv und für eine dynamisch typisierte Sprache im allgemeinen auch tödlich, wenn man sich so an Typen klammert. An der Stelle bin ich übrigens gleich in die 2. Falle getappt. Ich hielt mich für besonders schlau das Problem einfach wie folgt zu umgehen:

Code: Alles auswählen

class Value(object):
    from_str = None
    to_str   = str
    def __init__(self, value):
        self.value = self.__class__.from_str(value)
    def __str__(self):
        return self.__class__.to_str(self.value)
Denkste! In Python 2.x gibt es ja noch die „unbound“-Method die einen völlig unnötigen Typcheck auf das erste übergebene Argument macht. Mich würde mal interessieren wozu man überhaupt „unbound methods“ und erst recht diesen Typcheck macht?

Das 2. Problem wurde ja mit Python 3 abgehandelt(auch wenn ich stark dafür wäre das auch in 2.x zu beseitigen). Wie denkt ihr über die 1. Sache, sollte man Python derart abändern, dass alle callables die Attribute von Klassen sind in der Instanz zu Methoden werden? Vorteil: Wäre nur konsistent und logisch(duck typing). Nachteil: Wird sicherlich existierenden Code geben der sich darauf verlässt, dass dies nicht so ist.
BlackJack

@Darii: Hier wird sich nicht unbedingt an Typen geklammert, sondern wie bei "duck typing" üblich an Verhalten. Der Typ dürfte egal sein, was wichtig ist, ist das Vorhandensein einer entsprechend implementierten `__get__()`-Methode. Funktionen haben die, Klassen/Typen nicht.

Und mal davon ab ist ``def f(x): return 2*x`` semantisch identisch zu ``f = lambda x: return 2*x``. *Ich* würde mich beschweren, wenn sich das in Klassen unterschiedlich verhalten würden.

Lösung wäre hier doch einfach `staticmethod()`, oder?
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

BlackJack hat geschrieben:@Darii: Hier wird sich nicht unbedingt an Typen geklammert, sondern wie bei "duck typing" üblich an Verhalten. Der Typ dürfte egal sein, was wichtig ist, ist das Vorhandensein einer entsprechend implementierten `__get__()`-Methode. Funktionen haben die, Klassen/Typen nicht.
Interessant wusste ich nicht, ändert aber nichts grundsätzliches an meiner Kritik(ich könnte mich an dieser Stelle auch darüber auslassen warum __get__ die Klasse übergeben bekommt und __set__ nicht, classproperties werden so ziemlich unmöglich ohne Metaklassen).
Und mal davon ab ist ``def f(x): return 2*x`` semantisch identisch zu ``f = lambda x: return 2*x``. *Ich* würde mich beschweren, wenn sich das in Klassen unterschiedlich verhalten würden.
Darum gings mir doch auch gar nicht. Worum es mir ging, steht im Threadtitel.
Lösung wäre hier doch einfach `staticmethod()`, oder?
Nein, denn ich will keine statische Methode haben und ich hab auch keine Lust mit Wrappern auf Attribute loszugehen. Und würde es keine unbound-Methods geben, wäre das auch kein Problem. Aber ich hab jetzt eine bessere Methode gefunden:

Code: Alles auswählen

self.__class__.__dict__['from_str']
Schöner wäre es zwar wenn ich direkt ein Attribut der Instanz aufrufen könnte und von __dict__ wollte ich mich auch fern halten, aber alles andere wäre zu viel Aufwand.
BlackJack

@Darii: Ich dachte Du wolltest, dass `Boolean.from_str()` und `Boolean.to_str()` keine Methoden sein sollen, sondern einfache Funktionen!? Warum dann nicht `staticmethod()`? "Statische Methoden" sind ja keine Methoden, sondern eben Funktionen.

Den Verweis auf den Titel habe ich nicht so ganz verstanden. Behandelt Python nicht alle "callables" gleich? Das beobachtete Verhalten hat ja nichts mit `__call__()` zu tun, und das ist ja das Einzige was ein Objekt zum "callable" macht.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

BlackJack hat geschrieben:@Darii: Ich dachte Du wolltest, dass `Boolean.from_str()` und `Boolean.to_str()` keine Methoden sein sollen, sondern einfache Funktionen!? Warum dann nicht `staticmethod()`? "Statische Methoden" sind ja keine Methoden, sondern eben Funktionen.
Ich stör' mich einfach nur an dem Wort „Methode“ und wollte das umgehen und dabei bin ich halt an dieser dämlichen Typprüfung gescheitert. ;) Hat eben auch Nachteile, wenn man keine separaten Namensräume für Attribute und Methoden hat.
Den Verweis auf den Titel habe ich nicht so ganz verstanden. Behandelt Python nicht alle "callables" gleich? Das beobachtete Verhalten hat ja nichts mit `__call__()` zu tun, und das ist ja das Einzige was ein Objekt zum "callable" macht.
Jein. Problematisch finde ich, dass Methoden in Python ja „nur Funktionen“ sind, man aber andererseits Methoden nicht einfach durch callables(die sich von der Aufrufsemantik ja nicht von Funktionen unterscheiden) ersetzen kann, da Funktionen sich in Python anscheinend nicht nur dadurch auszeichnen, dass man sie „callen“ kann, sondern auch dadurch, dass die Deskriptoren sind.
Antworten