[Python-checkins] bpo-45017: move opcode-related logic from modulefinder to dis (GH-28246)

iritkatriel webhook-mailer at python.org
Thu Sep 9 09:04:21 EDT 2021


https://github.com/python/cpython/commit/04676b69466d2e6d2903f1c6879d2cb292721455
commit: 04676b69466d2e6d2903f1c6879d2cb292721455
branch: main
author: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2021-09-09T14:04:12+01:00
summary:

bpo-45017: move opcode-related logic from modulefinder to dis (GH-28246)

files:
M Lib/dis.py
M Lib/modulefinder.py
M Lib/test/test_dis.py

diff --git a/Lib/dis.py b/Lib/dis.py
index 66487dce0eefc..a073572e59e66 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -535,6 +535,42 @@ def findlinestarts(code):
             yield start, line
     return
 
+def _find_imports(co):
+    """Find import statements in the code
+
+    Generate triplets (name, level, fromlist) where
+    name is the imported module and level, fromlist are
+    the corresponding args to __import__.
+    """
+    IMPORT_NAME = opmap['IMPORT_NAME']
+    LOAD_CONST = opmap['LOAD_CONST']
+
+    consts = co.co_consts
+    names = co.co_names
+    opargs = [(op, arg) for _, op, arg in _unpack_opargs(co.co_code)
+                  if op != EXTENDED_ARG]
+    for i, (op, oparg) in enumerate(opargs):
+        if (op == IMPORT_NAME and i >= 2
+                and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
+            level = consts[opargs[i-2][1]]
+            fromlist = consts[opargs[i-1][1]]
+            yield (names[oparg], level, fromlist)
+
+def _find_store_names(co):
+    """Find names of variables which are written in the code
+
+    Generate sequence of strings
+    """
+    STORE_OPS = {
+        opmap['STORE_NAME'],
+        opmap['STORE_GLOBAL']
+    }
+
+    names = co.co_names
+    for _, op, arg in _unpack_opargs(co.co_code):
+        if op in STORE_OPS:
+            yield names[arg]
+
 
 class Bytecode:
     """The bytecode operations of a piece of code
diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py
index cb455f40c4d78..a0a020f9eeb9b 100644
--- a/Lib/modulefinder.py
+++ b/Lib/modulefinder.py
@@ -8,14 +8,6 @@
 import io
 import sys
 
-
-LOAD_CONST = dis.opmap['LOAD_CONST']
-IMPORT_NAME = dis.opmap['IMPORT_NAME']
-STORE_NAME = dis.opmap['STORE_NAME']
-STORE_GLOBAL = dis.opmap['STORE_GLOBAL']
-STORE_OPS = STORE_NAME, STORE_GLOBAL
-EXTENDED_ARG = dis.EXTENDED_ARG
-
 # Old imp constants:
 
 _SEARCH_ERROR = 0
@@ -394,24 +386,13 @@ def _safe_import_hook(self, name, caller, fromlist, level=-1):
 
     def scan_opcodes(self, co):
         # Scan the code, and yield 'interesting' opcode combinations
-        code = co.co_code
-        names = co.co_names
-        consts = co.co_consts
-        opargs = [(op, arg) for _, op, arg in dis._unpack_opargs(code)
-                  if op != EXTENDED_ARG]
-        for i, (op, oparg) in enumerate(opargs):
-            if op in STORE_OPS:
-                yield "store", (names[oparg],)
-                continue
-            if (op == IMPORT_NAME and i >= 2
-                    and opargs[i-1][0] == opargs[i-2][0] == LOAD_CONST):
-                level = consts[opargs[i-2][1]]
-                fromlist = consts[opargs[i-1][1]]
-                if level == 0: # absolute import
-                    yield "absolute_import", (fromlist, names[oparg])
-                else: # relative import
-                    yield "relative_import", (level, fromlist, names[oparg])
-                continue
+        for name in dis._find_store_names(co):
+            yield "store", (name,)
+        for name, level, fromlist in dis._find_imports(co):
+            if level == 0:  # absolute import
+                yield "absolute_import", (fromlist, name)
+            else:  # relative import
+                yield "relative_import", (level, fromlist, name)
 
     def scan_code(self, co, m):
         code = co.co_code
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index b97e41cdfab5e..a140a89f0e7e8 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1326,5 +1326,38 @@ def test_assert_not_in_with_arg_in_bytecode(self):
         with self.assertRaises(AssertionError):
             self.assertNotInBytecode(code, "LOAD_CONST", 1)
 
+class TestFinderMethods(unittest.TestCase):
+    def test__find_imports(self):
+        cases = [
+            ("import a.b.c", ('a.b.c', 0, None)),
+            ("from a.b import c", ('a.b', 0, ('c',))),
+            ("from a.b import c as d", ('a.b', 0, ('c',))),
+            ("from a.b import *", ('a.b', 0, ('*',))),
+            ("from ...a.b import c as d", ('a.b', 3, ('c',))),
+            ("from ..a.b import c as d, e as f", ('a.b', 2, ('c', 'e'))),
+            ("from ..a.b import *", ('a.b', 2, ('*',))),
+        ]
+        for src, expected in cases:
+            with self.subTest(src=src):
+                code = compile(src, "<string>", "exec")
+                res = tuple(dis._find_imports(code))
+                self.assertEqual(len(res), 1)
+                self.assertEqual(res[0], expected)
+
+    def test__find_store_names(self):
+        cases = [
+            ("x+y", ()),
+            ("x=y=1", ('x', 'y')),
+            ("x+=y", ('x',)),
+            ("global x\nx=y=1", ('x', 'y')),
+            ("global x\nz=x", ('z',)),
+        ]
+        for src, expected in cases:
+            with self.subTest(src=src):
+                code = compile(src, "<string>", "exec")
+                res = tuple(dis._find_store_names(code))
+                self.assertEqual(res, expected)
+
+
 if __name__ == "__main__":
     unittest.main()



More information about the Python-checkins mailing list