Рассмотрим этот сценарий:
print("before loop")
for i in range(100):
breakpoint()
print("after loop")
breakpoint()
print("exit")
Если не нажать «c» сто раз, как можно пройти точку останова в цикле на уровне L3 и перейти к L5?
Я попробовал команду игнорировать, но не смог:
$ python3 example.py
before loop
> /tmp/example.py(2)<module>()
-> for i in range(100):
(Pdb) ignore 0
*** Breakpoint 0 already deleted
(Pdb) c
> /tmp/example.py(2)<module>()
-> for i in range(100):
(Pdb) ignore 0
*** Breakpoint 0 already deleted
(Pdb) c
> /tmp/example.py(2)<module>()
-> for i in range(100):
Я хочу выполнить оставшуюся часть цикла, не отключая снова точку останова на L3, затем напечатать «после цикла» и прерваться перед печатью «выхода», оставаясь в отладчике. Ответ не должен требовать выхода из отладчика и повторного входа в среду выполнения или изменения исходного кода.
@Sayse Ответ не должен требовать... изменения исходного кода.






breakpoint — это обычная функция Python, поэтому в качестве некрасивого, но функционального решения вы можете временно перезаписать breakpoint новой функцией, которая выборочно пропускает точки останова.
Предположим, foo.py содержит следующее:
for i in range(100):
breakpoint()
breakpoint()
print("hi")
Запустите foo.py, и он сразу же сломается на первом breakpoint, где continuing неоднократно продолжает цикл:
> /code/foo.py(1)<module>()
-> for i in range(100):
(Pdb) c
> /code/foo.py(1)<module>()
-> for i in range(100):
(Pdb) c
> /code/foo.py(1)<module>()
-> for i in range(100):
Поместите следующее во временный модуль (например, tmp_break_3zfh.py):
import inspect
import builtins
def breakpoint():
caller = inspect.stack()[1]
if caller.filename.endswith("/foo.py") and caller.lineno == 2:
return
builtins.breakpoint()
Затем импортируйте ее из консоли pdb, чтобы перезаписать реальную функцию точки останова только для этого модуля. Когда мы continue, затем step (чтобы выйти из оболочки breakpoint и войти в вызывающую программу), мы видим, что мы остановлены после цикла:
(Pdb) from tmp_break_3zfh import breakpoint
(Pdb) c
--Return--
> /code/tmp_break_3zfh.py(8)breakpoint()->None
-> builtins.breakpoint()
(Pdb) s
--Return--
> /code/foo.py(5)<module>()
-> print("hi")
Вы можете использовать переменную среды PYTHONBREAKPOINT для вызова пользовательского обработчика точки останова, который вы определяете в отдельном файле.
Например:
$ export PYTHONBREAKPOINT=mybreak.mybreak
# mybreak/__init__.py
import pdb
_counter = 0
def mybreak(*args, **kwargs):
global _counter
if _counter >= 100:
# default behavior
pdb.set_trace(*args, **kwargs)
else:
# skip dropping into pdb while inside the range(100) loop
pass
_counter += 1
Возможно, вам придется немного повозиться, если в файле есть другие вызовы breakpoint(), но это будет просто вопрос отслеживания того, каким будет значение счетчика, когда вы захотите пропустить точку останова.
Альтернативная реализация пользовательского обработчика, как предложено в ответе nneonneo:
# mybreak/__init__.py
import inspect
import pdb
def mybreak():
caller = inspect.stack()[1]
if caller.filename.endswith("/foo.py") and caller.lineno == 2:
# skip this breakpoint
return
pdb.set_trace(*args, **kwargs)
Интересный. Я бы подумал, что PYTHONBREAKPOINT нужно будет установить вне среды выполнения, чтобы иметь какой-либо эффект, но интерактивное использование os.environ["PYTHONBREAKPOINT"] = "__main__.mybreak" действительно тоже работает.
Я бы подумал то же самое. Это полезно знать!
Чтобы уточнить ответ nneonneo и четко удовлетворить требование «без перезапуска интерпретатора или изменения исходного кода», вам не обязательно предоставлять хаки внутри точки останова.
Вы можете временно отключить встроенную функцию breakpoint и восстановить ее позже. Чтобы восстановить, просто добавьте точку останова pdb сразу после цикла и переназначьте ее обратно.
Вместо хранения исходного breakpoint в переменной вы можете использовать __import__('builtins').breakpoint - это также будет относиться к исходной функции (и лучше, когда ваш цикл фактически не находится в теле скрипта, см. ниже).
# python /tmp/q.py
before loop
> /tmp/q.py(2)<module>()
-> for i in range(100):
(Pdb) _my_old_breakpoint = breakpoint
(Pdb) breakpoint = lambda: None
(Pdb) break 4
Breakpoint 1 at /tmp/q.py:4
(Pdb) c
> /tmp/q.py(4)<module>()
-> print("after loop")
(Pdb) breakpoint = _my_old_breakpoint
(Pdb) c
after loop
> /tmp/q.py(6)<module>()
-> print("exit")
(Pdb) c
exit
Насколько я знаю, нет способа включить/отключить вызовы breakpoint через pdb, он управляет только «пользовательскими» точками останова, установленными в сеансе pdb.
Если этот цикл не находится в глобальной области видимости, вместо этого вам нужно будет изменить builtins:
cat > /tmp/q.py <<EOF
def main():
print("before loop")
for i in range(100):
breakpoint()
print("after loop")
breakpoint()
print("exit")
main()
EOF
# python /tmp/q.py
before loop
> /tmp/q.py(3)main()
-> for i in range(100):
(Pdb) import builtins
(Pdb) builtins._my_old_breakpoint=breakpoint
(Pdb) builtins.breakpoint = lambda: None
(Pdb) break 5
Breakpoint 1 at /tmp/q.py:5
(Pdb) c
> /tmp/q.py(5)main()
-> print("after loop")
(Pdb) builtins.breakpoint = builtins._my_old_breakpoint
(Pdb) c
after loop
> /tmp/q.py(7)main()
-> print("exit")
(Pdb) c
exit
Может быть, это тривиально, но я обычно добавляю дополнительный оператор if, чтобы поставить точки останова, когда они мне нужны условно (т. е.
if i == 0: breakpoint())