inspect — Inspeccionar objetos en vivo

Propósito:El módulo inspect proporciona funciones para introspección en objetos vivos y su código fuente.

El módulo inspect proporciona funciones para aprender sobre objetos en vivo, incluidos módulos, clases, instancias, funciones y métodos. Las funciones de este módulo se pueden usar para recuperar el código fuente original de una función, ver los argumentos de un método en la pila y extraer el tipo de información útil para producir documentación de la biblioteca para el código fuente.

Módulo de ejemplo

El resto de los ejemplos para esta sección usan este archivo de ejemplo, example.py.

example.py
# This comment appears first
# and spans 2 lines.

# This comment does not show up in the output of getcomments().

"""Sample file to serve as the basis for inspect examples.
"""


def module_level_function(arg1, arg2='default', *args, **kwargs):
    """This function is declared in the module."""
    local_variable = arg1 * 2
    return local_variable


class A(object):
    """The A class."""

    def __init__(self, name):
        self.name = name

    def get_name(self):
        "Returns the name of the instance."
        return self.name


instance_of_a = A('sample_instance')


class B(A):
    """This is the B class.
    It is derived from A.
    """

    # This method is not part of A.
    def do_something(self):
        """Does some work"""

    def get_name(self):
        "Overrides version from A"
        return 'B(' + self.name + ')'

Inspeccionando Módulos

El primer tipo de sondas de introspección son objetos vivos para aprender sobre ellos. Usa getmembers() para descubrir los atributos de miembro del objeto. Los tipos de miembros que se pueden devolver dependen del tipo de objeto escaneado. Los módulos pueden contener clases y funciones; las clases pueden contener métodos y atributos; y así.

Los argumentos de getmembers() son un objeto para escanear (un módulo, clase o instancia) y una función de predicado opcional que se utiliza para filtrar los objetos devueltos. El valor de retorno es una lista de tuplas con dos valores: el nombre del miembro y el tipo del miembro. El módulo inspect incluye varias funciones de predicado con nombres como ismodule(), isclass(), etc.

inspect_getmembers_module.py
import inspect

import example

for name, data in inspect.getmembers(example):
    if name.startswith('__'):
        continue
    print('{} : {!r}'.format(name, data))

Esta muestra imprime los miembros del módulo example. Los módulos tienen varios atributos privados que se utilizan como parte de la implementación de importación, así como un conjunto de __builtins__. Todos estos se ignoran en la salida de este ejemplo porque en realidad no son parte del módulo y la lista es larga.

$ python3 inspect_getmembers_module.py

A : <class 'example.A'>
B : <class 'example.B'>
instance_of_a : <example.A object at 0x1045a6978>
module_level_function : <function module_level_function at
0x1045be8c8>

El argumento predicate se puede usar para filtrar los tipos de objetos devueltos.

inspect_getmembers_module_class.py
import inspect

import example

for name, data in inspect.getmembers(example, inspect.isclass):
    print('{} : {!r}'.format(name, data))

Ahora, solo las clases se incluyen en la salida.

$ python3 inspect_getmembers_module_class.py

A : <class 'example.A'>
B : <class 'example.B'>

Inspección de clases

Las clases se escanean usando getmembers() de la misma manera que los módulos, aunque los tipos de miembros son diferentes.

inspect_getmembers_class.py
import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A), width=65)

Como no se aplica ningún filtrado, la salida muestra los atributos, métodos, ranuras y otros miembros de la clase.

$ python3 inspect_getmembers_class.py

