Python: добавление процедуры кода в каждую строку блока кода

#python #decorator #introspection

#python #декоратор #самоанализ

Вопрос:

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

Например, ниже я пытаюсь напечатать «привет» перед каждой строкой foo() функции. Я думаю, что декоратор может мне помочь, но для редактирования каждой строки моей foo() функции и добавления того, что я хочу, до или после нее потребуется некоторая функция самоанализа.

Я пытаюсь выполнить что-то вроде этого :

 >>> def foo():
...    print 'bar'
...    print 'barbar'
...    print 'barbarbar'

>>> foo()
hello
bar
hello
barbar 
hello
barbarbar
  

Как я могу это выполнить? поможет ли __code__ объект? нужен ли мне декоратор и самоанализ одновременно?

РЕДАКТИРОВАТЬ: Вот еще один пример цели этого потока:

 >>> def foo():
...    for i in range(0,3):
...        print 'bar'

>>> foo()
hello
bar
hello
bar 
hello
bar
  

В этом новом случае перед печатью каждой «строки» я хочу напечатать «привет».

Основная цель этого — иметь возможность выполнить другую функцию или протестировать любую глобальную переменную перед выполнением следующей строки кода. Представьте, что если глобальная переменная равна True , то код переходит к следующей строке; в то время как если глобальная переменная равна False , то это останавливает выполнение функции.

РЕДАКТИРОВАТЬ: В некотором смысле, я ищу инструмент для внедрения кода в другой блок кода.

РЕДАКТИРОВАТЬ: Спасибо unutbu, я добился этого кода :

 import sys
import time
import threading

class SetTrace(object):
    """
    with SetTrace(monitor):
    """
    def __init__(self, func):
        self.func = func
    def __enter__(self):
        sys.settrace(self.func)
        return self
    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)
        # http://effbot.org/zone/python-with-statement.htm
        # When __exit__ returns True, the exception is swallowed.
        # When __exit__ returns False, the exception is reraised.
        # This catches Sentinel, and lets other errors through
        # return isinstance(exc_value, Exception)

def monitor(frame, event, arg):
    if event == "line":
        if not running:
            raise Exception("global running is False, exiting")
    return monitor

def isRunning(function):
    def defaultBehavior(*args):
        with SetTrace(monitor):
            ret = function(*args)
            return ret
    return defaultBehavior

@isRunning
def foo():
    while True:
        time.sleep(1)
        print 'bar'

global running
running = True
thread = threading.Thread(target = foo)
thread.start()
time.sleep(3)
running = False
  

Ответ №1:

Возможно, вы ищете sys.settrace:

 import sys
class SetTrace(object):
    def __init__(self, func):
        self.func = func

    def __enter__(self):
        sys.settrace(self.func)
        return self

    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)

def monitor(frame, event, arg):
    if event == "line":
        print('hello')
        # print(frame.f_globals) 
        # print(frame.f_locals)  
    return monitor



def foo():
   print 'bar'
   print 'barbar'
   print 'barbarbar'

with SetTrace(monitor):
    foo()
  

дает

 hello
bar
hello
barbar
hello
barbarbar
hello
  

Внутри monitor вы можете получить доступ к foo локальным и глобальным данным с помощью frame.f_locals и frame.f_globals .

Смотрите этот пост для примера того, как sys.settrace можно использовать для отладки.


Как остановить foo изнутри monitor :

Наиболее изящным способом сделать это было бы поместить внутри условный оператор, foo чтобы foo проверить, когда выходить. Затем вы можете манипулировать значением условия изнутри, monitor чтобы контролировать, когда foo завершается.

Однако, если вы не хотите или не можете изменять foo , альтернативой является создание исключения изнутри monitor . Исключение будет всплывать в стеке фреймов до тех пор, пока оно не будет перехвачено. Если вы поймаете ее в SetTrace.__exit__ , то поток управления будет продолжаться, как если бы foo он только что вышел.

 import sys
class Sentinel(Exception): pass

