Рубрики
Без рубрики

inspect – проверка живых объектов

Автор оригинала: Doug Hellmann.

Цель:

Модуль inspect предоставляет функции для самоанализа в реальном времени. объекты и их исходный код.

Модуль inspect предоставляет функции для изучения живых объектов, включая модули, классы, экземпляры, функции и методы. Функции в этом модуле можно использовать для получения исходного исходного кода функции, просмотра аргументов метода в стеке и извлечения информации, полезной для создания документации библиотеки для исходного кода.

Пример модуля

В остальных примерах этого раздела используется файл этого примера, 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 + ')'

Проверка модулей

Первый вид интроспекции исследует живые объекты, чтобы узнать о них. Используйте getmembers () , чтобы обнаружить атрибуты членов объекта. Типы возвращаемых элементов зависят от типа сканируемого объекта. Модули могут содержать классы и функции; классы могут содержать методы и атрибуты; и так далее.

Аргументы для getmembers () – это объект для сканирования (модуль, класс или экземпляр) и необязательная функция предиката, которая используется для фильтрации возвращаемых объектов. Возвращаемое значение – это список кортежей с двумя значениями: имя члена и тип члена. Модуль inspect включает несколько таких функций-предикатов с такими именами, как ismodule () , isclass () и т. Д.

inspect_getmembers_module.py

import inspect

import example

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

В этом образце печатаются элементы модуля example . У модулей есть несколько частных атрибутов, которые используются как часть реализации импорта, а также набор __builtins__ . Все они игнорируются в выводе для этого примера, потому что они фактически не являются частью модуля, а список длинный.

$ python3 inspect_getmembers_module.py

A : 
B : 
instance_of_a : 
module_level_function : 

Аргумент predicate можно использовать для фильтрации типов возвращаемых объектов.

inspect_getmembers_module_class.py

import inspect

import example

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

Теперь в вывод включаются только классы.

$ python3 inspect_getmembers_module_class.py

A : 
B : 

Проверка классов

Классы сканируются с помощью getmembers () так же, как и модули, хотя типы членов различны.

inspect_getmembers_class.py

import inspect
from pprint import pprint

import example

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

Поскольку фильтрация не применяется, на выходе отображаются атрибуты, методы, слоты и другие члены класса.

$ python3 inspect_getmembers_class.py

[('__class__', ),
 ('__delattr__',
  ),
 ('__dict__',
  mappingproxy({'__dict__': ,
                '__doc__': 'The A class.',
                '__init__': ,
                '__module__': 'example',
                '__weakref__': ,
                'get_name': })),
 ('__dir__', ),
 ('__doc__', 'The A class.'),
 ('__eq__', ),
 ('__format__', ),
 ('__ge__', ),
 ('__getattribute__',
  ),
 ('__gt__', ),
 ('__hash__', ),
 ('__init__', ),
 ('__init_subclass__',
  ),
 ('__le__', ),
 ('__lt__', ),
 ('__module__', 'example'),
 ('__ne__', ),
 ('__new__',
  ),
 ('__reduce__', ),
 ('__reduce_ex__', ),
 ('__repr__', ),
 ('__setattr__',
  ),
 ('__sizeof__', ),
 ('__str__', ),
 ('__subclasshook__',
  ),
 ('__weakref__', ),
 ('get_name', )]

Чтобы найти методы класса, используйте предикат isfunction () . Предикат ismethod () распознает только связанные методы экземпляров.

inspect_getmembers_class_methods.py

import inspect
from pprint import pprint

import example

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

Теперь возвращаются только несвязанные методы.

$ python3 inspect_getmembers_class_methods.py

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

Вывод для B включает переопределение для get_name () , а также новый метод и унаследованный метод __init __ () , реализованный в А .

inspect_getmembers_class_methods_b.py

import inspect
from pprint import pprint

import example

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

Методы, унаследованные от A , такие как __init __ () , идентифицируются как методы B .

