Показать строки, где вызывается определенный метод
Допустим, у вас есть определенный метод (функция) из определенного модуля (определенного класса, необязательно). Возможно ли через самоанализ исходного кода библиотеки распечатать все строки, где этот метод вызывается (используется)? Его можно вызывать внутренне (с помощью self.method_name()) или извне с помощью (object1.method_name() в исходном файле 1, object2.method_name() в исходном файле 2, ... и objectN.method_name() в исходном файле N).
Пример может быть показан на re
модуль и его метод re.findall
,
Я пытался напечатать строки с grep
, но это проблема для методов с одинаковым именем (например, я попробовал это с методом с именем connect (), но в 24 классах есть метод с именем connect... Я хотел бы отфильтровать это для определенного класса (и / или модуля)).
4 ответа
Я использую функции довольно регулярно. К счастью, меня никогда не интересовало то, что было бы так сильно дублировано.
Вот что я мог бы сделать, а не писать одноразовый код, если ложные попадания для Class.method
были слишком распространены, чтобы фильтровать вручную. Первый grep для class Class
найти module
с определением класса и обратите внимание на диапазон строк. Затем grep этот модуль для self.method
и удалять или игнорировать попадания за пределы этого диапазона. Затем grep всех модулей, представляющих интерес для import module
а также from module
найти модули, которые могут использовать класс и метод. Затем grep групп модулей в зависимости от конкретной формы импорта.
Как уже отмечали другие, даже при этом будут пропущены вызовы, использующие псевдонимы для имени метода. Но только вы можете знать, является ли это проблемой для вашего сценария. Это не то, что я знаю, для того, что я сделал.
Совершенно другой подход, не зависящий от имен, состоит в том, чтобы снабдить функцию кодом, который регистрирует ее вызовы, после использования динамического самоанализа для определения вызывающего. (Я считаю, что есть так Q&As об этом.)
Я добавляю это как еще один ответ, потому что код слишком велик, чтобы сводить все вместе в первом.
Это очень упрощенный пример для определения какой функции вызывается с использованием абстрактного синтаксического дерева.
Чтобы применить это к объектам, вы должны сложить их при вводе, затем перейти к их классу и, встретив вызов функции, сказать, что она вызывается из этого конкретного объекта.
Вы можете увидеть, насколько это сложно, когда модули включаются. Каждый модуль должен быть введен, и его подмодули и все функции должны быть сопоставлены, чтобы вы могли отслеживать вызовы к ним и так далее.
import ast
def walk (node):
"""ast.walk() skips the order, just walks, so tracing is not possible with it."""
end = []
end.append(node)
for n in ast.iter_child_nodes(node):
# Consider it a leaf:
if isinstance(n, ast.Call):
end.append(n)
continue
end += walk(n)
return end
def calls (tree):
"""Prints out exactly where are the calls and what functions are called."""
tree = walk(tree) # Arrange it into our list
# First get all functions in our code:
functions = {}
for node in tree:
if isinstance(node, (ast.FunctionDef, ast.Lambda)):
functions[node.name] = node
# Find where are all called functions:
stack = []
for node in tree:
if isinstance(node, (ast.FunctionDef, ast.Lambda)):
# Entering function
stack.append(node)
elif stack and hasattr(node, "col_offset"):
if node.col_offset<=stack[-1].col_offset:
# Exit the function
stack.pop()
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Attribute):
fname = node.func.value.id+"."+node.func.attr+"()"
else: fname = node.func.id+"()"
try:
ln = functions[fname[:-2]].lineno
ln = "at line %i" % ln
except: ln = ""
print "Line", node.lineno, "--> Call to", fname, ln
if stack:
print "from within", stack[-1].name+"()", "that starts on line", stack[-1].lineno
else:
print "directly from root"
code = """
import os
def f1 ():
print "I am function 1"
return "This is for function 2"
def f2 ():
print f1()
def f3 ():
print "I am a function inside a function!"
f3()
f2()
print "My PID:", os.getpid()
"""
tree = ast.parse(code)
calls(tree)
The output is:
Line 9 --> Call to f1() at line 4
from within f2() that starts on line 8
Line 12 --> Call to f3() at line 10
from within f2() that starts on line 8
Line 13 --> Call to f2() at line 8
directly from root
Line 14 --> Call to os.getpid()
directly from root
Вы можете использовать ast или модуль компилятора, чтобы копаться в скомпилированном коде и узнавать места, где функции вызываются явно.
Вы также можете просто скомпилировать код с помощью compile() с флагом ast и проанализировать его как абстрактное синтаксическое дерево. Тогда вы идете посмотреть, что называется, где в нем.
Но вы можете отследить все, что происходит во время выполнения кода, используя некоторые приемы из модулей sys, inspect и traceback.
Например, вы можете установить функцию трассировки, которая будет захватывать каждый кадр интерпретатора, прежде чем он будет выполнен:
import dis
import sys
def tracefunc (frame, evt, arg):
print frame.f_code.co_filename, frame.f_lineno, evt
print frame.f_code.co_name, frame.f_code.co_firstlineno
#print dis.dis(f.f_code)
sys.settrace(tracefunc)
После этого кода каждый выполненный шаг будет напечатан с файлом, содержащим код, строку шага, с которой начинается объект кода, и он будет разбирать его, чтобы вы могли видеть все, что делается или будет выполнено в фоновом режиме (если вы раскомментируете это).
Если вы хотите сопоставить исполняемый байт-код с кодом Python, вы можете использовать модуль tokenize. Вы создаете кеш токенизированных файлов по мере их появления в трассировке и извлекаете код Python из соответствующих строк при необходимости.
Используя все упомянутые вещи, вы можете совершать блуждания, включая написание декомпилятора байт-кода, перебирать весь код, как с помощью goto в C, принудительно прерывать потоки (не рекомендуется, если вы точно не знаете, что вы делаете), отслеживать, какая функция называется вашей функцией. (хорошо для потоковых серверов, чтобы распознавать клиентов, перехватывающих свои части потока), и всякие сумасшедшие вещи.
Продвинутые сумасшедшие вещи, которые я должен сказать. НЕ ИСПОЛЬЗУЙТЕ поток кода в ТАКОМ СПОСОБЕ, ЕСЛИ ЭТО НЕ АБСОЛЮТНО НЕОБХОДИМО, и вы ТОЧНО не знаете, что делаете.
Я проголосую только потому, что упомянул, что такие вещи даже возможны.
Пример для динамического определения, какой экземпляр client() пытается получить содержимое:
from thread import get_ident
import sys
class Distributer:
def read (self):
# Who called me:
cf = sys._current_frames()
tid = get_ident() # Make it thread safe
frame = cf[tid]
# Now, I was called in one frame back so
# go back and find the 'self' variable of a method that called me
# and self, of course, contains the instance from which I was called
client = frame.f_back.f_locals["self"]
print "I was called by", client
class Client:
def __init__ (self, name):
self.name = name
def snatch (self):
# Now client gets his content:
content.read()
def __str__ (self):
return self.name
content = Distributer()
clients = [Client("First"), Client("Second"), Client("Third"), Client("Fourth"), Client("Etc...")]
for client in clients:
client.snatch()
Теперь вы пишете это в функции трассировки вместо фиксированного метода, но умно, не полагаясь на имена переменных, а на адреса и прочее, и вы можете отслеживать, что происходит, когда и где. Большая работа, но возможно.
Вы, наверное, знаете, но я просто не могу рисковать тем, что вы не знаете: Python не является строго типизированным языком.
Как-то вроде objectn.connect()
не важно что objectn
есть (это может быть модуль, класс, функция, получившая атрибут, ...). Это также не волнует, если connect
это метод или, если это класс, который вызывается или фабрика для функций. Было бы счастливо принять любой objectn
что-то возвращает вызываемый при попытке получить атрибут connect
,
Мало того, существует множество способов вызова методов, просто предположите что-то вроде:
class Fun(object):
def connect(self):
return 100
objectn = Fun()
(lambda x: x())(getattr(objectn, '{0}t'.format('co' + {0:'nnec'}[0])))
Там нет никакого способа вы могли бы надежный поиск objectn.connect()
и матч получить матч для (lambda x: x())(getattr(objectn, '{0}t'.format('co' + {0:'nnec'}[0])))
, но оба вызывают метод connect
из objectn
,
Поэтому мне очень жаль говорить, что даже с абстрактными синтаксическими деревьями, (необязательными) аннотациями и статическим анализом кода будет (почти?) Невозможно найти все места, где вызывается конкретный метод определенного класса.