Załóżmy, że napisałem dekoratora, który robi coś bardzo ogólnego. Na przykład może przekonwertować wszystkie argumenty na określony typ, przeprowadzić rejestrowanie, zaimplementować zapamiętywanie itp.
Oto przykład:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
Jak dotąd wszystko dobrze. Jest jednak jeden problem. Dekorowana funkcja nie zachowuje dokumentacji oryginalnej funkcji:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Na szczęście istnieje obejście:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
Tym razem nazwa funkcji i dokumentacja są poprawne:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Ale nadal jest problem: podpis funkcji jest nieprawidłowy. Informacja "* args, ** kwargs" jest prawie bezużyteczna.
Co robić? Przychodzą mi do głowy dwa proste, ale wadliwe obejścia:
1 - Dołącz poprawny podpis w dokumencie:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
To jest złe z powodu duplikacji. Podpis nadal nie będzie poprawnie wyświetlany w automatycznie generowanej dokumentacji. Łatwo jest zaktualizować funkcję i zapomnieć o zmianie dokumentu lub popełnieniu literówki. [ I tak, zdaję sobie sprawę z faktu, że dokumentacja już powiela treść funkcji. Proszę zignoruj to; funny_function to tylko przypadkowy przykład. ]
2 - Nie używaj dekoratora lub używaj dekoratora specjalnego przeznaczenia do każdego konkretnego podpisu:
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
Działa to dobrze w przypadku zestawu funkcji, które mają identyczny podpis, ale ogólnie jest bezużyteczne. Jak powiedziałem na początku, chcę mieć możliwość używania dekoratorów w sposób całkowicie ogólny.
Szukam rozwiązania w pełni ogólnego i automatycznego.
Pytanie brzmi więc: czy istnieje sposób na edycję podpisu dekorowanej funkcji po jej utworzeniu?
W przeciwnym razie, czy mogę napisać dekorator, który wyodrębnia sygnaturę funkcji i używa tych informacji zamiast "* kwargs, ** kwargs" podczas konstruowania funkcji dekorowanej? Jak wyodrębnić te informacje? Jak mam skonstruować dekorowaną funkcję - z exec?
Jakieś inne podejścia?
inspect.Signaturedodało do zajmowania się dekorowanymi funkcjami.