Подтвердить что ты не робот

Установить точку останова отладчика в конце функции без возврата

Я отлаживаю метод f(), у которого нет return.

class A(object):

    def __init__(self):
        self.X = []

    def f(self):            
        for i in range(10):
            self.X.append(i)

Мне нужно посмотреть, как этот метод изменяет переменную X сразу после ее вызова. Для этого я вставляю return в конце метода и устанавливаю там точку останова:

enter image description here

Таким образом, как только метод достигнет своего return, я могу увидеть значение моей переменной X.


Это делает работу, но я уверен, что есть лучший способ. Редактирование метода или функции каждый раз, когда мне нужно отлаживать, кажется глупым.

Вопрос:
Есть ли другой способ (например, вариант в отладчике) установить точку останова в конце метода, который не имеет return?

(Обратите внимание, что установка точки останова при вызове функции и использование Step Over не будет отображаться X при наведении указателя мыши, так как функция вызывается из другого модуля.)

4b9b3361

Ответ 1

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

В этом случае условие очень просто, так как оно просто i == 9, но оно может быть намного сложнее в зависимости от вашего условия цикла, поэтому иногда добавление инструкции в конце будет более легким решением.

Conditional breakpoint in IntelliJ IDEA

Этот снимок экрана - от IntelliJ IDEA, и ваш скриншот выглядит с той же IDE, поэтому просто щелкните правой кнопкой мыши точку останова, чтобы отобразить диалоговое окно и введите свое условие.

Если вы используете какую-либо другую среду IDE, я уверен, что есть возможность сделать условную точку прерывания.

Update:

Нет поддержки для разбиения в конце метода в отладчике Python только в начале метода:

b (reak) [[имя файла:] lineno | function [, condition]]

С аргументом lineno установите разрыв в текущем файле. С аргументом функции установите разрыв в первом исполняемом выражении внутри этой функции. Номер строки может иметь префикс имени файла и двоеточия, чтобы указать точку останова в другом файле (возможно, еще не загружен). Поиск файла осуществляется по sys.path. Обратите внимание, что каждой точке останова присваивается номер, к которому относятся все остальные команды останова.

Если присутствует второй аргумент, это выражение, которое должно оцениваться в true до того, как будет соблюдена точка останова.

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

Ответ 2

Ваша IDE скрывает, что под капотом. То есть, что-то вроде

import pdb

добавляется к вашим script и

pdb.set_trace()

вставлен перед линией, на которую вы разместили свою точку останова. Из того, что вы говорите, я выводю, что PyCharm не любит размещать точки останова на пустых строках. Однако pdb.set_trace() можно отлично поместить в конце метода.

Итак, вы можете вставить их самостоятельно (или написать макрос) и запустить python -m pdb, чтобы начать отладку.

(Изменить) пример

import pdb

class A(object):

    def __init__(self):
        self.X = []

    def f(self):
        for i in range(10):
            self.X.append(i)
        pdb.set_trace()

if __name__ == '__main__':
    a = A()
    a.f()

Отладка с помощью

$ python -m pdb test.py 
> /dev/test.py(1)<module>()
----> 1 import pdb
      2 
      3 class A(object):

ipdb> cont
--Return--
> /dev/test.py(11)f()->None
-> pdb.set_trace()
(Pdb) self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(Pdb)

ipdb можно использовать вместо pdb.

Ответ 3

Быстрое & грязное решение, которое работает на любом языке, поддерживающем monkeypatching (Python, Ruby, ObjC и т.д.). Я честно не помню, когда-либо нуждался в этом на Python, но я сделал это совсем немного в SmallTalk и ObjC, так что, возможно, это будет полезно для вас.

Просто динамически оберните A.f в функцию, например:

real_A_f = A.f
def wrap_A_f(self, *args, **kwargs):
    result = real_A_f(self, *args, **kwargs)
    return result
A.f = wrap_A_f

В большинстве отладчиков с возможностью сценариев вы должны иметь возможность написать script, который делает это автоматически для метода по имени. В pdb, который позволяет выполнять обычный код Python прямо в отладчике, это особенно просто.

Теперь вы можете поместить точку останова на return result, и она гарантированно попадет сразу после возвращения реального A.f (даже если она вернется посередине или упадет с конца без инструкции return).

