Return-Type von Dekoratoren

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
sprudel
User
Beiträge: 250
Registriert: Donnerstag 8. März 2007, 17:12

Hallo,

ich frage mich gerade, ob es irgendwie möglich ist, den Return-Type von einem Dekorator zu definieren.
Konkret geht es darum, dass ich eine Funktion mit weiterer Funktionalität erweitern möchte - die Funktion selbst soll in einer Klasse gewrappt werden und dann, für die einfachen Fälle einfach mit __call__ ausgeführt werden können. Ich möchte aber weiterhin auch, wenn ich beispielsweise eine Funktion f annotiert habe, für diese die Codevervollständigung von der Klasse, mit der ich sie dekoriert habe, bekommen. So etwas Ähnliches funktioniert ja auch mit @property. Hat da jemand eine Idee? Vielen Dank.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Der Return-Type eines Dekorators ist immer ein callable, egal ob Funktions- oder Klassen-basiert. Bei dem Rest geht mir einiges durcheinander.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

kbr hat geschrieben: Dienstag 24. Juli 2018, 19:22 Der Return-Type eines Dekorators ist immer ein callable
nicht zwangsläufig, man kann auch einen Decorator so definieren:

Code: Alles auswählen

def black_hole(func):
    return None

@black_hole
def irgendwas():
    print("Hello")
man bekommt natürlich dann einen TypeError: 'NoneType' object is not callable zur Runtime
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
sprudel
User
Beiträge: 250
Registriert: Donnerstag 8. März 2007, 17:12

ThomasL hat geschrieben: Dienstag 24. Juli 2018, 20:09
kbr hat geschrieben: Dienstag 24. Juli 2018, 19:22 Der Return-Type eines Dekorators ist immer ein callable
nicht zwangsläufig, man kann auch einen Decorator so definieren:

Code: Alles auswählen

def black_hole(func):
    return None

@black_hole
def irgendwas():
    print("Hello")
man bekommt natürlich dann einen TypeError: 'NoneType' object is not callable zur Runtime
Das Problem hier ist aber immer noch, dass die Typinferenz irgendwas dann für ein simples Callable hält. Da helfen auch keine Typannotationen von black_hole. Und das ist mein Problem. Gibt es da irgendeinen Trick?
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThomasL: Es muss kein „callable“ sein, aber wenn man kein „callable“ zurückgibt, dann bekommt man grundsätzlich eine Ausnahme‽ Äh… Es *muss* ein „callable“ sein. Sonst ist's kaputt.

@sprudel: Schau Dir mal das `decorator`-Modul von Michele Simionato an. Dann braucht man keine Typannotation selber schreiben um die Signatur der dekorierten Objekte zu ”retten”.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich benutze zum retten functools.wraps, AFAIK macht das Michele’s Modul überflüssig.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

__blackjack__ hat geschrieben: Dienstag 24. Juli 2018, 22:13 @ThomasL: Es muss kein „callable“ sein, aber wenn man kein „callable“ zurückgibt, dann bekommt man grundsätzlich eine Ausnahme‽ Äh… Es *muss* ein „callable“ sein. Sonst ist's kaputt.
Das wollte ich @kbr auf nette Weise so sagen. Mein Post war nicht unbedingt als konstruktiv zu verstehen, eher als Anekdote.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@ThomasL: Danke, es auf die nette Weise anzugehen. Natürlich kann ein callable alles zurückgeben. Im Falle von Dekoratoren ergibt es aber keinen Sinn, etwas anderes als ein callable zurück zu liefern. Von dem Kontext ging ich implizit aus – aber bekanntlich ist ja 'explicit better than implicit'.

@__deets__: tatsächlich, wraps zieht auch __annotations__ nach. Hatte ich noch nicht ausprobiert, da ich type hints bislang vermeide. Ist aber folgerichtig, wenn sie denn schon zum Sprachumfang gehören.
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Rettet das mittlerweile tatsächlich die Signatur der dekorierten Funktion? Die Dokumentation von `decorator` suggeriert etwas anderes und auch die Python-Dokumentation spricht nur von Attributen. Hier bräuchte man dann also wirklich Typannotationen im geretteten `__annotations__`-Attribut.

Hm, habe es gerade mal mit Python 3.5 ausprobiert. `inspect.getargspec()` liefert das falsche Ergebnis, dafür gibt es wohl in Python 3 neu `inspect.signature()` was das richtige Ergebnis liefert. Also ich kann bei Python 2.7 nicht auf `decorator` verzichten. Da ich IPython benutze ist es sowieso installiert. :-)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich muss gestehen ich habe mir das dermaßen ewig her angewöhnt das zu benutzen, dass die Semantik mir nicht mehr geläufig ist - es hat nur die decorator Notwendigkeit entfernt.

Jetzt sehe ich es transportiert nur Namen & Docstring. Das hat aller Wahrscheinlichkeit dann für mich immer gereicht, über Argument Namen und defaults musste ich nie was erfahren.
Benutzeravatar
__blackjack__
User
Beiträge: 13069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Das ist bei Autovervollständigung wie beim OP interessant und ich hatte mir das hauptsächlich wegen generierter Dokumentation angewöhnt. Vielleicht ist Sphinx da mittlerweile auch schlauer, aber damit hatte ich halt mal das Problem das die Signatur in der Dokumentation dann falsch war.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Hier mal ein kleiner Test (Python 3.6):

Code: Alles auswählen

import functools

def hello(f: callable) -> callable:
    """The hello decorator"""
    @functools.wraps(f)
    def wrap(wrap_name: str, option:int=42) -> int:
        """The inner wrapper, with unused option and wrong return type"""
        return 'Hello, ' + f(wrap_name)
    return wrap

@hello
def func(func_name: str, useless:str='default') -> str:
    """Returns a greeting string."""
    return f'greetings, stranger: {func_name}!'

for each in ('name', 'doc', 'annotations', 'defaults'):
    print('{}: {}'.format(each, getattr(func, '__{}__'.format(each))))

Code: Alles auswählen

name: func
doc: Returns a greeting string.
annotations: {'func_name': <class 'str'>, 'useless': <class 'str'>, 'return': <class 'str'>}
defaults: (42,)
functools.wraps übernimmt nicht die defaults.
Was auch richtig ist.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@kbr: die defaults sind auch eine Eigenschaft der dekorierenden Funktion. Name und DocString kann man ohne Gefahr ändern, was ja auch gemacht wird. Die Annotations zu übertragen kann aber schon falsch sein, wenn sich die Parameter ändern, wie in diesem Fall. Dafür kennt `wraps` das Argument `assigned`, das Defaultmäßig diese Metadaten kopiert: '__module__', '__name__', '__qualname__', '__doc__', '__annotations__', wobei in Deinem Fall alles bis auf __annotations__ kopiert werden darf.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sirius3: Stimmt, die __annotations__ sollten genauso wenig wie die __defaults__ kopiert werden, wenn der Dekorator die Signatur ändert. Wobei es sicher sehr überraschend sein kann, wenn eine dekorierte Funktion nicht mehr der Implementierung gemäß aufzurufen ist. Kann man sicherlich machen, aber ich halte das für fragwürdig - oder es braucht sehr gute Gründe, die dann auch an dekorierter Stelle vermerkt sein sollten.
Antworten