$ python3 inspect_getmembers_class_methods_b.py

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

Проверка экземпляров

Самоанализ экземпляров работает так же, как и другие объекты.

inspect_getmembers_instance.py

import inspect
from pprint import pprint

import example

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

Предикат ismethod () распознает два связанных метода из A в примере экземпляра.

$ python3 inspect_getmembers_instance.py

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

Строки документации

Строку документации для объекта можно получить с помощью getdoc () . Возвращаемое значение – атрибут __doc__ с табуляцией, расширенной до пробелов, и с равномерным отступом.

inspect_getdoc.py

import inspect
import example

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

Вторая строка строки документации имеет отступ, когда она извлекается напрямую через атрибут, но перемещается в левое поле с помощью 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.

В дополнение к фактической строке документации можно получить комментарии из исходного файла, в котором реализован объект, если источник доступен. Функция getcomments () просматривает источник объекта и находит комментарии в строках, предшествующих реализации.

inspect_getcomments_method.py

import inspect
import example

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

Возвращенные строки включают префикс комментария с удаленным префиксом пробела.

$ python3 inspect_getcomments_method.py

# This method is not part of A.

Когда модуль передается в getcomments () , возвращаемое значение всегда является первым комментарием в модуле.

inspect_getcomments_module.py

import inspect
import example

print(inspect.getcomments(example))

Смежные строки из файла примера включаются как один комментарий, но как только появляется пустая строка, комментарий останавливается.

$ python3 inspect_getcomments_module.py

# This comment appears first
# and spans 2 lines.

Получение источника

Если для модуля доступен файл .py , исходный исходный код для класса или метода можно получить с помощью getsource () и getsourcelines () .

inspect_getsource_class.py

import inspect
import example

print(inspect.getsource(example.A))

Когда класс передается, все методы этого класса включаются в вывод.

$ 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

Чтобы получить источник для одного метода, передайте ссылку на метод в getsource () .

inspect_getsource_method.py

import inspect
import example

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

В этом случае сохраняется исходный уровень отступа.

$ python3 inspect_getsource_method.py

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

Используйте getsourcelines () вместо getsource () для получения строк исходного кода, разделенных на отдельные строки.

inspect_getsourcelines_method.py

import inspect
import pprint
import example

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

Возвращаемое значение от getsourcelines () – это кортеж , содержащий список строк (строк из исходного файла) и номер начальной строки в файле, где появляется исходный код.

$ python3 inspect_getsourcelines_method.py

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

Если исходный файл недоступен, getsource () и getsourcelines () вызывают IOError .

Сигнатуры методов и функций

В дополнение к документации для функции или метода можно запросить полную спецификацию аргументов, которые принимает вызываемый объект, включая значения по умолчанию. Функция signature () возвращает экземпляр Signature , содержащий информацию об аргументах функции.

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

Аргументы функции доступны через атрибут parameters в Signature . parameters – это упорядоченный словарь, сопоставляющий имена параметров с экземплярами Parameter , описывающими аргумент. В этом примере первый аргумент функции, arg1 , не имеет значения по умолчанию, а arg2 имеет.

$ python3 inspect_signature_function.py

module_level_function(arg1,, *args, **kwargs)

Parameter details:
  arg1
 
  *args
  **kwargs

Подпись для функции может использоваться декораторами или другими функциями для проверки входных данных, предоставления различных значений по умолчанию и т. Д. Однако создание подходящего универсального и многоразового декоратора проверки имеет одну особую проблему, потому что это может быть сложно. для сопоставления входящих аргументов с их именами для функций, которые принимают комбинацию именованных и позиционных аргументов. Методы bind () и bind_partial () предоставляют необходимую логику для обработки сопоставления. Они возвращают экземпляр BoundArguments , заполненный аргументами, связанными с именами аргументов указанной функции.

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

