Run source code in closure so code object created within source can find variables in locals correctly returns True if the source is executed, False otherwise
(self, source, globals, locals)
| 67 | |
| 68 | class PdbClosureBackport: |
| 69 | def _exec_in_closure(self, source, globals, locals): # type: ignore[no-untyped-def] |
| 70 | """Run source code in closure so code object created within source |
| 71 | can find variables in locals correctly |
| 72 | returns True if the source is executed, False otherwise |
| 73 | """ |
| 74 | |
| 75 | # Determine if the source should be executed in closure. Only when the |
| 76 | # source compiled to multiple code objects, we should use this feature. |
| 77 | # Otherwise, we can just raise an exception and normal exec will be used. |
| 78 | |
| 79 | code = compile(source, "<string>", "exec") |
| 80 | if not any(isinstance(const, CodeType) for const in code.co_consts): |
| 81 | return False |
| 82 | |
| 83 | # locals could be a proxy which does not support pop |
| 84 | # copy it first to avoid modifying the original locals |
| 85 | locals_copy = dict(locals) |
| 86 | |
| 87 | locals_copy["__pdb_eval__"] = {"result": None, "write_back": {}} |
| 88 | |
| 89 | # If the source is an expression, we need to print its value |
| 90 | try: |
| 91 | compile(source, "<string>", "eval") |
| 92 | except SyntaxError: |
| 93 | pass |
| 94 | else: |
| 95 | source = "__pdb_eval__['result'] = " + source |
| 96 | |
| 97 | # Add write-back to update the locals |
| 98 | source = ( |
| 99 | "try:\n" |
| 100 | + textwrap.indent(source, " ") |
| 101 | + "\n" |
| 102 | + "finally:\n" |
| 103 | + " __pdb_eval__['write_back'] = locals()" |
| 104 | ) |
| 105 | |
| 106 | # Build a closure source code with freevars from locals like: |
| 107 | # def __pdb_outer(): |
| 108 | # var = None |
| 109 | # def __pdb_scope(): # This is the code object we want to execute |
| 110 | # nonlocal var |
| 111 | # <source> |
| 112 | # return __pdb_scope.__code__ |
| 113 | source_with_closure = ( |
| 114 | "def __pdb_outer():\n" |
| 115 | + "\n".join(f" {var} = None" for var in locals_copy) |
| 116 | + "\n" |
| 117 | + " def __pdb_scope():\n" |
| 118 | + "\n".join(f" nonlocal {var}" for var in locals_copy) |
| 119 | + "\n" |
| 120 | + textwrap.indent(source, " ") |
| 121 | + "\n" |
| 122 | + " return __pdb_scope.__code__" |
| 123 | ) |
| 124 | |
| 125 | # Get the code object of __pdb_scope() |
| 126 | # The exec fills locals_copy with the __pdb_outer() function and we can call |