[('__class__', <class 'type'>),
 ('__delattr__',
  <slot wrapper '__delattr__' of 'object' objects>),
 ('__dict__',
  mappingproxy({'__dict__': <attribute '__dict__' of 'A'
objects>,
                '__doc__': 'The A class.',
                '__init__': <function A.__init__ at
0x1045b3158>,
                '__module__': 'example',
                '__weakref__': <attribute '__weakref__' of 'A'
objects>,
                'get_name': <function A.get_name at
0x1045b31e0>})),
 ('__dir__', <method '__dir__' of 'object' objects>),
 ('__doc__', 'The A class.'),
 ('__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 0x1045b3158>),
 ('__init_subclass__',
  <built-in method __init_subclass__ of type object at
0x101d12d58>),
 ('__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 0x100996700>),
 ('__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
0x101d12d58>),
 ('__weakref__', <attribute '__weakref__' of 'A' objects>),
 ('get_name', <function A.get_name at 0x1045b31e0>)]

Para encontrar los métodos de una clase, usa el predicado isfunction(). El predicado ismethod() solo reconoce métodos vinculados de instancias.

inspect_getmembers_class_methods.py
import inspect
from pprint import pprint

import example

pprint(inspect.getmembers(example.A, inspect.isfunction))

Solo los métodos no vinculados se devuelven ahora.

$ python3 inspect_getmembers_class_methods.py

[('__init__', <function A.__init__ at 0x1045b2158>),
 ('get_name', <function A.get_name at 0x1045b21e0>)]

La salida para B incluye la anulación para get_name() así como el nuevo método, y el método heredado __init__() implementado en A.

inspect_getmembers_class_methods_b.py
import inspect
from pprint import pprint

import example

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

Los métodos heredados de A, como __init__(), se identifican como métodos de B.

$ python3 inspect_getmembers_class_methods_b.py

[('__init__', <function A.__init__ at 0x103dc5158>),
 ('do_something', <function B.do_something at 0x103dc5268>),
 ('get_name', <function B.get_name at 0x103dc52f0>)]

Inspección de instancias

La introspección de instancias funciona de la misma manera que otros objetos.

inspect_getmembers_instance.py
import inspect
from pprint import pprint

import example

a = example.A(name='inspect_getmembers')
pprint(inspect.getmembers(a, inspect.ismethod))

El predicado ismethod() reconoce dos métodos vinculados de A en la instancia de ejemplo.

$ python3 inspect_getmembers_instance.py

[('__init__', <bound method A.__init__ of <example.A object at 0
x101d9c0f0>>),
 ('get_name', <bound method A.get_name of <example.A object at 0
x101d9c0f0>>)]

Cadenas de documentación

La cadena de documentación de un objeto se puede recuperar con getdoc(). El valor de retorno es el atributo __doc__ con tabuladores expandidos a espacios y con sangría 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 segunda línea de la cadena de documentación se sangra cuando se recupera directamente a través del atributo, pero se mueve al margen izquierdo mediante getdoc().

$ python3 inspect_getdoc.py

B.__doc__:
This is the B class.
    It is derived from A.


getdoc(B):
This is the B class.
It is derived from A.

Además de la cadena de documentación real, es posible recuperar los comentarios del código fuente donde se implementa un objeto, si el código fuente está disponible. La función getcomments() mira el código fuente del objeto y encuentra comentarios en las líneas que preceden a la implementación.

inspect_getcomments_method.py
import inspect
import example

print(inspect.getcomments(example.B.do_something))

Las líneas devueltas incluyen el prefijo de comentario con cualquier prefijo de espacio en blanco eliminado.

$ python3 inspect_getcomments_method.py

# This method is not part of A.

Cuando se pasa un módulo a getcomments(), el valor de retorno es siempre el primer comentario en el módulo.

inspect_getcomments_module.py
import inspect
import example

print(inspect.getcomments(example))

Las líneas contiguas del archivo de ejemplo se incluyen como un solo comentario, pero tan pronto como aparece una línea en blanco, el comentario se detiene.

$ python3 inspect_getcomments_module.py

# This comment appears first
# and spans 2 lines.

Recuperar el código fuente

Si el archivo .py está disponible para un módulo, el código fuente original para la clase o método se puede recuperar usando getsource() y getsourcelines().

inspect_getsource_class.py
import inspect
import example

print(inspect.getsource(example.A))

Cuando se pasa una clase, todos los métodos para la clase se incluyen en la salida.

$ python3 inspect_getsource_class.py

class A(object):
    """The A class."""

    def __init__(self, name):
        self.name = name

    def get_name(self):
        "Returns the name of the instance."
        return self.name

Para recuperar el código fuente de un único método, pasa la referencia del método a getsource().

inspect_getsource_method.py
import inspect
import example

print(inspect.getsource(example.A.get_name))

El nivel de sangría original se conserva en este caso.

$ python3 inspect_getsource_method.py

    def get_name(self):
        "Returns the name of the instance."
        return self.name

Usa getsourcelines() en lugar de getsource() para recuperar las líneas de código fuente original divididas en cadenas individuales.

inspect_getsourcelines_method.py
import inspect
import pprint
import example

pprint.pprint(inspect.getsourcelines(example.A.get_name))

El valor de retorno de getsourcelines() es una tuple que contiene una lista de cadenas (las líneas del código fuente) y un número de línea de inicio en el archivo donde aparece el código fuente.

$ python3 inspect_getsourcelines_method.py

(['    def get_name(self):\n',
  '        "Returns the name of the instance."\n',
  '        return self.name\n'],
 23)

Si el código fuente no está disponible, getsource() y getsourcelines() generan un IOError.

Firmas de métodos y funciones

Además de la documentación para una función o método, es posible solicitar una especificación completa de los argumentos que toma el invocable, incluidos los valores predeterminados. La función signature() devuelve una instancia de Signature que contiene información sobre los argumentos de la función.

inspect_signature_function.py
import inspect
import example

sig = inspect.signature(example.module_level_function)
print('module_level_function{}'.format(sig))

print('\nParameter details:')
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))

