inspect - Ispezione di oggetti in tempo reale.

Scopo Il modulo inspect fornisce le funzioni per l'introspezione degli oggetti in tempo reale e del loro codice sorgente.
Versione Python Aggiunto nella 2.1, con aggiornamenti nella 2.3 e 2.5

Il modulo inspect fornisce le funzioni per ottenere informazioni riguardo agli oggetti in tempo reale, compresi moduli, classi, istanze, funzioni e metodi. Si possono usare le funzioni di questo modulo per ottenere il codice sorgente originale per una funzione, per cercare i parametri per un metodo sullo stack, per estrarre quel tipo di informazioni utili per produrre documentazione di libreria per il proprio codice sorgente. Il mio (Doug Hellmann - n.d.t.) modulo CommandLineApp usa inspect per determinare le opzioni valide per un programma da riga di comando, così come per rilevare un qualsiasi parametro ed il suo proprio nome in modo che i programmi da riga di comando siano auto-documentanti ed il testo di aiuto venga generato automaticamente.

Informazioni sul Modulo

Il primo tipo di introspezione supportata consente di ispezionare gli oggetti in tempo reale per apprendere informazioni su di essi. Ad esempio è possibile scoprire le clessi e le funzioni in un modulo, i metodi di una classe, ecc.. Si inizierà con i dettagli a livello di modulo, quindi si scenderà fino a livello funzione.

Per determinare come l'interprete tratterà e caricherà un file come modulo, si usa getmoduleinfo(). Si passa il nome del file come unico parametro, ed il valore restituito è una tupla che comprende il nome base del modulo, il suffisso del file, la modalità che verrà usata per leggere il file, ed il tipo di modulo così come definito nel modulo imp. E' importante notare che la funzione guarda solo il nome del file, e non verifica in realtà se il file esiste o cerca di leggere il file.

import imp
import inspect
import sys

if len(sys.argv) >= 2:
    filename = sys.argv[1]
else:
    filename = 'example.py'

try:
    (name, suffix, mode, mtype)  = inspect.getmoduleinfo(filename)
except TypeError:
    print "Non si riesce a determinare il tipo di %s" % filename
else:
    mtype_name = { imp.PY_SOURCE:'sorgente',
                   imp.PY_COMPILED:'compilato',
                   }.get(mtype, mtype)

    mode_description = { 'rb':'(lettura-binario)',
                         'U':'(universal newline)',
                         }.get(mode, '')

    print 'NOME     :', name
    print 'SUFFISSO :', suffix
    print "MODALITA':", mode, mode_description
    print 'MTYPE    :', mtype_name

Ecco alcune esecuzioni di esempio:

$ python inspect_getmoduleinfo.py example.py

NOME     : example
SUFFISSO : .py
MODALITA': U (universal newline)
MTYPE    : sorgente

$ python inspect_getmoduleinfo.py readme.txt

Non si riesce a determinare il tipo di inspect/readme.txt

$ python inspect_getmoduleinfo.py notthere.pyc

NOME     : notthere
SUFFISSO : .pyc
MODALITA': rb (lettura-binario)
MTYPE    : compilato

Il Modulo Example

I restanti esempi per questo tutorial usano un singolo file sorgente di esempio: example.py, che viene riportato di seguito.

#!/usr/bin/env python

# Questo commento compare per primo
# e si sviluppa su due righe

# Questo commento non viene mostrato nell'output di getcomments()

"""File di esempio che funge da base per gli esempi di inspect."""

def module_level_function(arg1, arg2='default', *args, **kwargs):
    """Questa funzione viene dichiarata a livello di modulo."""
    local_variable = arg1
    return

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('istanza_di_esempio')

class B(A):
    """Questa e' la classe B.
    Derivata da A.
    """

    # Questo metodo non fa parte di A.
    def do_something(self):
        """Fa qualche cosa"""
        pass

    def get_name(self):
        "Versione da A sovrascritta"
        return 'B(' + self.name + ')'

Moduli

E' possibile ispezionare gli oggetti in tempo reale per determinare i loro componenti usando getmembers(). I parametri per getmembers() sono un oggetto da analizzare (un modulo, una classe, una istanza) ed una funzione predicato opzionale che viene usata per filtrare gli oggetti restituiti. Il valore di ritorno è una lista di tuple con due valori: il nome del membro, ed il tipo dello stesso. Il modulo inspect comprende parecchie di queste funzioni predicato con nomi tipo ismodule(), isclass() ecc. E' possibile fornire anche le proprie funzioni predicato.

