Restarting from the debugger keeps state without third party Python hacks that you mention. In this example Python increments x twice, Lisp just once:
>>> x = 0
>>> def f():
... global x # yuck!
... x += 1
...
>>> def g(y):
... h()
...
>>>
>>> g(f())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in g
NameError: name 'h' is not defined
>>>
>>> def h(): pass
...
>>> g(f())
>>>
>>> x
2
Versus:
* (setf x 0)
* (defun f() (incf x))
* (defun g(y) (h))
* (g(f))
debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {1001878103}>:
The function COMMON-LISP-USER::H is undefined.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Retry calling H.
1: [USE-VALUE ] Call specified function.
2: [RETURN-VALUE ] Return specified values.
3: [RETURN-NOTHING] Return zero values.
4: [ABORT ] Exit debugger, returning to top level.
("undefined function")
0] (defun h() nil)
; No debug variables for current frame: using EVAL instead of EVAL-IN-FRAME.
H
0] 0
NIL
* x
1