modulefinder.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. """Find modules used by a script, using introspection."""
  2. import dis
  3. import importlib._bootstrap_external
  4. import importlib.machinery
  5. import marshal
  6. import os
  7. import io
  8. import sys
  9. import types
  10. import warnings
  11. LOAD_CONST = dis.opmap['LOAD_CONST']
  12. IMPORT_NAME = dis.opmap['IMPORT_NAME']
  13. STORE_NAME = dis.opmap['STORE_NAME']
  14. STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
  15. STORE_OPS = STORE_NAME, STORE_GLOBAL
  16. EXTENDED_ARG = dis.EXTENDED_ARG
  17. # Old imp constants:
  18. _SEARCH_ERROR = 0
  19. _PY_SOURCE = 1
  20. _PY_COMPILED = 2
  21. _C_EXTENSION = 3
  22. _PKG_DIRECTORY = 5
  23. _C_BUILTIN = 6
  24. _PY_FROZEN = 7
  25. # Modulefinder does a good job at simulating Python's, but it can not
  26. # handle __path__ modifications packages make at runtime. Therefore there
  27. # is a mechanism whereby you can register extra paths in this map for a
  28. # package, and it will be honored.
  29. # Note this is a mapping is lists of paths.
  30. packagePathMap = {}
  31. # A Public interface
  32. def AddPackagePath(packagename, path):
  33. packagePathMap.setdefault(packagename, []).append(path)
  34. replacePackageMap = {}
  35. # This ReplacePackage mechanism allows modulefinder to work around
  36. # situations in which a package injects itself under the name
  37. # of another package into sys.modules at runtime by calling
  38. # ReplacePackage("real_package_name", "faked_package_name")
  39. # before running ModuleFinder.
  40. def ReplacePackage(oldname, newname):
  41. replacePackageMap[oldname] = newname
  42. def _find_module(name, path=None):
  43. """An importlib reimplementation of imp.find_module (for our purposes)."""
  44. # It's necessary to clear the caches for our Finder first, in case any
  45. # modules are being added/deleted/modified at runtime. In particular,
  46. # test_modulefinder.py changes file tree contents in a cache-breaking way:
  47. importlib.machinery.PathFinder.invalidate_caches()
  48. spec = importlib.machinery.PathFinder.find_spec(name, path)
  49. if spec is None:
  50. raise ImportError("No module named {name!r}".format(name=name), name=name)
  51. # Some special cases:
  52. if spec.loader is importlib.machinery.BuiltinImporter:
  53. return None, None, ("", "", _C_BUILTIN)
  54. if spec.loader is importlib.machinery.FrozenImporter:
  55. return None, None, ("", "", _PY_FROZEN)
  56. file_path = spec.origin
  57. if spec.loader.is_package(name):
  58. return None, os.path.dirname(file_path), ("", "", _PKG_DIRECTORY)
  59. if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
  60. kind = _PY_SOURCE
  61. elif isinstance(spec.loader, importlib.machinery.ExtensionFileLoader):
  62. kind = _C_EXTENSION
  63. elif isinstance(spec.loader, importlib.machinery.SourcelessFileLoader):
  64. kind = _PY_COMPILED
  65. else: # Should never happen.
  66. return None, None, ("", "", _SEARCH_ERROR)
  67. file = io.open_code(file_path)
  68. suffix = os.path.splitext(file_path)[-1]
  69. return file, file_path, (suffix, "rb", kind)
  70. class Module:
  71. def __init__(self, name, file=None, path=None):
  72. self.__name__ = name
  73. self.__file__ = file
  74. self.__path__ = path
  75. self.__code__ = None
  76. # The set of global names that are assigned to in the module.
  77. # This includes those names imported through starimports of
  78. # Python modules.
  79. self.globalnames = {}
  80. # The set of starimports this module did that could not be
  81. # resolved, ie. a starimport from a non-Python module.
  82. self.starimports = {}
  83. def __repr__(self):
  84. s = "Module(%r" % (self.__name__,)
  85. if self.__file__ is not None:
  86. s = s + ", %r" % (self.__file__,)
  87. if self.__path__ is not None:
  88. s = s + ", %r" % (self.__path__,)
  89. s = s + ")"
  90. return s
  91. class ModuleFinder:
  92. def __init__(self, path=None, debug=0, excludes=None, replace_paths=None):
  93. if path is None:
  94. path = sys.path
  95. self.path = path
  96. self.modules = {}
  97. self.badmodules = {}
  98. self.debug = debug
  99. self.indent = 0
  100. self.excludes = excludes if excludes is not None else []
  101. self.replace_paths = replace_paths if replace_paths is not None else []
  102. self.processed_paths = [] # Used in debugging only
  103. def msg(self, level, str, *args):
  104. if level <= self.debug:
  105. for i in range(self.indent):
  106. print(" ", end=' ')
  107. print(str, end=' ')
  108. for arg in args:
  109. print(repr(arg), end=' ')
  110. print()
  111. def msgin(self, *args):
  112. level = args[0]
  113. if level <= self.debug:
  114. self.indent = self.indent + 1
  115. self.msg(*args)
  116. def msgout(self, *args):
  117. level = args[0]
  118. if level <= self.debug:
  119. self.indent = self.indent - 1
  120. self.msg(*args)
  121. def run_script(self, pathname):
  122. self.msg(2, "run_script", pathname)
  123. with io.open_code(pathname) as fp:
  124. stuff = ("", "rb", _PY_SOURCE)
  125. self.load_module('__main__', fp, pathname, stuff)
  126. def load_file(self, pathname):
  127. dir, name = os.path.split(pathname)
  128. name, ext = os.path.splitext(name)
  129. with io.open_code(pathname) as fp:
  130. stuff = (ext, "rb", _PY_SOURCE)
  131. self.load_module(name, fp, pathname, stuff)
  132. def import_hook(self, name, caller=None, fromlist=None, level=-1):
  133. self.msg(3, "import_hook", name, caller, fromlist, level)
  134. parent = self.determine_parent(caller, level=level)
  135. q, tail = self.find_head_package(parent, name)
  136. m = self.load_tail(q, tail)
  137. if not fromlist:
  138. return q
  139. if m.__path__:
  140. self.ensure_fromlist(m, fromlist)
  141. return None
  142. def determine_parent(self, caller, level=-1):
  143. self.msgin(4, "determine_parent", caller, level)
  144. if not caller or level == 0:
  145. self.msgout(4, "determine_parent -> None")
  146. return None
  147. pname = caller.__name__
  148. if level >= 1: # relative import
  149. if caller.__path__:
  150. level -= 1
  151. if level == 0:
  152. parent = self.modules[pname]
  153. assert parent is caller
  154. self.msgout(4, "determine_parent ->", parent)
  155. return parent
  156. if pname.count(".") < level:
  157. raise ImportError("relative importpath too deep")
  158. pname = ".".join(pname.split(".")[:-level])
  159. parent = self.modules[pname]
  160. self.msgout(4, "determine_parent ->", parent)
  161. return parent
  162. if caller.__path__:
  163. parent = self.modules[pname]
  164. assert caller is parent
  165. self.msgout(4, "determine_parent ->", parent)
  166. return parent
  167. if '.' in pname:
  168. i = pname.rfind('.')
  169. pname = pname[:i]
  170. parent = self.modules[pname]
  171. assert parent.__name__ == pname
  172. self.msgout(4, "determine_parent ->", parent)
  173. return parent
  174. self.msgout(4, "determine_parent -> None")
  175. return None
  176. def find_head_package(self, parent, name):
  177. self.msgin(4, "find_head_package", parent, name)
  178. if '.' in name:
  179. i = name.find('.')
  180. head = name[:i]
  181. tail = name[i+1:]
  182. else:
  183. head = name
  184. tail = ""
  185. if parent:
  186. qname = "%s.%s" % (parent.__name__, head)
  187. else:
  188. qname = head
  189. q = self.import_module(head, qname, parent)
  190. if q:
  191. self.msgout(4, "find_head_package ->", (q, tail))
  192. return q, tail
  193. if parent:
  194. qname = head
  195. parent = None
  196. q = self.import_module(head, qname, parent)
  197. if q:
  198. self.msgout(4, "find_head_package ->", (q, tail))
  199. return q, tail
  200. self.msgout(4, "raise ImportError: No module named", qname)
  201. raise ImportError("No module named " + qname)
  202. def load_tail(self, q, tail):
  203. self.msgin(4, "load_tail", q, tail)
  204. m = q
  205. while tail:
  206. i = tail.find('.')
  207. if i < 0: i = len(tail)
  208. head, tail = tail[:i], tail[i+1:]
  209. mname = "%s.%s" % (m.__name__, head)
  210. m = self.import_module(head, mname, m)
  211. if not m:
  212. self.msgout(4, "raise ImportError: No module named", mname)
  213. raise ImportError("No module named " + mname)
  214. self.msgout(4, "load_tail ->", m)
  215. return m
  216. def ensure_fromlist(self, m, fromlist, recursive=0):
  217. self.msg(4, "ensure_fromlist", m, fromlist, recursive)
  218. for sub in fromlist:
  219. if sub == "*":
  220. if not recursive:
  221. all = self.find_all_submodules(m)
  222. if all:
  223. self.ensure_fromlist(m, all, 1)
  224. elif not hasattr(m, sub):
  225. subname = "%s.%s" % (m.__name__, sub)
  226. submod = self.import_module(sub, subname, m)
  227. if not submod:
  228. raise ImportError("No module named " + subname)
  229. def find_all_submodules(self, m):
  230. if not m.__path__:
  231. return
  232. modules = {}
  233. # 'suffixes' used to be a list hardcoded to [".py", ".pyc"].
  234. # But we must also collect Python extension modules - although
  235. # we cannot separate normal dlls from Python extensions.
  236. suffixes = []
  237. suffixes += importlib.machinery.EXTENSION_SUFFIXES[:]
  238. suffixes += importlib.machinery.SOURCE_SUFFIXES[:]
  239. suffixes += importlib.machinery.BYTECODE_SUFFIXES[:]
  240. for dir in m.__path__:
  241. try:
  242. names = os.listdir(dir)
  243. except OSError:
  244. self.msg(2, "can't list directory", dir)
  245. continue
  246. for name in names:
  247. mod = None
  248. for suff in suffixes:
  249. n = len(suff)
  250. if name[-n:] == suff:
  251. mod = name[:-n]
  252. break
  253. if mod and mod != "__init__":
  254. modules[mod] = mod
  255. return modules.keys()
  256. def import_module(self, partname, fqname, parent):
  257. self.msgin(3, "import_module", partname, fqname, parent)
  258. try:
  259. m = self.modules[fqname]
  260. except KeyError:
  261. pass
  262. else:
  263. self.msgout(3, "import_module ->", m)
  264. return m
  265. if fqname in self.badmodules:
  266. self.msgout(3, "import_module -> None")
  267. return None
  268. if parent and parent.__path__ is None:
  269. self.msgout(3, "import_module -> None")
  270. return None
  271. try:
  272. fp, pathname, stuff = self.find_module(partname,
  273. parent and parent.__path__, parent)
  274. except ImportError:
  275. self.msgout(3, "import_module ->", None)
  276. return None
  277. try:
  278. m = self.load_module(fqname, fp, pathname, stuff)
  279. finally:
  280. if fp:
  281. fp.close()
  282. if parent:
  283. setattr(parent, partname, m)
  284. self.msgout(3, "import_module ->", m)
  285. return m
  286. def load_module(self, fqname, fp, pathname, file_info):
  287. suffix, mode, type = file_info
  288. self.msgin(2, "load_module", fqname, fp and "fp", pathname)
  289. if type == _PKG_DIRECTORY:
  290. m = self.load_package(fqname, pathname)
  291. self.msgout(2, "load_module ->", m)
  292. return m
  293. if type == _PY_SOURCE:
  294. co = compile(fp.read(), pathname, 'exec')
  295. elif type == _PY_COMPILED:
  296. try:
  297. data = fp.read()
  298. importlib._bootstrap_external._classify_pyc(data, fqname, {})
  299. except ImportError as exc:
  300. self.msgout(2, "raise ImportError: " + str(exc), pathname)
  301. raise
  302. co = marshal.loads(memoryview(data)[16:])
  303. else:
  304. co = None
  305. m = self.add_module(fqname)
  306. m.__file__ = pathname
  307. if co:
  308. if self.replace_paths:
  309. co = self.replace_paths_in_code(co)
  310. m.__code__ = co
  311. self.scan_code(co, m)
  312. self.msgout(2, "load_module ->", m)
  313. return m
  314. def _add_badmodule(self, name, caller):
  315. if name not in self.badmodules:
  316. self.badmodules[name] = {}
  317. if caller:
  318. self.badmodules[name][caller.__name__] = 1
  319. else:
  320. self.badmodules[name]["-"] = 1
  321. def _safe_import_hook(self, name, caller, fromlist, level=-1):
  322. # wrapper for self.import_hook() that won't raise ImportError
  323. if name in self.badmodules:
  324. self._add_badmodule(name, caller)
  325. return
  326. try:
  327. self.import_hook(name, caller, level=level)
  328. except ImportError as msg:
  329. self.msg(2, "ImportError:", str(msg))
  330. self._add_badmodule(name, caller)
  331. except SyntaxError as msg:
  332. self.msg(2, "SyntaxError:", str(msg))
  333. self._add_badmodule(name, caller)
  334. else:
  335. if fromlist:
  336. for sub in fromlist:
  337. fullname = name + "." + sub
  338. if fullname in self.badmodules:
  339. self._add_badmodule(fullname, caller)
  340. continue
  341. try:
  342. self.import_hook(name, caller, [sub], level=level)
  343. except ImportError as msg:
  344. self.msg(2, "ImportError:", str(msg))
  345. self._add_badmodule(fullname, caller)
  346. def scan_opcodes(self, co):
  347. # Scan the code, and yield 'interesting' opcode combinations
  348. code = co.co_code
  349. names = co.co_names
  350. consts = co.co_consts
  351. opargs = [(op, arg) for _, op, arg in dis._unpack_opargs(code)
  352. if op != EXTENDED_ARG]
  353. for i, (op, oparg) in enumerate(opargs):
  354. if op in STORE_OPS:
  355. yield "store", (names[oparg],)
  356. continue
  357. if (op == IMPORT_NAME and i >= 2
  358. and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
  359. level = consts[opargs[i-2][1]]
  360. fromlist = consts[opargs[i-1][1]]
  361. if level == 0: # absolute import
  362. yield "absolute_import", (fromlist, names[oparg])
  363. else: # relative import
  364. yield "relative_import", (level, fromlist, names[oparg])
  365. continue
  366. def scan_code(self, co, m):
  367. code = co.co_code
  368. scanner = self.scan_opcodes
  369. for what, args in scanner(co):
  370. if what == "store":
  371. name, = args
  372. m.globalnames[name] = 1
  373. elif what == "absolute_import":
  374. fromlist, name = args
  375. have_star = 0
  376. if fromlist is not None:
  377. if "*" in fromlist:
  378. have_star = 1
  379. fromlist = [f for f in fromlist if f != "*"]
  380. self._safe_import_hook(name, m, fromlist, level=0)
  381. if have_star:
  382. # We've encountered an "import *". If it is a Python module,
  383. # the code has already been parsed and we can suck out the
  384. # global names.
  385. mm = None
  386. if m.__path__:
  387. # At this point we don't know whether 'name' is a
  388. # submodule of 'm' or a global module. Let's just try
  389. # the full name first.
  390. mm = self.modules.get(m.__name__ + "." + name)
  391. if mm is None:
  392. mm = self.modules.get(name)
  393. if mm is not None:
  394. m.globalnames.update(mm.globalnames)
  395. m.starimports.update(mm.starimports)
  396. if mm.__code__ is None:
  397. m.starimports[name] = 1
  398. else:
  399. m.starimports[name] = 1
  400. elif what == "relative_import":
  401. level, fromlist, name = args
  402. if name:
  403. self._safe_import_hook(name, m, fromlist, level=level)
  404. else:
  405. parent = self.determine_parent(m, level=level)
  406. self._safe_import_hook(parent.__name__, None, fromlist, level=0)
  407. else:
  408. # We don't expect anything else from the generator.
  409. raise RuntimeError(what)
  410. for c in co.co_consts:
  411. if isinstance(c, type(co)):
  412. self.scan_code(c, m)
  413. def load_package(self, fqname, pathname):
  414. self.msgin(2, "load_package", fqname, pathname)
  415. newname = replacePackageMap.get(fqname)
  416. if newname:
  417. fqname = newname
  418. m = self.add_module(fqname)
  419. m.__file__ = pathname
  420. m.__path__ = [pathname]
  421. # As per comment at top of file, simulate runtime __path__ additions.
  422. m.__path__ = m.__path__ + packagePathMap.get(fqname, [])
  423. fp, buf, stuff = self.find_module("__init__", m.__path__)
  424. try:
  425. self.load_module(fqname, fp, buf, stuff)
  426. self.msgout(2, "load_package ->", m)
  427. return m
  428. finally:
  429. if fp:
  430. fp.close()
  431. def add_module(self, fqname):
  432. if fqname in self.modules:
  433. return self.modules[fqname]
  434. self.modules[fqname] = m = Module(fqname)
  435. return m
  436. def find_module(self, name, path, parent=None):
  437. if parent is not None:
  438. # assert path is not None
  439. fullname = parent.__name__+'.'+name
  440. else:
  441. fullname = name
  442. if fullname in self.excludes:
  443. self.msgout(3, "find_module -> Excluded", fullname)
  444. raise ImportError(name)
  445. if path is None:
  446. if name in sys.builtin_module_names:
  447. return (None, None, ("", "", _C_BUILTIN))
  448. path = self.path
  449. return _find_module(name, path)
  450. def report(self):
  451. """Print a report to stdout, listing the found modules with their
  452. paths, as well as modules that are missing, or seem to be missing.
  453. """
  454. print()
  455. print(" %-25s %s" % ("Name", "File"))
  456. print(" %-25s %s" % ("----", "----"))
  457. # Print modules found
  458. keys = sorted(self.modules.keys())
  459. for key in keys:
  460. m = self.modules[key]
  461. if m.__path__:
  462. print("P", end=' ')
  463. else:
  464. print("m", end=' ')
  465. print("%-25s" % key, m.__file__ or "")
  466. # Print missing modules
  467. missing, maybe = self.any_missing_maybe()
  468. if missing:
  469. print()
  470. print("Missing modules:")
  471. for name in missing:
  472. mods = sorted(self.badmodules[name].keys())
  473. print("?", name, "imported from", ', '.join(mods))
  474. # Print modules that may be missing, but then again, maybe not...
  475. if maybe:
  476. print()
  477. print("Submodules that appear to be missing, but could also be", end=' ')
  478. print("global names in the parent package:")
  479. for name in maybe:
  480. mods = sorted(self.badmodules[name].keys())
  481. print("?", name, "imported from", ', '.join(mods))
  482. def any_missing(self):
  483. """Return a list of modules that appear to be missing. Use
  484. any_missing_maybe() if you want to know which modules are
  485. certain to be missing, and which *may* be missing.
  486. """
  487. missing, maybe = self.any_missing_maybe()
  488. return missing + maybe
  489. def any_missing_maybe(self):
  490. """Return two lists, one with modules that are certainly missing
  491. and one with modules that *may* be missing. The latter names could
  492. either be submodules *or* just global names in the package.
  493. The reason it can't always be determined is that it's impossible to
  494. tell which names are imported when "from module import *" is done
  495. with an extension module, short of actually importing it.
  496. """
  497. missing = []
  498. maybe = []
  499. for name in self.badmodules:
  500. if name in self.excludes:
  501. continue
  502. i = name.rfind(".")
  503. if i < 0:
  504. missing.append(name)
  505. continue
  506. subname = name[i+1:]
  507. pkgname = name[:i]
  508. pkg = self.modules.get(pkgname)
  509. if pkg is not None:
  510. if pkgname in self.badmodules[name]:
  511. # The package tried to import this module itself and
  512. # failed. It's definitely missing.
  513. missing.append(name)
  514. elif subname in pkg.globalnames:
  515. # It's a global in the package: definitely not missing.
  516. pass
  517. elif pkg.starimports:
  518. # It could be missing, but the package did an "import *"
  519. # from a non-Python module, so we simply can't be sure.
  520. maybe.append(name)
  521. else:
  522. # It's not a global in the package, the package didn't
  523. # do funny star imports, it's very likely to be missing.
  524. # The symbol could be inserted into the package from the
  525. # outside, but since that's not good style we simply list
  526. # it missing.
  527. missing.append(name)
  528. else:
  529. missing.append(name)
  530. missing.sort()
  531. maybe.sort()
  532. return missing, maybe
  533. def replace_paths_in_code(self, co):
  534. new_filename = original_filename = os.path.normpath(co.co_filename)
  535. for f, r in self.replace_paths:
  536. if original_filename.startswith(f):
  537. new_filename = r + original_filename[len(f):]
  538. break
  539. if self.debug and original_filename not in self.processed_paths:
  540. if new_filename != original_filename:
  541. self.msgout(2, "co_filename %r changed to %r" \
  542. % (original_filename,new_filename,))
  543. else:
  544. self.msgout(2, "co_filename %r remains unchanged" \
  545. % (original_filename,))
  546. self.processed_paths.append(original_filename)
  547. consts = list(co.co_consts)
  548. for i in range(len(consts)):
  549. if isinstance(consts[i], type(co)):
  550. consts[i] = self.replace_paths_in_code(consts[i])
  551. return co.replace(co_consts=tuple(consts), co_filename=new_filename)
  552. def test():
  553. # Parse command line
  554. import getopt
  555. try:
  556. opts, args = getopt.getopt(sys.argv[1:], "dmp:qx:")
  557. except getopt.error as msg:
  558. print(msg)
  559. return
  560. # Process options
  561. debug = 1
  562. domods = 0
  563. addpath = []
  564. exclude = []
  565. for o, a in opts:
  566. if o == '-d':
  567. debug = debug + 1
  568. if o == '-m':
  569. domods = 1
  570. if o == '-p':
  571. addpath = addpath + a.split(os.pathsep)
  572. if o == '-q':
  573. debug = 0
  574. if o == '-x':
  575. exclude.append(a)
  576. # Provide default arguments
  577. if not args:
  578. script = "hello.py"
  579. else:
  580. script = args[0]
  581. # Set the path based on sys.path and the script directory
  582. path = sys.path[:]
  583. path[0] = os.path.dirname(script)
  584. path = addpath + path
  585. if debug > 1:
  586. print("path:")
  587. for item in path:
  588. print(" ", repr(item))
  589. # Create the module finder and turn its crank
  590. mf = ModuleFinder(path, debug, exclude)
  591. for arg in args[1:]:
  592. if arg == '-m':
  593. domods = 1
  594. continue
  595. if domods:
  596. if arg[-2:] == '.*':
  597. mf.import_hook(arg[:-2], None, ["*"])
  598. else:
  599. mf.import_hook(arg)
  600. else:
  601. mf.load_file(arg)
  602. mf.run_script(script)
  603. mf.report()
  604. return mf # for -i debugging
  605. if __name__ == '__main__':
  606. try:
  607. mf = test()
  608. except KeyboardInterrupt:
  609. print("\n[interrupted]")