Skip to content

Commit 6dcb556

Browse files
authored
PyEval_GetLocals() to return a borrowed reference. Minor fixes to examples. (#2057)
1 parent 3eb5865 commit 6dcb556

File tree

1 file changed

+42
-21
lines changed

1 file changed

+42
-21
lines changed

pep-0667.rst

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
141143
Changes 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+
158157
Behavior of f_locals for optimized functions
159158
--------------------------------------------
160159

@@ -185,8 +184,12 @@ C-API
185184
PyEval_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

191194
This code::
192195

@@ -209,8 +212,9 @@ PyFrame_FastToLocals, etc.
209212
These functions were designed to convert the internal "fast" representation
210213
of 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

362383
Comparison with PEP 558
363384
=======================
@@ -372,8 +393,8 @@ complex, and has many corner cases which will lead to bugs.
372393
The key difference between this PEP and PEP 558 is that
373394
PEP 558 requires an internal copy of the local variables,
374395
whereas 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

378399
The semantics of ``frame.f_locals``
379400
-----------------------------------
@@ -412,7 +433,7 @@ An alternative way to define ``locals()`` would be simply as::
412433

413434
This would be simpler and easier to understand. However,
414435
there 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

417438
References
418439
==========

0 commit comments

Comments
 (0)