I tipi dei membri che potrebbero essere restituiti dipendono dal tipo di oggetto analizzato. I moduli possono contenere classi e funzioni; le classi possono contenere metodi ed attributi; e così via.

import inspect

import example

for name, data in inspect.getmembers(example):
    if name == '__builtins__':
        continue
    print '%s :' % name, repr(data)

Questo esempio stampa i membri del modulo example. I moduli hanno un insieme di __builtins__, i quali sono ignorati nell'output per questo esempio poichè essi non sono effettivamente parte del modulo e l'elenco è lungo.

$ python inspect_getmembers_module.py

A : 
B : 
__doc__ : 'File di esempio che funge da base per gli esempi di inspect.'
__file__ : '/home/robby/pydev/pymotw-it/dumpscripts/insp/example.py'
__name__ : 'example'
__package__ : None
instance_of_a : 
module_level_function : 

Il parametro predicato può essere usato per filtrare i tipi degli oggetti ritornati.

import inspect

import example

for name, data in inspect.getmembers(example, inspect.isclass):
    print '%s :' % name, repr(data)

Si noti che solo le classi sono ora comprese nell'output.

$ python inspect_getmembers_module_class.py

A : 
B : 

Classi

Le classi sono analizzate usando getmembers() allo stesso modo dei moduli, sebbene i tipi dei membri siano diversi.

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A))

Visto che non viene applicato alcun filtro, l'output mostra gli attributi, i metodi, gli slot, ed altri membri della classe:

$ python inspect_getmembers_class.py

[('__class__', ),
 ('__delattr__', ),
 ('__dict__', ),
 ('__doc__', 'La classe A.'),
 ('__format__', ),
 ('__getattribute__', ),
 ('__hash__', ),
 ('__init__', ),
 ('__module__', 'example'),
 ('__new__', ),
 ('__reduce__', ),
 ('__reduce_ex__', ),
 ('__repr__', ),
 ('__setattr__', ),
 ('__sizeof__', ),
 ('__str__', ),
 ('__subclasshook__',
  ),
 ('__weakref__', ),
 ('get_name', )]

Per trovare i metodi di una classe, si usa il predicato ismethod():

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A, inspect.ismethod))
$ python inspect_getmembers_class_methods.py

[('__init__', ),
 ('get_name', )]

Se si osserva la classe B, si nota la sovrascrittura di get_name() così come il nuovo metodo ed il metodo ereditato __init__() implementato in A.

import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.B, inspect.ismethod))

Si noti che sebbene __init__() sia ereditato da A, esso viene identificato come metodo di B.

$ python inspect_getmembers_class_methods_b.py

[('__init__', ),
 ('do_something', ),
 ('get_name', )]

Stringhe di Documentazione

Le docstring di un oggetto possono essere recuperate con getdoc(). Il valore restituito è l'attributo __doc__ con i tabulatori convertiti in spazi con l'indentazione resa uniforme.

import inspect
import example

print 'B.__doc__:'
print example.B.__doc__
print
print 'getdoc(B):'
print inspect.getdoc(example.B)

Si noti la differenza nell'indentazione sulla seconda riga della docstring:

$ python inspect_getdoc.py

B.__doc__:
Questa è la classe B.
    E' derivata da A.


getdoc(B):
Questa è la classe B.
E' derivata da A.

Oltre alla effettiva docstring, è anche possibile recuperare i commenti dal file sorgente dove è implementato un oggetto, se la sorgente è disponibile. La funzione getcomments() cerca nella sorgente dell'oggetto e trova i commenti sulle righe che precedono l'implementazione.

import inspect
import example

print inspect.getcomments(example.B.do_something)

Le righe restituite comprendono il prefisso del commento, ma un qualsiasi prefisso whitespace (i.e. gli spazi di indentazione) viene rimosso

$ python inspect_getcomments_method.py

# Questo metodo non è parte di A.

Quando un modulo viene passato a getcomments(), il valore restituito è sempre il primo commento nel modulo.

import inspect
import example

print inspect.getcomments(example)

Si noti che righe conigue dal file example sono comprese come singolo commento, ma non appena compare una riga vuota il commento viene interrotto.