Los argumentos de la función están disponibles a través del atributo parameters de la Signature. parameters es un diccionario ordenado que asigna los nombres de los parámetros a las instancias de Parameter que describen el argumento. En este ejemplo, el primer argumento de la función, arg1, no tiene un valor predeterminado, mientras que arg2 sí.

$ python3 inspect_signature_function.py

module_level_function(arg1, arg2='default', *args, **kwargs)

Parameter details:
  arg1
  arg2='default'
  *args
  **kwargs

Los decoradores u otras funciones pueden usar la Signature para una función para validar entradas, proporcionar diferentes valores predeterminados, etc. Sin embargo, escribir un decorador de validación adecuadamente genérico y reutilizable tiene un desafío especial, porque puede ser complicado hacer coincidir argumentos entrantes con sus nombres para funciones que aceptan una combinación de argumentos con nombre y posicionales. Los métodos bind() y bind_partial() proporcionan la lógica necesaria para manejar la asignación. Devuelven una instancia BoundArguments rellenada con los argumentos asociados con los nombres de los argumentos de una función específica.

inspect_signature_bind.py
import inspect
import example

sig = inspect.signature(example.module_level_function)

bound = sig.bind(
    'this is arg1',
    'this is arg2',
    'this is an extra positional argument',
    extra_named_arg='value',
)

print('Arguments:')
for name, value in bound.arguments.items():
    print('{} = {!r}'.format(name, value))

print('\nCalling:')
print(example.module_level_function(*bound.args, **bound.kwargs))

La instancia BoundArguments tiene los atributos args y kwargs que se pueden usar para llamar a la función usando la sintaxis para expandir la tupla y el diccionario en la pila como argumentos.

$ python3 inspect_signature_bind.py

Arguments:
arg1 = 'this is arg1'
arg2 = 'this is arg2'
args = ('this is an extra positional argument',)
kwargs = {'extra_named_arg': 'value'}

Calling:
this is arg1this is arg1

Si solo hay algunos argumentos disponibles, bind_partial() seguirá creando una instancia de BoundArguments. Es posible que no se pueda utilizar por completo hasta que se agreguen los argumentos restantes.

inspect_signature_bind_partial.py
import inspect
import example

sig = inspect.signature(example.module_level_function)

partial = sig.bind_partial(
    'this is arg1',
)

print('Without defaults:')
for name, value in partial.arguments.items():
    print('{} = {!r}'.format(name, value))

print('\nWith defaults:')
partial.apply_defaults()
for name, value in partial.arguments.items():
    print('{} = {!r}'.format(name, value))

apply_defaults() agregará cualquier valor de los parámetros predeterminados.

$ python3 inspect_signature_bind_partial.py

Without defaults:
arg1 = 'this is arg1'

With defaults:
arg1 = 'this is arg1'
arg2 = 'default'
args = ()
kwargs = {}

Jerarquías de clase

inspect incluye dos métodos para trabajar directamente con jerarquías de clases. El primero, getclasstree(), crea una estructura de datos en forma de árbol basada en las clases que se le dan y sus clases base. Cada elemento de la lista devuelta es una tupla con una clase y sus clases base u otra lista que contiene tuplas para subclases.

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

El resultado de este ejemplo es el árbol de herencia para las clases A, B, C y D. D aparece dos veces, ya que hereda tanto de C como de A.

$ python3 inspect_getclasstree.py

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

Si se llama a getclasstree() con unique establecido en un valor verdadero, la salida es diferente.

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

Esta vez, D solo aparece en la salida una vez:

$ python3 inspect_getclasstree_unique.py

 object
   A
     B
       C
         D

Orden de resolución de métodos

La otra función para trabajar con jerarquías de clases es getmro(), que devuelve una tupla de clases en el orden en que deben analizarse al resolver un atributo que podría heredarse de una clase base utilizando el Orden de resolución de métodos (MRO). Cada clase en la secuencia aparece solo una vez.

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

