Jeszcze małe dopytanko. Z wnętrza funkcji __name__ nie da nam nazwy tej funkcji tylko moduł inspect daje takie możliwości?
Rozwińmy zatem trochę temat. Dobieranie się do __name__ z wnętrza funkcji działa podobnie jak i dla innych atrybutów:
Kopiuj
>>> def foo():
... print(foo.__name__)
...
>>> foo()
foo
Tylko, że to się mija z celem, skoro w środku funkcji foo musimy odnieść się do obiektu o nazwie foo, to równie dobrze można by napisać:
Kopiuj
def foo():
print("foo")
Pytanie się zatem nasuwa, czy można by się ze środka funkcji dobrać do niej samej w sposób uniwersalny. Analogiczny problem pojawia się jeżeli używamy funkcji rekurencyjnych. Załóżmy, na przykład że mamy taką funkcję:
Kopiuj
def fibonacci(n):
if n <= 1:
return n
else:
return(fibonacci(n-1) + fibonacci(n-2))
Jeżeli chcielibyśmy zmienić nazwę takiej funkcji z fibonacci na fib to musimy to zrobić w trzech miejscach zamiast w jednym. Pytanie zatem, czy możemy tego uniknąć. Odpowiedź brzmi (o ile mi wiadomo) nie. Dokument PEP 3130 zaproponował wprowadzenie słowa kluczowego __function__, który w sposób uniwersalny odnosiłby się do bieżącej funkcji wewnątrz bloku kodu (https://peps.python.org/pep-3130/) ale został odrzucony. Można by próbować obchodzić to ograniczenie analizując ramki stosu (na tym polegają rozwiązania podane w https://stackoverflow.com/questions/5067604/determine-function-name-from-within-that-function), ale to są hacki i wolałbym na nich nie polegać, szczególnie gdy mamy funkcję rekurencyjną i nasz stos jest wielopiętrowy.
Dodajmy jeszcze, że __name__ można modyfikować. Robi to na przykład functools.wraps. Załóżmy, że mamy dekorator twice, który wywołuje dekorowaną funkcję dwa razy. Jeżeli użyjemy go na jakiejś funkcji to stracimy oryginalną nazwę i dokumentację:
Kopiuj
>>> def twice(f):
... def wrapper(*args, **kwrags):
... f(*args, **kwrags)
... f(*args, **kwrags)
... return wrapper
...
>>> @twice
... def foo():
... """Function documentation"""
... print("Hello world")
...
>>> foo()
Hello world
Hello world
>>> print(foo.__name__)
wrapper
>>> print(foo.__doc__)
None
Rozwiązaniem jest zastosowanie functools.wraps, które modyfikuje atrybuty __name__ i __doc__ zwracanego wrappera tak, aby wskazywały na oryginał:
Kopiuj
>>> import functools
>>>
>>> def twice(f):
... @functools.wraps(f)
... def wrapper(*args, **kwrags):
... f(*args, **kwrags)
... f(*args, **kwrags)
... return wrapper
...
>>> @twice
... def foo():
... """Function documentation"""
... print("Hello world")
...
>>> print(foo.__name__)
foo
>>> print(foo.__doc__)
Function documentation
O ile sam __name__ jest jak widać modyfikowalny, to istnieje również atrybut tylko do odczytu __code__.co_name:
Kopiuj
>>> foo.__code__.co_name
'foo'
Moduł inspect operuje bodajże na tych wartościach, a __name__ jest dodawane dla wygody i stosowania na co dzień.