$ python inspect_getcomments_module.py

# Questo commento compare per primo
# e si sviluppa su due righe

Recuperare la Sorgente

Se il file .py per un modulo è disponibile, il codice sorgente originale per la classe od il metodo può essere recuperato usando getsource() e getsourcelines().

import inspect
import example

print inspect.getsource(example.A.get_name)

In qusto caso il livello di indentazione originale viene mantenuto.

$ python inspect_getsource_method.py

    def get_name(self):
        "Ritorna il nome dell'istanza."
        return self.name

Quando una classe viene passata, tutti i metodi della classe sono inclusi nell'output.

import inspect
import example

print inspect.getsource(example.A)
$ python 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

Se occorre che le righe della sorgente vengano divise, può essere più facile usare getsourcelines() in luogo di getsource(). Il valore restituito da getsourcelines() è una tupla che contiene una lista di stringhe (le righe dal file sorgente), ed un numero di riga di partenza nel file dove compare la sorgente.

import inspect
import pprint
import example

pprint.pprint(inspect.getsourcelines(example.A.get_name))
$ python inspect_getsourcelines_method.py

(['    def get_name(self):\n',
  '        "Ritorna il nome dell\'istanza."\n',
  '        return self.name\n'],
 19)

Se il file sorgente non è disponibile, getsource() e getsourcelines() sollevano un IOError

Parametri di Metodo e Funzione

Oltre alla documentazione per una funzione o metodo, è anche possibile richiedere una specifica completa dei parametri che il chiamabile riceve, compresi i valori predefiniti. La funzione getargspec() restituisce una tupla che contiene l'elenco dei nomi di parametro posizionale, il nome di tutti i parametri posizionali variabili (es. *args), i nomi di tutti i parametri nominali variabili (es. **kwds), ed i valori predefiniti per i parametri. Se ci sono valori predefiniti, essi si abbinano dalla fine dell'elenco dei parametri posizionali.

import inspect
import example

arg_spec = inspect.getargspec(example.module_level_function)
print 'NOMEI   :', arg_spec[0]
print '*       :', arg_spec[1]
print '**      :', arg_spec[2]
print 'predef. :', arg_spec[3]

args_with_defaults = arg_spec[0][-len(arg_spec[3]):]
print 'parametri & predefiniti:', zip(args_with_defaults, arg_spec[3])

Si noti che il primo parametro, arg1, non ha un valore predefinito. L'unico predefinito quindi viene di conseguenza abbinato ad arg2.

$ python inspect_getargspec_function.py

NOMEI   : ['arg1', 'arg2']
*       : args
**      : kwargs
predef. : ('default',)
parametri & predefiniti: [('arg2', 'default')]

Gerarchie di classe

Il modulo inspect comprende due metodi per lavorare direttamente con le gerarchie di classe. Il primo, getclasstree(). crea una struttura dati tipo albero usando liste e tuple annidate in base alle classi fornite ed alle loro classi base. Ogni elemento in una lista restituita può essere una tupla con una classe e la sua classe base, oppure un'altra lista che contiene tuple per le sottoclassi.

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]))

L'output per questo esempio è un "albero" di ereditarietà per le classi A, B, C e D. Si noti che D compare due volte, visto che eredita sia da C che da A.

$ python inspect_getclasstree.py

A, B, C, D:
 object
   A
     D
     B
       C
         D

Se si chiama getclasstree() con unique=True, il risultato è diverso.

import inspect
import example
from inspect_getclasstree import *

print_class_tree(inspect.getclasstree([example.A, example.B, C, D],
                                      unique=True,
                                      ))

In questo caso, D compare una sola volta nell'output:

$ python inspect_getclasstree_unique.py

 object
   A
     B
       C
         D

Ordine di Risoluzione del Metodo

L'altra funzione per lavorare con le gerarchie di classe è getmro(), che restituisce una tupla di classi nell'ordine nel quale dovrebbero essere scorse quando si risolve un attributo che potrebbe essere ereditato da una classe base. Ogni classe nella sequenza appare una sola volta.

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 '\t', c.__name__
print
print 'C_First:'
for c in inspect.getmro(C_First):
    print '\t', c.__name__

Il risultato dimostra la natura "prima-in-profondità" della ricerca MRO. Per B_First, A è davanti a C nell'ordine di ricerca, visto che B è derivata da A.