class SetTrace(object):
    """
    with SetTrace(monitor):
        ...
    """
    def __init__(self, func):
        self.func = func

    def __enter__(self):
        sys.settrace(self.func)
        return self

    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)
        # http://effbot.org/zone/python-with-statement.htm
        # When __exit__ returns True, the exception is swallowed.
        # When __exit__ returns False, the exception is reraised.

        # This catches Sentinel, and lets other errors through
        return isinstance(exc_value, Sentinel)

def monitor(frame, event, arg):
    if event == "line":
        l = frame.f_locals
        x = l.get('x', 0)
        print('x = {}'.format(x))
        if x > 3:
            raise Sentinel()
    return monitor

def foo():
    x = 0
    while True:
        print 'bar'
        x  = 1

with SetTrace(monitor):
    foo()
  

Комментарии:

1. Большое спасибо, это похоже на то, что вы правы, основная цель потока не в отладке, а в том, чтобы иметь возможность остановить блок кода, который выполняется, путем изменения значения другой глобальной переменной, которую эта функция будет оценивать перед выполнением каждой строки кода. Это добавило бы точку вытеснения внутри каждой строки кода, если глобальный переключатель имеет значение false => тогда функция останавливается, не доходя до конца, если переключатель имеет значение true => затем функция выполняет строку и переходит к следующей. Я достаточно ясно объясняю это?

2. Да, вы можете проверять и изменять значение локальных и глобальных значений изнутри monitor .

3. Спасибо, но могу ли я остановить текущее выполнение отслеживаемой функции? в таком случае, остановка foo() от выполнения? (могу ли я каким-то образом добавить строку возврата, когда захочу между ними, чтобы завершить свою функцию?)

4. Я отредактировал сообщение выше, чтобы показать, как foo можно завершить изнутри monitor .

5. большое спасибо, это работает, я добавлю персональные изменения в свой пост

Ответ №2:

Вероятно, лучший ответ зависит от того, что вы на самом деле хотите сделать, но для того, чтобы сделать то, что вы просили, я бы сделал это:

 from itertools import chain

def print_(s):
    print s

def print_f(s):
   return (lambda: print_(s))

def execute_several(functions):
    for f in functions:
        f()

def prepend_printhello(function):
    return (print_f("hello"), function)

def foo():
    execute_several(chain(*map(prepend_printhello, map(print_f, ("bar", "barbar", "barbarbar")))))
  

Комментарии:

1. Это не соответствует тому, что я ищу, поскольку это меняет способ кодирования функции foo() создавая функцию 3 lamba, я ищу способ сохранить классический способ кодирования и добавить декоратор, который будет выполнять всю эту работу.

2. Кстати: есть поведение, с которым нелегко справиться, представьте функцию foo() с циклом for , я хотел бы выполнить свою печать ‘hello’ в каждом цикле этого цикла, в некотором смысле, я ищу точку вытеснения в каждой строке кода

Ответ №3:

Похоже, вам нужен отладчик, взгляните на встроенный pdb . С pdb вы можете сделать это:

 >>> def foo():
...     import pdb;pdb.set_trace()
...     print 'bar'
...     print 'barbar'
...     print 'barbarbar'
... 
>>> 
>>> foo()
> <stdin>(3)foo()
(Pdb) print 'hi'
hi
(Pdb) n
bar
> <stdin>(4)foo()
(Pdb) n
barbar
> <stdin>(5)foo()
(Pdb) n
barbarbar
--Return--
> <stdin>(5)foo()->None
(Pdb) n
--Return--
> <stdin>(1)<module>()->None
(Pdb) n
>>> 
  

Как и большинство других отладчиков, это позволяет пошагово просматривать ваш код построчно. Вы можете ознакомиться с документацией для получения дополнительной информации, но в приведенном выше примере pdb.set_trace() -вызов устанавливает точку входа отладки и вызывает консоль pdb. С консоли вы можете изменять переменные и делать всевозможные вещи. n Это просто сокращение для next , которое делает один шаг вперед.