Экземпляр BoundArguments имеет атрибуты args и kwargs , которые можно использовать для вызова функции с использованием синтаксиса для развертывания кортежа и словаря в стеке как аргументы.

$ 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

Если доступны только некоторые аргументы, bind_partial () все равно создаст экземпляр BoundArguments . Его нельзя будет полностью использовать, пока не будут добавлены остальные аргументы.

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 () добавит любые значения из значений параметра по умолчанию.

$ python3 inspect_signature_bind_partial.py

Without defaults:
arg1 = 'this is arg1'

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

Иерархии классов

inspect включает два метода для работы непосредственно с иерархиями классов. Первый, getclasstree () , создает древовидную структуру данных на основе заданных классов и их базовых классов. Каждый элемент в возвращаемом списке является либо кортежем с классом и его базовыми классами, либо другим списком, содержащим кортежи для подклассов.

inspect_getclasstree.py

import inspect
import example


class C(example.B):
    pass


class D(C, example.A):
    pass


def print_class_tree(tree, indent1):
    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])
    )

Результатом этого примера является дерево наследования для классов A , B , C и D . D появляется дважды, поскольку он наследуется как от C , так и от A .

$ python3 inspect_getclasstree.py

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

Если getclasstree () вызывается с параметром unique , установленным на истинное значение, результат будет другим.

inspect_getclasstree_unique.py

import inspect
import example
from inspect_getclasstree import *

print_class_tree(inspect.getclasstree(
    [example.A, example.B, C, D],
    uniqueTrue,
))

На этот раз D появляется в выводе только один раз:

$ python3 inspect_getclasstree_unique.py

 object
   A
     B
       C
         D

Порядок разрешения метода

Другая функция для работы с иерархиями классов – это getmro () , которая возвращает кортеж классов в том порядке, в котором они должны сканироваться при разрешении атрибута, который может быть унаследован от базовый класс с использованием порядка разрешения методов (MRO). Каждый класс в последовательности появляется только один раз.

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

Этот вывод демонстрирует природу поиска MRO «сначала в глубину». Для B_First A также стоит перед C в порядке поиска, поскольку B является производным от A .

$ python3 inspect_getmro.py

B_First:
  B_First
  B
  A
  C
  object

C_First:
  C_First
  C
  B
  A
  object

Стек и рамки

Помимо самоанализа объектов кода, inspect включает функции для проверки среды выполнения во время выполнения программы. Большинство этих функций работают со стеком вызовов и работают с кадрами вызовов . Объекты фрейма содержат текущий контекст выполнения, включая ссылки на выполняемый код, выполняемую операцию, а также значения локальных и глобальных переменных. Обычно такая информация используется для построения трассировки при возникновении исключений. Это также может быть полезно для ведения журнала или при отладке программ, поскольку кадры стека могут быть опрошены, чтобы обнаружить значения аргументов, переданных в функции.

currentframe () возвращает фрейм наверху стека (для текущей функции).

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)

Значения аргументов для recurse () включены в словарь локальных переменных фрейма.

$ python3 inspect_currentframe.py

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

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

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

Используя 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)

Последняя часть вывода представляет основную программу за пределами функции recurse () .

$ python3 inspect_stack.py

inspect_stack.py[11]
  -> for level in inspect.stack():
{'level':>,,,
code_context=['    for level in inspect.stack():\n'],

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__': ,
 '__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': ,
 'pprint': ,
 'recurse': ,
 'show_stack': }

Существуют и другие функции для построения списков фреймов в различных контекстах, например, при обработке исключения. Дополнительную информацию см. В документации по trace () , getouterframes () и getinnerframes () .

Интерфейс командной строки

Модуль inspect также включает интерфейс командной строки для получения сведений об объектах без необходимости записывать вызовы в отдельной программе Python. Входные данные – это имя модуля и необязательный объект внутри модуля. По умолчанию выводится исходный код названного объекта. Использование аргумента --details вызывает печать метаданных вместо источника.

$ 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

Смотрите также