Este resultado demuestra la naturaleza de «profundidad primero» de la búsqueda MRO. Para B_First, A también aparece antes de C en el orden de búsqueda, porque B se deriva de A.

$ python3 inspect_getmro.py

B_First:
  B_First
  B
  A
  C
  object

C_First:
  C_First
  C
  B
  A
  object

La pila y los marcos

Además de la introspección de los objetos de código, inspeccionar incluye funciones para inspeccionar el entorno de tiempo de ejecución mientras se ejecuta un programa. La mayoría de estas funciones funcionan con la pila de llamadas y operan en marcos de llamadas. Los objetos de marco contienen el contexto de ejecución actual, incluidas las referencias al código que se ejecuta, la operación que se ejecuta, así como los valores de las variables locales y globales. Por lo general, dicha información se utiliza para crear trazas cuando se generan excepciones. También puede ser útil para iniciar sesión o al depurar programas, ya que los marcos de la pila se pueden interrogar para descubrir los valores de los argumentos pasados a las funciones.

currentframe() devuelve el marco en la parte superior de la pila (para la función actual).

inspect_currentframe.py
import inspect
import pprint


def recurse(limit, keyword='default', *, kwonly='must be named'):
    local_variable = '.' * limit
    keyword = 'changed value of argument'
    frame = inspect.currentframe()
    print('line {} of {}'.format(frame.f_lineno,
                                 frame.f_code.co_filename))
    print('locals:')
    pprint.pprint(frame.f_locals)
    print()
    if limit <= 0:
        return
    recurse(limit - 1)
    return local_variable

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

Los valores de los argumentos para recurse() se incluyen en el diccionario del marco de variables locales.

$ python3 inspect_currentframe.py

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x10458d408>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 2,
 'local_variable': '..'}

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x101b1ba18>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 1,
 'local_variable': '.'}

line 14 of inspect_currentframe.py
locals:
{'frame': <frame object at 0x101b2cdc8>,
 'keyword': 'changed value of argument',
 'kwonly': 'must be named',
 'limit': 0,
 'local_variable': ''}

Al usar stack(), también es posible acceder a todos los marcos de la pila desde el marco actual hasta la primera función que llama. Este ejemplo es similar al mostrado anteriormente, excepto que espera hasta llegar al final de la recursión para imprimir la información de la pila.

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)

La última parte de la salida representa el programa principal, fuera de la función recursive().

$ python3 inspect_stack.py

inspect_stack.py[11]
  -> for level in inspect.stack():
{'level': FrameInfo(frame=<frame object at 0x1045823f8>,
filename='inspect_stack.py', lineno=11, function='show_stack',
code_context=['    for level in inspect.stack():\n'], index=0)}

inspect_stack.py[24]
  -> show_stack()
{'limit': 0, 'local_variable': ''}

inspect_stack.py[26]
  -> recurse(limit - 1)
{'limit': 1, 'local_variable': '.'}

inspect_stack.py[26]
  -> recurse(limit - 1)
{'limit': 2, 'local_variable': '..'}

inspect_stack.py[31]
  -> recurse(2)
{'__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': 'Inspecting the call stack.\n',
 '__file__': 'inspect_stack.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader
object at 0x101f9c080>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'inspect': <module 'inspect' from
'.../lib/python3.6/inspect.py'>,
 'pprint': <module 'pprint' from '.../lib/python3.6/pprint.py'>,
 'recurse': <function recurse at 0x1045b9f28>,
 'show_stack': <function show_stack at 0x101f21e18>}

Existen otras funciones para crear listas de marcos en diferentes contextos, como cuando se procesa una excepción. Consulta la documentación de trace(), getouterframes() y getinnerframes() para obtener más detalles.

Interfaz de línea de comando

El módulo inspect también incluye una interfaz de línea de comandos para obtener detalles sobre los objetos sin tener que escribir las llamadas en un programa Python separado. La entrada es un nombre de módulo y un objeto opcional desde el módulo. La salida predeterminada es el código fuente del objeto nombrado. El uso del argumento --details hace que se impriman los metadatos en lugar del código fuente.

$ python3 -m inspect -d example

Target: example
Origin: .../example.py
Cached: .../__pycache__/example.cpython-36.pyc
Loader: <_frozen_importlib_external.SourceFileLoader object at 0
x103e16fd0>

$ python3 -m inspect -d example:A

Target: example:A
Origin: .../example.py
Cached: .../__pycache__/example.cpython-36.pyc
Line: 16

$ python3 -m inspect example:A.get_name

    def get_name(self):
        "Returns the name of the instance."
        return self.name

Ver también