$ python inspect_getmro.py

B_First:
	B_First
	B
	A
	C
	object

C_First:
	C_First
	C
	B
	A
	object

Gli Stack ed i Frame

Oltre all'introspezoine di oggetti di codice, inspect comprende funzioni per ispezionare l'ambiente di esecuzione mentre un programma sta girando. La maggior parte di queste funzioni lavorano con la chiamata allo stack, ed operano su "frame di chiamata". Ogni record di frame nello stack è una tupla di sei elementi che contiene l'oggetto frame, il nome del file dove si trova il codice, il numero di riga in detto file per la riga che si sta attualmente eseguendo, il nome della funzione che è stata chiamata, un elenco di righe di contesto dal file sorgente, e l'indice all'interno di quell'elenco della riga corrente. In genere questo tipo di informazioni sono utili per costruire dei traceback quando vengono sollevate eccezioni. Può anche essere utile quando si esegue il debug di programmi, visto che i frame dello stack possono essere interrogati per scoprire i valori dei parametri passati alle funzioni.

currentframe() restituisce il frame alla sommità dello stack (per la funzione corrente). getargvalues() restituisce una tupla con i nomi dei parametri, i nomi dei parametri variabili, ed un dizionario con i valori locali dal frame. Combinando questi valori, si possono vedere i parametri delle funzioni e le variabili locali in diversi punti nello stack di chiamata.

import inspect

def recurse(limit):
    local_variable = '.' * limit
    print limit, inspect.getargvalues(inspect.currentframe())
    if limit <= 0:
        return
    recurse(limit - 1)
    return

if __name__ == '__main__':
    recurse(3)

Il valore di local_variable viene incluso nelle variabii locali del frame anche se non è un parametro della funzione.

$ python inspect_getargvalues.py

$ python2.6 inspect_getargvalues.py
3 ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '...', 'limit': 3})
2 ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '..', 'limit': 2})
1 ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '.', 'limit': 1})
0 ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '', 'limit': 0})

Usando stack() è anche possibile accedere a tutti i frame dello stack dal frame corrente fino al primo chiamante. Questo esempio è simile a quello precedente, eccetto il fatto che attende fino a che la ricorsione termina per stampare le informazioni dello stack.

import inspect

def recurse(limit):
    local_variable = '.' * limit
    if limit <= 0:
        for frame, filename, line_num, func, source_code, source_index in inspect.stack():
            print '%s[%d]\n  -> %s' % (filename, line_num, source_code[source_index].strip())
            print inspect.getargvalues(frame)
            print
        return
    recurse(limit - 1)
    return

if __name__ == '__main__':
    recurse(3)

L'ultima parte dell'output rappresenta il programma principale, al di fuori della funzione ricorsiva.

$ python inspect_stack.py

inspect_stack.py[9]
  -> for frame, filename, line_num, func, source_code, source_index in inspect.stack():
ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '', 'line_num': 9, 'frame': , 'filename': 'inspect_stack.py', 'limit': 0, 'func': 'recurse', 'source_index': 0, 'source_code': ['        for frame, filename, line_num, func, source_code, source_index in inspect.stack():\n']})

inspect_stack.py[14]
  -> recurse(limit - 1)
ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '.', 'limit': 1})

inspect_stack.py[14]
  -> recurse(limit - 1)
ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '..', 'limit': 2})

inspect_stack.py[14]
  -> recurse(limit - 1)
ArgInfo(args=['limit'], varargs=None, keywords=None, locals={'local_variable': '...', 'limit': 3})

inspect_stack.py[18]
  -> recurse(3)
ArgInfo(args=[], varargs=None, keywords=None, locals={'__builtins__': ,
'__file__': 'inspect_stack.py',
'inspect': ,
'recurse': , '__package__': None, '__name__': '__main__',
'__doc__': None})

Ci sono altre funzioni per costruire liste di frame in diversi contesti, tipo quando viene elaborata una eccezione. Si veda la documentazione per trace, getoutergrames() e getinnerframes() per maggiori dettagli.

Vedere anche:

inspect
La documentazione della libreria standard per questo modulo
CommandLineApp
Classe base per applicazioni da riga di comando orientate agli oggetti.
Ordine di risoluzione del Metodo in Python 2.3
Documentazione per l'ordine di Risoluzione del Metodo C3 usata da Python 2.3 e successivo.