@@ -92,8 +92,9 @@ for function scopes it will be a custom class.
9292``locals() `` will be defined as::
9393
9494 def locals():
95- f_locals = sys._getframe(1).f_locals
96- if not isinstance(f_locals, dict):
95+ frame = sys._getframe(1)
96+ f_locals = frame.f_locals
97+ if frame.is_function():
9798 f_locals = dict(f_locals)
9899 return f_locals
99100
@@ -109,6 +110,7 @@ For example::
109110 return sys._getframe(1).f_locals
110111
111112 def test():
113+ if 0: y = 1 # Make 'y' a local variable
112114 x = 1
113115 l()['x'] = 2
114116 l()['y'] = 4
@@ -141,20 +143,17 @@ Both functions will return a new reference.
141143Changes to existing APIs
142144''''''''''''''''''''''''
143145
144- The existing C-API function ``PyEval_GetLocals() `` will always raise an
145- exception with a message like::
146+ The C-API function ``PyEval_GetLocals() `` will be deprecated.
147+ `` PyEval_Locals() `` should be used instead.
146148
147- PyEval_GetLocals() is unsafe. Please use PyEval_Locals() instead.
148-
149- This is necessary as ``PyEval_GetLocals() ``
150- returns a borrowed reference which cannot be made safe.
151-
152- The following functions will be retained, but will become no-ops::
149+ The following three functions will become no-ops, and will be deprecated::
153150
154151 PyFrame_FastToLocalsWithError()
155152 PyFrame_FastToLocals()
156153 PyFrame_LocalsToFast()
157154
155+ The above four deprecated functions will be removed in 3.13.
156+
158157Behavior of f_locals for optimized functions
159158--------------------------------------------
160159
@@ -185,8 +184,12 @@ C-API
185184PyEval_GetLocals
186185''''''''''''''''
187186
188- Code that uses ``PyEval_GetLocals() `` will continue to operate safely, but
189- will need to be changed to use ``PyEval_Locals() `` to restore functionality.
187+ Because ``PyEval_GetLocals() `` returns a borrowed reference, it requires
188+ the dictionary to be cached on the frame, extending its lifetime and
189+ forces memory to be allocated for the frame object on the heap as well.
190+
191+ Using ``PyEval_Locals() `` will be much more efficient
192+ than ``PyEval_GetLocals() ``.
190193
191194This code::
192195
@@ -209,8 +212,9 @@ PyFrame_FastToLocals, etc.
209212These functions were designed to convert the internal "fast" representation
210213of the locals variables of a function to a dictionary, and vice versa.
211214
212- Calls to them are no longer required. C code that directly accesses the ``f_locals ``
213- field of a frame should be modified to call ``PyFrame_GetLocals() `` instead::
215+ Calls to them are no longer required. C code that directly accesses the
216+ ``f_locals `` field of a frame should be modified to call
217+ ``PyFrame_GetLocals() `` instead::
214218
215219 PyFrame_FastToLocals(frame);
216220 PyObject *locals = frame.f_locals;
@@ -246,14 +250,17 @@ They serve only to illustrate the proposed design.
246250
247251 def __init__(self, ...):
248252 self._name_to_offset_mapping_impl = NULL
253+ self._variable_names = deduplicate(
254+ self.co_varnames + self.co_cellvars + self.co_freevars
255+ )
249256 ...
250257
251258 @property
252259 def _name_to_offset_mapping(self):
253260 "Mapping of names to offsets in local variable array."
254261 if self._name_to_offset_mapping_impl is NULL:
255262 self._name_to_offset_mapping_impl = {
256- name: index for (index, name) in enumerate(self.co_varnames )
263+ name: index for (index, name) in enumerate(self._variable_names )
257264 }
258265 return self._name_to_offset_mapping_impl
259266
@@ -315,7 +322,7 @@ They serve only to illustrate the proposed design.
315322 f = self._frame
316323 co = f.f_code
317324 yield from iter(f._extra_locals)
318- for index, name in enumerate(co._varnames ):
325+ for index, name in enumerate(co._variable_names ):
319326 val = f._locals[index]
320327 if val is NULL:
321328 continue
@@ -330,7 +337,7 @@ They serve only to illustrate the proposed design.
330337 co = f.f_code
331338 if f._extra_locals:
332339 return f._extra_locals.pop()
333- for index, _ in enumerate(co._varnames ):
340+ for index, _ in enumerate(co._variable_names ):
334341 val = f._locals[index]
335342 if val is NULL:
336343 continue
@@ -348,7 +355,7 @@ They serve only to illustrate the proposed design.
348355 f = self._frame
349356 co = f.f_code
350357 res = 0
351- for index, _ in enumerate(co._varnames ):
358+ for index, _ in enumerate(co._variable_names ):
352359 val = f._locals[index]
353360 if val is NULL:
354361 continue
@@ -358,6 +365,20 @@ They serve only to illustrate the proposed design.
358365 res += 1
359366 return len(self._extra_locals) + res
360367
368+ C API
369+ -----
370+
371+ ``PyEval_GetLocals() `` will be implemented roughly as follows::
372+
373+ PyObject *PyEval_GetLocals(void) {
374+ PyFrameObject * = ...; // Get the current frame.
375+ Py_CLEAR(frame->_locals_cache);
376+ frame->_locals_cache = PyEval_Locals();
377+ return frame->_locals_cache;
378+ }
379+
380+ As with all functions that return a borrowed reference, care must be taken to
381+ ensure that the reference is not used beyond the lifetime of the object.
361382
362383Comparison with PEP 558
363384=======================
@@ -372,8 +393,8 @@ complex, and has many corner cases which will lead to bugs.
372393The key difference between this PEP and PEP 558 is that
373394PEP 558 requires an internal copy of the local variables,
374395whereas this PEP does not.
375- Maintaining a copy would add considerably to the complexity of both
376- the specification and implementation, and bring no real benefits.
396+ Maintaining a copy adds considerably to the complexity of both
397+ the specification and implementation, and brings no real benefits.
377398
378399The semantics of ``frame.f_locals ``
379400-----------------------------------
@@ -412,7 +433,7 @@ An alternative way to define ``locals()`` would be simply as::
412433
413434This would be simpler and easier to understand. However,
414435there would be backwards compatibility issues when ``locals `` is assigned
415- to a local variable or when passed to ``eval ``.
436+ to a local variable or when passed to ``eval `` or `` exec `` .
416437
417438References
418439==========
0 commit comments