55that name.
66"""
77
8+ import functools
9+ import sys
10+ import os
11+ import tokenize
12+
813__all__ = ["getline" , "clearcache" , "checkcache" , "lazycache" ]
914
1015
1116# The cache. Maps filenames to either a thunk which will provide source code,
1217# or a tuple (size, mtime, lines, fullname) once loaded.
1318cache = {}
14- _interactive_cache = {}
1519
1620
1721def clearcache ():
@@ -45,54 +49,28 @@ def getlines(filename, module_globals=None):
4549 return []
4650
4751
48- def _getline_from_code (filename , lineno ):
49- lines = _getlines_from_code (filename )
50- if 1 <= lineno <= len (lines ):
51- return lines [lineno - 1 ]
52- return ''
53-
54- def _make_key (code ):
55- return (code .co_filename , code .co_qualname , code .co_firstlineno )
56-
57- def _getlines_from_code (code ):
58- code_id = _make_key (code )
59- if code_id in _interactive_cache :
60- entry = _interactive_cache [code_id ]
61- if len (entry ) != 1 :
62- return _interactive_cache [code_id ][2 ]
63- return []
64-
65-
6652def checkcache (filename = None ):
6753 """Discard cache entries that are out of date.
6854 (This is not checked upon each call!)"""
6955
7056 if filename is None :
71- # get keys atomically
72- filenames = cache .copy ().keys ()
73- else :
57+ filenames = list (cache .keys ())
58+ elif filename in cache :
7459 filenames = [filename ]
60+ else :
61+ return
7562
7663 for filename in filenames :
77- try :
78- entry = cache [filename ]
79- except KeyError :
80- continue
81-
64+ entry = cache [filename ]
8265 if len (entry ) == 1 :
8366 # lazy cache entry, leave it lazy.
8467 continue
8568 size , mtime , lines , fullname = entry
8669 if mtime is None :
8770 continue # no-op for files loaded via a __loader__
88- try :
89- # This import can fail if the interpreter is shutting down
90- import os
91- except ImportError :
92- return
9371 try :
9472 stat = os .stat (fullname )
95- except ( OSError , ValueError ) :
73+ except OSError :
9674 cache .pop (filename , None )
9775 continue
9876 if size != stat .st_size or mtime != stat .st_mtime :
@@ -104,17 +82,6 @@ def updatecache(filename, module_globals=None):
10482 If something's wrong, print a message, discard the cache entry,
10583 and return an empty list."""
10684
107- # These imports are not at top level because linecache is in the critical
108- # path of the interpreter startup and importing os and sys take a lot of time
109- # and slows down the startup sequence.
110- try :
111- import os
112- import sys
113- import tokenize
114- except ImportError :
115- # These import can fail if the interpreter is shutting down
116- return []
117-
11885 if filename in cache :
11986 if len (cache [filename ]) != 1 :
12087 cache .pop (filename , None )
@@ -161,20 +128,16 @@ def updatecache(filename, module_globals=None):
161128 try :
162129 stat = os .stat (fullname )
163130 break
164- except ( OSError , ValueError ) :
131+ except OSError :
165132 pass
166133 else :
167134 return []
168- except ValueError : # may be raised by os.stat()
169- return []
170135 try :
171136 with tokenize .open (fullname ) as fp :
172137 lines = fp .readlines ()
173138 except (OSError , UnicodeDecodeError , SyntaxError ):
174139 return []
175- if not lines :
176- lines = ['\n ' ]
177- elif not lines [- 1 ].endswith ('\n ' ):
140+ if lines and not lines [- 1 ].endswith ('\n ' ):
178141 lines [- 1 ] += '\n '
179142 size , mtime = stat .st_size , stat .st_mtime
180143 cache [filename ] = size , mtime , lines , fullname
@@ -203,29 +166,17 @@ def lazycache(filename, module_globals):
203166 return False
204167 # Try for a __loader__, if available
205168 if module_globals and '__name__' in module_globals :
206- spec = module_globals .get ('__spec__' )
207- name = getattr (spec , 'name' , None ) or module_globals ['__name__' ]
208- loader = getattr (spec , 'loader' , None )
209- if loader is None :
210- loader = module_globals .get ('__loader__' )
169+ name = module_globals ['__name__' ]
170+ if (loader := module_globals .get ('__loader__' )) is None :
171+ if spec := module_globals .get ('__spec__' ):
172+ try :
173+ loader = spec .loader
174+ except AttributeError :
175+ pass
211176 get_source = getattr (loader , 'get_source' , None )
212177
213178 if name and get_source :
214- def get_lines (name = name , * args , ** kwargs ):
215- return get_source (name , * args , ** kwargs )
179+ get_lines = functools .partial (get_source , name )
216180 cache [filename ] = (get_lines ,)
217181 return True
218182 return False
219-
220- def _register_code (code , string , name ):
221- entry = (len (string ),
222- None ,
223- [line + '\n ' for line in string .splitlines ()],
224- name )
225- stack = [code ]
226- while stack :
227- code = stack .pop ()
228- for const in code .co_consts :
229- if isinstance (const , type (code )):
230- stack .append (const )
231- _interactive_cache [_make_key (code )] = entry
0 commit comments