Несколько вещей, которые вы можете добавить:

  • Если вы хотите поймать повышение A.f, поместите код try: и except: raise вокруг кода и добавьте точку останова на raise.
  • Для Python 2.x вам может понадобиться обернуть это с помощью типов .MethodType, чтобы создать реальный несвязанный метод.
  • Если вы хотите только точку останова на конкретном экземпляре A, вы можете либо использовать условную точку останова, которая проверяет self is a, либо использовать types.MethodType для создания связанного экземпляра и сохранить его как A.f.
  • Вы можете использовать functools.wraps, если вы хотите скрыть оболочку от остальной части кода (и от вашей отладки, за исключением случаев, когда вы действительно хотите ее увидеть).
  • Так как pdb позволяет вам выполнять динамический код прямо в пространстве имен в реальном времени, вы можете поместить функцию wrap_method где-нибудь в вашем проекте, которая делает это, а затем в приглашении написать p utils.wrap_method(A, 'f'). Но если вы обмениваете несколько методов таким образом, они собираются использовать одни и те же точки останова (внутри функции обертки, определенной внутри wrap_method). Здесь я считаю, что условная точка останова является единственным разумным вариантом.
  • Если вам нужен доступ к реальным локалям A.f из точки останова обертки, это намного сложнее. Я могу придумать некоторые очень взломанные варианты (например, exec(real_A_f.__code__, real_A_f.globals()), но ничего мне не понравилось бы.

Ответ 4

С pdb вы можете использовать красивое сочетание break function и until lineno:

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

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

Изменено в версии 3.2: Разрешить предоставление явного номера строки.

Вы можете достичь того, что вам нужно.

Я немного изменил ваш пример (так что вы увидите, что команда выполняется, хотя pdb сообщает об этом как "следующая инструкция" ):

01: class A(object):
02: 
03:     def __init__(self):
04:         self.X = []
05:         
06:     def f(self):         
07:         print('pre exec')
08:         for i in range(10):
09:             self.X.append(i)
10:         print('post exec')
11:             
12: a = A()
13: a.f()
14: print('Game is over')
15:

И результат работы с python -m pdb test.py будет выглядеть следующим образом:

Запустите отладку и запустите ее сразу после объявления класса (чтобы вы могли добавить именованную точку останова):

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 11
> d:\tmp\stack\test.py(12)<module>()
-> a = A()

Теперь перерыв в начале функции:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:6

Просто продолжайте выполнение до тех пор, пока оно не достигнет точки останова:

(Pdb) continue
> d:\tmp\stack\test.py(7)f()
-> print('pre exec')

Воспользуйтесь преимуществами "также остановить, когда текущий кадр возвращается":

(Pdb) until 14
pre exec
post exec
--Return--

Как вы можете видеть, как pre exec, так и post exec были напечатаны, но при выполнении where вы все еще находитесь в f():

(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(13)<module>()
-> a.f()
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')

И все контекстные переменные не повреждены:

(Pdb) p self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Теперь с примером вашей реальной жизни:

01: class A(object):
02:     def __init__(self):
03:         self.X = []
04:         
05:     def f(self):         
06:         for i in range(10):
07:             self.X.append(i)
08:             
09: a = A()
10: a.f()
11: print('Game is over')

Запустите аналогичный способ, как и раньше:

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 8
> d:\tmp\stack\test.py(9)<module>()
-> a = A()
(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) cont
> d:\tmp\stack\test.py(6)f()
-> for i in range(10):

Теперь... Точка останова в f.A на самом деле означает точку останова при первой инструкции f.A, которая, к сожалению, for i in..., поэтому она будет разбиваться на нее каждый раз.

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

(Pdb) disable 1
Disabled breakpoint 1 at d:\tmp\stack\test.py:5

Снова используйте until <end of file>:

(Pdb) until 10
--Return--
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):

И снова доступны все переменные кадра:

(Pdb) p i
9
(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(10)<module>()
-> a.f()
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):
(Pdb)

Печально то, что я хотел попробовать эту часть автоматизации:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) commands 1
(com) disable 1
(com) until 11
(com) end

Что бы делать все, что вам нужно автоматически (опять же, disable 1 не требуется, когда у вас есть хотя бы один предзаципный оператор), но в соответствии с документацией на commands:

Указание любой команды, возобновляющей выполнение (в настоящее время продолжение, шаг, следующий, возврат, переход, выход и их сокращения) завершает список команд (как если бы эта команда сразу же была завершена). Это связано с тем, что в любое время, когда вы возобновляете выполнение (даже при простом следующем или шаге), вы можете столкнуться с другой точкой останова, которая может иметь свой собственный список команд, что приводит к двусмысленности в отношении того, какой список выполнить.

Итак, until просто не работает (по крайней мере для Python 3.2.5 под окнами), и вам нужно сделать это вручную.

Ответ 5

Здесь у вас есть несколько вариантов.

  • Добавить точку останова в последнюю строку функции.

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

  1. Добавить точку останова, в которой вызывается функция.

Это остановит отладчик перед вызываемой функцией, но вы можете "перешагнуть" эту функцию, чтобы увидеть значение A.x после вызова A.f().

  1. Добавьте временную инструкцию в конец функции, которая будет разбита на

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

Вы можете добавить простой оператор в конец функции для целей отладки и добавить там точку останова.

def f(self):            
    for i in range(10):
        self.X.append(i)
    debug_break = 1

Ответ 6

Почему бы просто не оставить там возвращение? Или a return None. В любом случае это неявно, интерпретатор/компилятор будет делать то же самое независимо:

Фактически, даже функции без оператора return возвращают значение, хотя и довольно скучное. Это значение называется None (его встроенное имя).

[источник: Учебник по Python 4.6].