From 1f89b78f6809fa2df44c59383faa9017376e6061 Mon Sep 17 00:00:00 2001 From: Ben Hsing Date: Thu, 6 Jun 2024 03:54:56 +0000 Subject: [PATCH 1/3] gh-70764: getclosurevars now identifies global variables from LOAD_GLOBAL instructions instead --- Lib/inspect.py | 21 +++++++++++---------- Lib/test/test_inspect/test_inspect.py | 13 +++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index e6e49a4ffa673a..b600575d37019e 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1593,18 +1593,19 @@ def getclosurevars(func): global_vars = {} builtin_vars = {} unbound_names = set() - for name in code.co_names: - if name in ("None", "True", "False"): - # Because these used to be builtins instead of keywords, they - # may still show up as name references. We ignore them. - continue - try: - global_vars[name] = global_ns[name] - except KeyError: + for instruction in dis.get_instructions(code): + opname = instruction.opname + name = instruction.argval + if opname == "LOAD_GLOBAL": try: - builtin_vars[name] = builtin_ns[name] + global_vars[name] = global_ns[name] except KeyError: - unbound_names.add(name) + try: + builtin_vars[name] = builtin_ns[name] + except KeyError: + unbound_names.add(name) + elif opname == "LOAD_ATTR": + unbound_names.add(name) return ClosureVars(nonlocal_vars, global_vars, builtin_vars, unbound_names) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 011d42f34b6461..e4585d0be8e678 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1803,6 +1803,19 @@ def g(local_ref): builtin_vars, unbound_names) self.assertEqual(inspect.getclosurevars(C().f(_arg)), expected) + def test_attribute_same_name_as_global_var(self): + class C: + _global_ref = object() + def f(): + print(C._global_ref, _global_ref) + nonlocal_vars = {"C": C} + global_vars = {"_global_ref": _global_ref} + builtin_vars = {"print": print} + unbound_names = {"_global_ref"} + expected = inspect.ClosureVars(nonlocal_vars, global_vars, + builtin_vars, unbound_names) + self.assertEqual(inspect.getclosurevars(f), expected) + def test_nonlocal_vars(self): # More complex tests of nonlocal resolution def _nonlocal_vars(f): From 6fd1ae02070fa0e0f1a2c889f824ae639d6b07ed Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 04:06:11 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst diff --git a/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst b/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst new file mode 100644 index 00000000000000..4cfb66a6ccc6ee --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst @@ -0,0 +1 @@ +Fixed an issue where :func:`inspect.getclosurevars` would incorrectly classify an attribute name as a global variable when the name exists both as an attribute name and a global variable. From 4967e42e8fefe543caa9e32e972c4cafd6d409ea Mon Sep 17 00:00:00 2001 From: Ben Hsing Date: Fri, 7 Jun 2024 09:49:52 +0000 Subject: [PATCH 3/3] gh-70764: improved performance by collecting global names in a set before looking them up --- Lib/inspect.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index b600575d37019e..024b1b80da5ae0 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1593,19 +1593,22 @@ def getclosurevars(func): global_vars = {} builtin_vars = {} unbound_names = set() + global_names = set() for instruction in dis.get_instructions(code): opname = instruction.opname name = instruction.argval - if opname == "LOAD_GLOBAL": + if opname == "LOAD_ATTR": + unbound_names.add(name) + elif opname == "LOAD_GLOBAL": + global_names.add(name) + for name in global_names: + try: + global_vars[name] = global_ns[name] + except KeyError: try: - global_vars[name] = global_ns[name] + builtin_vars[name] = builtin_ns[name] except KeyError: - try: - builtin_vars[name] = builtin_ns[name] - except KeyError: - unbound_names.add(name) - elif opname == "LOAD_ATTR": - unbound_names.add(name) + unbound_names.add(name) return ClosureVars(nonlocal_vars, global_vars, builtin_vars, unbound_names)