modulefinder.py 24 KB

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