From 785d6348c953d92a197803788bf61b6101fc211c Mon Sep 17 00:00:00 2001 From: blhsing Date: Wed, 6 Nov 2024 07:53:54 +0800 Subject: [PATCH] gh-70764: inspect.getclosurevars now identifies global variables with LOAD_GLOBAL (GH-120143) (cherry picked from commit 83ba8c2bba834c0b92de669cac16fcda17485e0e) Co-authored-by: blhsing --- Lib/inspect.py | 14 +++++++++----- Lib/test/test_inspect/test_inspect.py | 13 +++++++++++++ .../2024-06-06-04-06-05.gh-issue-70764.6511hw.rst | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-06-04-06-05.gh-issue-70764.6511hw.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index 8df2383f60b011..161928dfa9fb09 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1609,11 +1609,15 @@ 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 + global_names = set() + for instruction in dis.get_instructions(code): + opname = instruction.opname + name = instruction.argval + 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: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index e2e4f1230abd50..34ae951b38ad58 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -2160,6 +2160,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): 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.