inspect - Ispeziona Oggetti Vivi
Scopo: Fornisce funzioni per l'introspezione di oggetti in vita e del loro codice sorgente
Il modulo inspect fornisce funzioni per ottenere informazioni su oggetti vivi, compresi moduli, classi, istanze, funzioni e metodi. Le funzioni in questo modulo possono essere usate per recuperare il codice sorgente per una funzione, per trovare gli argomenti per un metodo nello stack, ed estrarre quel tipo di informazioni utili per produrre documentazione di libreria per il codice sorgente.
Modulo di Esempio
Il resto degli esempi in questo articolo usano il file example.py
.
# example.py
# Questo commento appare per primo
# e si trova du due righe.
# Questo commento non viene visualizzato nel risultato di getcomments().
"""File di esempio che serve come base per gli esempi di ispezione.
"""
def module_level_function(arg1, arg2='default', *args, **kwargs):
"""Questa funzione viene dichiarata nel modulo."""
local_variable = arg1 * 2
return local_variable
class A(object):
"""La classe A."""
def __init__(self, name):
self.name = name
def get_name(self):
"Ritorna il nome dell'istanza"
return self.name
instance_of_a = A('sample_instance')
class B(A):
"""Questa è la classe B.
Derivata da A.
"""
# Questo metodo non fa parte di A.
def do_something(self):
"""Esegue qualche operazione"""
def get_name(self):
"Sovrascrive la versione da A"
return 'B(' + self.name + ')'
Ispezionare Moduli
Il primo tipo di introspezione sonda gli oggetti in vita per ottenerne informazioni. Si usi getmembers()
per scoprire gli attributi membro dell'oggetto. I tipi di membro che potrebbero essere ritornati dipendono dal tipo di oggetto analizzato. I moduli possono contenere classi e funzioni, le classi possono contenere metodi e attributi ecc.
Gli argomenti per getmembers()
sono un oggetto da analizzare (modulo, classe o istanza) e una funzione predicato opzionale che viene usata per filtrare gli oggetti ritornati. Il valore ritornato è una lista di tuple con due valori: il nome del membro e il tipo del membro. Il modulo inspect contiene parecchie di queste funzioni predicato con nomi tipo ismodule()
, isclass()
, ecc.
# inspect_getmembers_module.py
import inspect
import example
for name, data in inspect.getmembers(example):
if name.startswith('__'):
continue
print('{} : {!r}'.format(name, data))
Questo esempio stampa i membri del modulo example
. I moduli hanno parecchi attributi privati che sono usati come parte dell'implementazione dell'importazione così come un insieme di __builtins__
. Tutto ciò viene ignorato nel risultato per questo esempio poichè non sono realmente parte del modulo e l'elenco è lungo.
$ python3 inspect_getmembers_module.py A : <class 'example.A'> B : <class 'example.B'> instance_of_a : <example.A object at 0x7fe6709338d0> module_level_function : <function module_level_function at 0x7fe67092f620>
L'argomento predicate
può essere usato per filtrare i tipi di oggetto ritornati.
# inspect_getmembers_module_class.py
import inspect
import example
for name, data in inspect.getmembers(example, inspect.isclass):
print('{} : {!r}'.format(name, data))
Ora solo le classi sono incluse nel risultato.
$ python3 inspect_getmembers_module_class.py A : <class 'example.A'> B : <class 'example.B'>
Ispezionare Classi
Le classi sono analizzate usando getmembers()
allo stesso modo dei moduli, anche se i tipi di membro sono differenti.
# inspect_getmembers_class.py
import inspect
from pprint import pprint
import example
pprint(inspect.getmembers(example.A), width=65)
Visto che non viene applicato alcun filtro, il risultato mostra attributi, metodi, slot e altri membri della classe.
$ python3 inspect_getmembers_class.py [('__class__', <class 'type'>), ('__delattr__', <slot wrapper '__delattr__' of 'object' objects>), ('__dict__', mappingproxy({'__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': 'La classe A.', '__init__': <function A.__init__ at 0x7fdf46b1bd90>, '__module__': 'example', '__weakref__': <attribute '__weakref__' of 'A' objects>, 'get_name': <function A.get_name at 0x7fdf46b1be18>})), ('__dir__', <method '__dir__' of 'object' objects>), ('__doc__', 'La classe A.'), ('__eq__', <slot wrapper '__eq__' of 'object' objects>), ('__format__', <method '__format__' of 'object' objects>), ('__ge__', <slot wrapper '__ge__' of 'object' objects>), ('__getattribute__', <slot wrapper '__getattribute__' of 'object' objects>), ('__gt__', <slot wrapper '__gt__' of 'object' objects>), ('__hash__', <slot wrapper '__hash__' of 'object' objects>), ('__init__', <function A.__init__ at 0x7fdf46b1bd90>), ('__le__', <slot wrapper '__le__' of 'object' objects>), ('__lt__', <slot wrapper '__lt__' of 'object' objects>), ('__module__', 'example'), ('__ne__', <slot wrapper '__ne__' of 'object' objects>), ('__new__', <built-in method __new__ of type object at 0x556d332d5f60>), ('__reduce__', <method '__reduce__' of 'object' objects>), ('__reduce_ex__', <method '__reduce_ex__' of 'object' objects>), ('__repr__', <slot wrapper '__repr__' of 'object' objects>), ('__setattr__', <slot wrapper '__setattr__' of 'object' objects>), ('__sizeof__', <method '__sizeof__' of 'object' objects>), ('__str__', <slot wrapper '__str__' of 'object' objects>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x556d3390dbf8>), ('__weakref__', <attribute '__weakref__' of 'A' objects>), ('get_name', <function A.get_name at 0x7fdf46b1be18>)]
Per trovare i metodi della classe, si usi il predicato isfunction()
. Il predicato ismethod()
riconosce solo metodi connessi di istanze.
# inspect_getmembers_class_methods.py
import inspect
from pprint import pprint
import example
pprint(inspect.getmembers(example.A, inspect.isfunction))
Ora sono ritornati solo i metodi non connessi.
$ python3 inspect_getmembers_class_methods.py [('__init__', <function A.__init__ at 0x7fa2b0a07d90>), ('get_name', <function A.get_name at 0x7fa2b0a07e18>)]
Il risultato per B
include anche la sovrascrittura del metodo get_name()
, e il metodo ereditato __init__()
implementato in A
.
# inspect_getmembers_class_methods_b.py
import inspect
from pprint import pprint
import example
pprint(inspect.getmembers(example.B, inspect.isfunction))
I metodi ereditati da A
tipo __init__()
, sono identificati come metodi di B
.
$ python3 inspect_getmembers_class_methods_b.py [('__init__', <function A.__init__ at 0x7f3da01afd90>), ('do_something', <function B.do_something at 0x7f3da01afea0>), ('get_name', <function B.get_name at 0x7f3da01aff28>)]
Ispezionare Istanze
L'introspezione delle istanze funziona allo stesso modo degli altri oggetti.
# inspect_getmembers_instance.py
import inspect
from pprint import pprint
import example
a = example.A(name='inspect_getmembers')
pprint(inspect.getmembers(a, inspect.ismethod))
Il predicato ismethod()
riconosce due metodi connessi da A
nell'istanza di esempio.
$ python3 inspect_getmembers_instance.py [('__init__', <bound method A.__init__ of <example.A object at 0x7f3ba43b89b0>>), ('get_name', <bound method A.get_name of <example.A object at 0x7f3ba43b89b0>>)]
Stringhe di documentazione
La docstring per un oggetto può essere recuperato con getdoc()
. Il valore di ritorno è nell'attributo __doc__
con i caratteri di tabulazione convertiti in spazi con l'indentazione resa uniforme.
# inspect_getdoc.py
import inspect
import example
print('B.__doc__:')
print(example.B.__doc__)
print()
print('getdoc(B):')
print(inspect.getdoc(example.B))
La seconda riga della docstring è indentata quando viene recuperata direttamente dall'attributo, poi viene spostata al margine sinistro da getdoc()
.
$ python3 inspect_getdoc.py B.__doc__: Questa è la classe B. Derivata da A. getdoc(B): Questa è la classe B. Derivata da A.
Oltre all'effettiva docstring, è possibile recuperare i commenti dal file sorgente dove un oggetto è implementato, se la sorgente è disponibile. La funzione getcomments()
cerca nella sorgente dell'oggetto e trova i commenti sulle righe che precedono l'implementazione.
# inspect_getcomments_method.py
import inspect
import example
print(inspect.getcomments(example.B.do_something))
Le righe ritornate comprendono il prefisso del commento con spazi, tabulazioni ritorni a capo eliminati.
$ python3 inspect_getcomments_method.py # Questo metodo non fa parte di A.
Quando viene passato un modulo a getcomments()
, il valore di ritorno è sempre il primo commento nel modulo.
# inspect_getcomments_module.py
import inspect
import example
print(inspect.getcomments(example))
Le righe contigue dal file di esempio sono incluse come singolo commento, ma non appena appare una riga vuota il commento si interrompe.
$ python3 inspect_getcomments_module.py # example.py # Questo commento appare per primo # e si trova du due righe.
Recuperare il Sorgente
Se il file .py
è disponibile per un modulo, il codice sorgente originale per la classe o il metodo può essere recuperato usando getsource()
e getsourcelines()
.
# inspect_getsource_class.py
import inspect
import example
print(inspect.getsource(example.A))
Quando viene passata una classe, sono inclusi nel risultato tutti i metodi della classe.
$ python3 inspect_getsource_class.py class A(object): """La classe A.""" def __init__(self, name): self.name = name def get_name(self): "Ritorna il nome dell'istanza" return self.name
Per recuperare il sorgente di un singolo metodo, si passi il riferimento del metodo a getsource()
.
# inspect_getsource_method.py
import inspect
import example
print(inspect.getsource(example.A.get_name))
In questo caso, il livello di indentazione originale viene conservato.
$ python3 inspect_getsource_method.py def get_name(self): "Ritorna il nome dell'istanza" return self.name
Si usi getsourcelines()
invece che getsource()
per recuperare le righe del sorgente divise in stringhe individuali.
# inspect_getsourcelines_method.py
import inspect
import pprint
import example
pprint.pprint(inspect.getsourcelines(example.A.get_name))
Il valore di ritorno da getsourcelines()
è una tupla che contiene una lista di stringhe (le righe dal file sorgente), e un numero di riga di partenza nel file sorgente dove appare.
$ python3 inspect_getsourcelines_method.py ([' def get_name(self):\n', ' "Ritorna il nome dell\'istanza"\n', ' return self.name\n'], 23)
Se il file sorgente non è disponibile, getsource()
e getsourcelines()
sollevano un errore IOError
.
Firme di Metodo e Funzione
Oltre alla documentazione per una funzione o metodo, è possibile richiedere una specifica completa degli argomenti che il chiamabile ottiene, compresi i valori predefiniti. La funzione signature()
ritorna una istanza Signature
che contiene informazioni circa gli argomenti della funzione.
# inspect_signature_function.py
import inspect
import example
sig = inspect.signature(example.module_level_function)
print('module_level_function{}'.format(sig))
print('\nDettagli parametro:')
for name, param in sig.parameters.items():
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
print(' {} (positional-only)'.format(name))
elif param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
if param.default != inspect.Parameter.empty:
print(' {}={!r}'.format(name, param.default))
else:
print(' {}'.format(name))
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
print(' *{}'.format(name))
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
if param.default != inspect.Parameter.empty:
print(' {}={!r} (keyword-only)'.format(
name, param.default))
else:
print(' {} (keyword-only)'.format(name))
elif param.kind == inspect.Parameter.VAR_KEYWORD:
print(' **{}'.format(name))
Gli argomenti della funzione sono disponibili tramite l'attributo parameters
di Signature
. parameters
è un dizionario ordinato che mappa i nomi alle istanze di Parameter
che descrivono l'argomento. In questo esempio il primo argomento alla funzione, arg1
non ha un valore predefinito, al contrario di arg2
.
$ python3 inspect_signature_function.py module_level_function(arg1, arg2='default', *args, **kwargs) Dettagli parametro: arg1 arg2='default' *args **kwargs
La firma (Signature
) di una funzione può essere usata da decoratori o altre funzioni per validare input, fornire valori predefiniti differenti, ecc. Scrivere un decoratore di validazione riutilizzabile e sufficientemente generico presenta una sfida particolare, poichè può essere complicato far corrispondere gli argomenti in entrata con i loro nomi per funzioni che accettano una combinazione di argomenti posizionali e nominativi. I metodi bind()
e bind_partial()
forniscono la logica necessaria per la gestione della mappatura. Essi ritornano una istanza di BoundArguments
popolata con gli argomenti associati con i nomi degli argomenti di una funzione specificata.
# inspect_signature_bind.py
import inspect
import example
sig = inspect.signature(example.module_level_function)
bound = sig.bind(
'questo è arg1',
'questo è arg2',
'questo è un argomento supplementare posizionale',
extra_named_arg='value',
)
print('Argomenti:')
for name, value in bound.arguments.items():
print('{} = {!r}'.format(name, value))
print('\nChiamata:')
print(example.module_level_function(*bound.args, **bound.kwargs))
L'istanza di BoundArguments
ha gli attributi args
e kwargs
che possono essere usati per chiamare la funzione usando la sintassi per espandere la tupla e il dizionario come argomenti.
$ python3 inspect_signature_bind.py Argomenti: arg1 = 'questo è arg1' arg2 = 'questo è arg2' args = ('questo è un argomento supplementare posizionale',) kwargs = {'extra_named_arg': 'value'} Chiamata: questo è arg1questo è arg1
Se sono disponibili solo alcuni argomenti, bind_partial()
potrà ancora creare una istanza di BoundArguments
. Potrebbe tuttavia non essere completamente utilizzabile fino a che non sono inclusi gli argomenti mancanti.
# inspect_signature_bind_partial.py
import inspect
import example
sig = inspect.signature(example.module_level_function)
partial = sig.bind_partial(
'questo è arg1',
)
print('Senza predefiniti:')
for name, value in partial.arguments.items():
print('{} = {!r}'.format(name, value))
print('\nCon predefiniti:')
partial.apply_defaults()
for name, value in partial.arguments.items():
print('{} = {!r}'.format(name, value))
apply_defaults()
aggiungerà qualunque valore dai parametri predefiniti.
$ python3 inspect_signature_bind_partial.py Senza predefiniti: arg1 = 'questo è arg1' Con predefiniti: arg1 = 'questo è arg1' arg2 = 'default' args = () kwargs = {}
Gerarchie di Classi
inspect include due metodi per lavorare direttamente con gerarchie di classi. Il primo, getclasstree()
, crea una struttura dati tipo albero basato sulle classi fornite e le loro classi base. Ciascun elemento nella lista restituito è una tupla con una classe e la sua classe base o un'altra lista che contiene tuple per le sottoclassi.
# inspect_getclasstree.py
import inspect
import example
class C(example.B):
pass
class D(C, example.A):
pass
def print_class_tree(tree, indent=-1):
if isinstance(tree, list):
for node in tree:
print_class_tree(node, indent + 1)
else:
print(' ' * indent, tree[0].__name__)
return
if __name__ == '__main__':
print('A, B, C, D:')
print_class_tree(inspect.getclasstree(
[example.A, example.B, C, D])
)
Il risultato di questo esempio è l'albero di ereditarietà per le classi A
, B
, C
e D
. D
appare due volte, visto che eredita sia da C
che da A
.
$ python3 inspect_getclasstree.py A, B, C, D: object A D B C D
Se getclasstree()
viene chiamato con unique
impostato a True
, il risultato è diverso.
# inspect_getclasstree_unique.py
import inspect
import example
from inspect_getclasstree import *
print_class_tree(inspect.getclasstree(
[example.A, example.B, C, D],
unique=True,
))
.
$ python3 inspect_getclasstree_unique.py object A B C D
Ordine di Risoluzione dei Metodi
L'altra funzione per lavorare con gerarchie di classi è getmro()
che ritorna una tupla di classi nell'ordine nel quale dovrebbero essere scorse quando si risolve un attributo che potrebbe essere ereditato da una classe base usando l'Ordine di Risoluzione del Metodo (MRO). Ciascuna classe nella sequenza appare una sola volta.
# inspect_getmro.py
import inspect
import example
class C(object):
pass
class C_First(C, example.B):
pass
class B_First(example.B, C):
pass
print('B_First:')
for c in inspect.getmro(B_First):
print(' {}'.format(c.__name__))
print()
print('C_First:')
for c in inspect.getmro(C_First):
print(' {}'.format(c.__name__))
Il risultato dimostra la natura della ricerca di MRO come "prima la profondità". Per B_First
, A
viene prima di C
nell'ordine di ricerca, visto che B
è derivato da A
.
$ python3 inspect_getmro.py B_First: B_First B A C object C_First: C_First C B A object
Stack e Frame
Oltre alla introspezione degli oggetti di codice, inspect comprende anche funzioni per ispezionare l'ambiente di esecuzione mentre un programma sta girando. La maggior parte di queste funzioni lavorano con le chiamate allo stack, e operano su *call frame* (subroutine all'interno di una chiamata di stack). Questi oggetti conservano il contesto di esecuzione corrente, incluso i riferimenti al codice in esecuzione, l'operazione che si sta eseguendo così come i valori delle variabili locali e globali. Tipicamente queste informazioni sono usate per costruire *traceback* quando vengono sollevate eccezioni. Possono anche essere utili per registrazioni o per debug di programmi, visto che i frame dello stack possono essere interrogati per scoprire i valori degli argomenti passati alle funzioni.
currentframe()
ritorna il frame alla sommità dello stack (per la funzione corrente).
# inspect_currentframe.py
import inspect
import pprint
def recurse(limit, keyword='predefinito', *, kwonly='deve essere nominativo'):
local_variable = '.' * limit
keyword = 'modificato valore dell\'argomento'
frame = inspect.currentframe()
print('riga {} di {}'.format(frame.f_lineno,
frame.f_code.co_filename))
print('locali:')
pprint.pprint(frame.f_locals)
print()
if limit <= 0:
return
recurse(limit - 1)
return local_variable
if __name__ == '__main__':
recurse(2)
I valori dell'argomento di recurse()
sono inclusi nel dizionario delle variabili locali del frame.
$ python3 inspect_currentframe.py riga 11 di inspect_currentframe.py locali: {'frame': <frame object at 0x7f69a4292048>, 'keyword': "modificato valore dell'argomento", 'kwonly': 'deve essere nominativo', 'limit': 2, 'local_variable': '..'} riga 11 di inspect_currentframe.py locali: {'frame': <frame object at 0x55f7c1545b78>, 'keyword': "modificato valore dell'argomento", 'kwonly': 'deve essere nominativo', 'limit': 1, 'local_variable': '.'} riga 11 di inspect_currentframe.py locali: {'frame': <frame object at 0x55f7c15ba038>, 'keyword': "modificato valore dell'argomento", 'kwonly': 'deve essere nominativo', 'limit': 0, 'local_variable': ''}
Usando stack()
è anche possibile accedere a tutti i frame degli stack dal frame corrente al primo chiamante. Questo esempio è simile a quello mostrato in precedenza, eccetto che attende fino a che viene raggiunta la fine della ricorsione per stampare le informazioni dello stack.
# inspect_stack.py
import inspect
import pprint
def show_stack():
for level in inspect.stack():
print('{}[{}]\n -> {}'.format(
level.frame.f_code.co_filename,
level.lineno,
level.code_context[level.index].strip(),
))
pprint.pprint(level.frame.f_locals)
print()
def recurse(limit):
local_variable = '.' * limit
if limit <= 0:
show_stack()
return
recurse(limit - 1)
return local_variable
if __name__ == '__main__':
recurse(2)
L'ultima parte del risultato rappresenta il programma principale, all'esterno della funzione recurse()
.
$ python3 inspect_stack.py inspect_stack.py[8] -> for level in inspect.stack(): {'level': FrameInfo(frame=<frame object at 0x7f78927b7980>, filename='inspect_stack.py', lineno=8, function='show_stack', code_context=[' for level in inspect.stack():\n'], index=0)} inspect_stack.py[21] -> show_stack() {'limit': 0, 'local_variable': ''} inspect_stack.py[23] -> recurse(limit - 1) {'limit': 1, 'local_variable': '.'} inspect_stack.py[23] -> recurse(limit - 1) {'limit': 2, 'local_variable': '..'} inspect_stack.py[28] -> recurse(2) {'__builtins__': <module 'builtins' (built-in)>, '__cached__': None, '__doc__': None, '__file__': 'inspect_stack.py', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f78940137f0>, '__name__': '__main__', '__package__': None, '__spec__': None, 'inspect': <module 'inspect' from '/usr/lib/python3.5/inspect.py'>, 'pprint': <module 'pprint' from '/usr/lib/python3.5/pprint.py'>, 'recurse': <function recurse at 0x7f7892764c80>, 'show_stack': <function show_stack at 0x7f789405e048>}
Ci sono altre funzioni per costruire liste di frame in contesti differenti, tipo quando viene elaborata una eccezione. Si veda la documentazione per trace()
, getouterframes()
, e getinnerframes()
per maggiori dettagli.
Interfaccia da Riga di Comando
Il modulo inspect include anche una interfaccia da riga di comando per ottenere dettagli circa gli oggetti senza dover scrivere le chiamate in un programma Python separato. L'input è un nome di modulo e oggetto (opzionale) all'interno del modulo. Il risultato predefinito è il codice sorgente per l'oggetto passato. Usando l'argomento --details
verranno stampati i metadati in luogo del sorgente.
$ python3 -m inspect -d example Target: example Origin: .../example.py Cached: .../__pycache__/example.cpython-35.pyc Loader: <_frozen_importlib_external.SourceFileLoader object at 0x7f8ce5eed908>
$ python3 -m inspect -d example:A Target: example:A Origin: .../example.py Cached: .../__pycache__/example.cpython-35.pyc Line: 17
$ python3 -m inspect -d example:A.get_name Target: example:A.get_name Origin: .../example.py Cached: .../__pycache__/example.cpython-35.pyc Line: 23
Vedere anche:
- inspect
- La documentazione della libreria standard per questo modulo.
- Note di portabilità per inspect
- Python 2.3 Method Resolution Order
- Documentazione per l'ordine di risoluzione del metodo usato da Python 2.3 e superiore
- pyclbr
- Il modulo pyclbr fornisce accesso ad alcune delle stesse informazioni di inspect esaminando il modulo senza importarlo.
- PEP 362
- Funzione per Firma Oggetto