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.
Return-Type von Dekoratoren
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")
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
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
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?ThomasL hat geschrieben: ↑Dienstag 24. Juli 2018, 20:09nicht zwangsläufig, man kann auch einen Decorator so definieren:man bekommt natürlich dann einen TypeError: 'NoneType' object is not callable zur RuntimeCode: Alles auswählen
def black_hole(func): return None @black_hole def irgendwas(): print("Hello")
- __blackjack__
- User
- Beiträge: 13100
- 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”.
@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
Das wollte ich @kbr auf nette Weise so sagen. Mein Post war nicht unbedingt als konstruktiv zu verstehen, eher als Anekdote.__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.
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
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
@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.
@__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.
- __blackjack__
- User
- Beiträge: 13100
- 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.
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
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.
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.
- __blackjack__
- User
- Beiträge: 13100
- 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
Hier mal ein kleiner Test (Python 3.6):
functools.wraps übernimmt nicht die defaults.
Was auch richtig ist.
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,)
Was auch richtig ist.
@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.
@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.