Установить точку останова отладчика в конце функции без возврата
Я отлаживаю метод 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
при наведении указателя мыши, так как функция вызывается из другого модуля.)
Ответы
Ответ 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
Здесь у вас есть несколько вариантов.
- Добавить точку останова в последнюю строку функции.
В этом случае последняя строка находится внутри цикла, поэтому вам придется перебирать каждый элемент в цикле.
- Добавить точку останова, в которой вызывается функция.
Это остановит отладчик перед вызываемой функцией, но вы можете "перешагнуть" эту функцию, чтобы увидеть значение A.x
после вызова A.f()
.
- Добавьте временную инструкцию в конец функции, которая будет разбита на
Этот трюк будет работать, если ваша функция закончится в цикле и есть несколько мест, вызываемых функцией, или вы не хотите отслеживать вызов функции.
Вы можете добавить простой оператор в конец функции для целей отладки и добавить там точку останова.
def f(self):
for i in range(10):
self.X.append(i)
debug_break = 1
Ответ 6
Почему бы просто не оставить там возвращение? Или a return None
. В любом случае это неявно, интерпретатор/компилятор будет делать то же самое независимо:
Фактически, даже функции без оператора return возвращают значение, хотя и довольно скучное. Это значение называется None (его встроенное имя).
[источник: Учебник по Python 4.6].