@@ -107,6 +107,10 @@ class IPythonKernel(KernelBase):
107107 # now costs nothing. See also the `debugger` property.
108108 debugger_class = LazyType ("ipykernel.debugger.Debugger" )
109109
110+ _debugger : Any | None = None
111+ _debugger_init_attempted : bool | None = None
112+ _stopped_queue_poll_started : bool = False
113+
110114 compiler_class = Type (XCachingCompiler )
111115
112116 use_experimental_completions = Bool (
@@ -150,18 +154,14 @@ def __init__(self, **kwargs):
150154 m .__file__ for m in sys .modules .copy ().values () if hasattr (m , "__file__" ) and m .__file__
151155 ]
152156
153- # The debugger itself (and the debugpy import it requires) is
154- # created lazily on first use, see the `debugger` property below.
155- self ._debugger = None
156- self ._debugger_init_attempted = False
157- self ._stopped_queue_poll_started = False
158-
159157 if "debugger_class" in self ._trait_values :
160158 # Someone explicitly picked a debugger class, via kwargs or config.
161159 # The class is therefore already imported, so there is nothing left
162160 # to defer: build the debugger now, as pre-7.4 versions did. This
163161 # also keeps the failure mode of a bad `debugger_class` at
164162 # construction time rather than at the first debug request.
163+ # todo: maybe just delete that and move it to lazy at a future date
164+ # if we think is it ok
165165 _ = self .debugger
166166
167167 # Initialize the InteractiveShell subclass
@@ -251,44 +251,46 @@ def debugger(self):
251251 Importing debugpy is expensive, so we avoid it until a debug
252252 request actually comes in.
253253 """
254- if self ._debugger is None and not self ._debugger_init_attempted :
255- from . debugger import _is_debugpy_available
254+ if self ._debugger is not None or self ._debugger_init_attempted :
255+ return self . _debugger
256256
257- if not _is_debugpy_available :
258- # A module-level constant: it will not become True later, so
259- # this is the one answer worth caching.
260- self ._debugger_init_attempted = True
261- return None
257+ # keep lazy import because of side effects and slow import
258+ # or move to lazy import once python 3.15+
259+ from .debugger import _is_debugpy_available
262260
263- debugger_class = self .debugger_class
264- try :
265- debugger = debugger_class (
266- self .log ,
267- self .debugpy_stream ,
268- self ._publish_debug_event ,
269- self .debug_shell_socket ,
270- self .session ,
271- self ._kernel_modules ,
272- self .debug_just_my_code ,
273- self .filter_internal_frames ,
274- )
275- except Exception :
276- # Deliberately do not set `_debugger_init_attempted`: a
277- # failure here must not silently turn every later debug
278- # request into a `None` reply. Let it raise (so the request
279- # gets a proper error reply) and retry next time.
280- self .log .exception ("Failed to initialize the debugger from %r" , debugger_class )
281- raise
282-
283- self ._debugger = debugger
261+ if not _is_debugpy_available :
262+ # A module-level constant: it will not become True later, so
263+ # this is the one answer worth caching.
284264 self ._debugger_init_attempted = True
285- self ._ensure_stopped_queue_poll ()
265+ return None
266+
267+ debugger_class = self .debugger_class
268+ try :
269+ debugger = debugger_class (
270+ self .log ,
271+ self .debugpy_stream ,
272+ self ._publish_debug_event ,
273+ self .debug_shell_socket ,
274+ self .session ,
275+ self ._kernel_modules ,
276+ self .debug_just_my_code ,
277+ self .filter_internal_frames ,
278+ )
279+ except Exception :
280+ # Deliberately do not set `_debugger_init_attempted`: a
281+ # failure here must not silently turn every later debug
282+ # request into a `None` reply. Let it raise (so the request
283+ # gets a proper error reply) and retry next time.
284+ self .log .exception ("Failed to initialize the debugger from %r" , debugger_class )
285+ raise
286+
287+ self ._debugger = debugger
288+ self ._debugger_init_attempted = True
289+ self ._ensure_stopped_queue_poll ()
286290 return self ._debugger
287291
288292 @debugger .setter
289293 def debugger (self , value ):
290- # `debugger` used to be a plain instance attribute assigned in
291- # __init__; keep it writable for subclasses that replace it.
292294 self ._debugger = value
293295 self ._debugger_init_attempted = True
294296 if value is not None :
@@ -297,7 +299,7 @@ def debugger(self, value):
297299 # picks it up.
298300 self ._ensure_stopped_queue_poll ()
299301
300- def _ensure_stopped_queue_poll (self ):
302+ def _ensure_stopped_queue_poll (self ) -> None :
301303 """Schedule `poll_stopped_queue` once, as soon as it can run.
302304
303305 Called both when the debugger is created (or assigned) and from
0 commit comments