reSTify PEP 280 (#318)

This commit is contained in:
Huang Huang 2017-08-12 02:47:47 +08:00 committed by Brett Cannon
parent 7f68262609
commit 75b53a7813
1 changed files with 413 additions and 401 deletions

View File

@ -5,12 +5,14 @@ Last-Modified: $Date$
Author: guido@python.org (Guido van Rossum)
Status: Deferred
Type: Standards Track
Content-Type: text/x-rst
Created: 10-Feb-2002
Python-Version: 2.3
Post-History:
Deferral
========
While this PEP is a nice idea, no-one has yet emerged to do the work of
hashing out the differences between this PEP, PEP 266 and PEP 267.
@ -18,6 +20,7 @@ Deferral
Abstract
========
This PEP describes yet another approach to optimizing access to
module globals, providing an alternative to PEP 266 (Optimizing
@ -30,6 +33,7 @@ Abstract
Description
===========
(Note: Jason Orendorff writes: """I implemented this once, long
ago, for Python 1.5-ish, I believe. I got it to the point where
@ -41,7 +45,7 @@ Description
Let a cell be a really simple Python object, containing a pointer
to a Python object and a pointer to a cell. Both pointers may be
NULL. A Python implementation could be:
``NULL``. A Python implementation could be::
class cell(object):
@ -54,7 +58,7 @@ Description
Let a celldict be a mapping from strings (the names of a module's
globals) to objects (the values of those globals), implemented
using a dict of cells. A Python implementation could be:
using a dict of cells. A Python implementation could be::
class celldict(object):
@ -113,26 +117,26 @@ Description
# Etc.
It is possible that a cell exists corresponding to a given key,
but the cell's objptr is NULL; let's call such a cell empty. When
but the cell's objptr is ``NULL``; let's call such a cell empty. When
the celldict is used as a mapping, it is as if empty cells don't
exist. However, once added, a cell is never deleted from a
celldict, and it is possible to get at empty cells using the
getcell() method.
``getcell()`` method.
The celldict implementation never uses the cellptr attribute of
cells.
We change the module implementation to use a celldict for its
__dict__. The module's getattr, setattr and delattr operations
``__dict__``. The module's getattr, setattr and delattr operations
now map to getitem, setitem and delitem on the celldict. The type
of <module>.__dict__ and globals() is probably the only backwards
of ``<module>.__dict__`` and ``globals()`` is probably the only backwards
incompatibility.
When a module is initialized, its __builtins__ is initialized from
the __builtin__ module's __dict__, which is itself a celldict.
For each cell in __builtins__, the new module's __dict__ adds a
cell with a NULL objptr, whose cellptr points to the corresponding
cell of __builtins__. Python pseudo-code (ignoring rexec):
When a module is initialized, its ``__builtins__`` is initialized from
the ``__builtin__`` module's ``__dict__``, which is itself a celldict.
For each cell in ``__builtins__``, the new module's ``__dict__`` adds a
cell with a ``NULL`` objptr, whose cellptr points to the corresponding
cell of ``__builtins__``. Python pseudo-code (ignoring rexec)::
import __builtin__
@ -157,52 +161,53 @@ Description
def __delattr__(self, k):
del self.__dict__[k]
The compiler generates LOAD_GLOBAL_CELL <i> (and STORE_GLOBAL_CELL
<i> etc.) opcodes for references to globals, where <i> is a small
The compiler generates ``LOAD_GLOBAL_CELL <i>`` (and ``STORE_GLOBAL_CELL
<i>`` etc.) opcodes for references to globals, where ``<i>`` is a small
index with meaning only within one code object like the const
index in LOAD_CONST. The code object has a new tuple, co_globals,
index in ``LOAD_CONST``. The code object has a new tuple, ``co_globals``,
giving the names of the globals referenced by the code indexed by
<i>. No new analysis is required to be able to do this.
``<i>``. No new analysis is required to be able to do this.
When a function object is created from a code object and a celldict,
the function object creates an array of cell pointers by asking the
celldict for cells corresponding to the names in the code object's
co_globals. If the celldict doesn't already have a cell for a
``co_globals``. If the celldict doesn't already have a cell for a
particular name, it creates and an empty one. This array of cell
pointers is stored on the function object as func_cells. When a
pointers is stored on the function object as ``func_cells``. When a
function object is created from a regular dict instead of a
celldict, func_cells is a NULL pointer.
celldict, ``func_cells`` is a ``NULL`` pointer.
When the VM executes a LOAD_GLOBAL_CELL <i> instruction, it gets
cell number <i> from func_cells. It then looks in the cell's
PyObject pointer, and if not NULL, that's the global value. If it
is NULL, it follows the cell's cell pointer to the next cell, if it
is not NULL, and looks in the PyObject pointer in that cell. If
that's also NULL, or if there is no second cell, NameError is
raised. (It could follow the chain of cell pointers until a NULL
When the VM executes a ``LOAD_GLOBAL_CELL <i>`` instruction, it gets
cell number ``<i>`` from ``func_cells``. It then looks in the cell's
``PyObject`` pointer, and if not ``NULL``, that's the global value. If it
is ``NULL``, it follows the cell's cell pointer to the next cell, if it
is not ``NULL``, and looks in the ``PyObject`` pointer in that cell. If
that's also ``NULL``, or if there is no second cell, ``NameError`` is
raised. (It could follow the chain of cell pointers until a ``NULL``
cell pointer is found; but I have no use for this.) Similar for
STORE_GLOBAL_CELL <i>, except it doesn't follow the cell pointer
``STORE_GLOBAL_CELL <i>``, except it doesn't follow the cell pointer
chain -- it always stores in the first cell.
There are fallbacks in the VM for the case where the function's
globals aren't a celldict, and hence func_cells is NULL. In that
case, the code object's co_globals is indexed with <i> to find the
globals aren't a celldict, and hence ``func_cells`` is ``NULL``. In that
case, the code object's ``co_globals`` is indexed with ``<i>`` to find the
name of the corresponding global and this name is used to index the
function's globals dict.
Additional Ideas
================
- Never make func_cell a NULL pointer; instead, make up an array
of empty cells, so that LOAD_GLOBAL_CELL can index func_cells
without a NULL check.
- Never make ``func_cell`` a ``NULL`` pointer; instead, make up an array
of empty cells, so that ``LOAD_GLOBAL_CELL`` can index ``func_cells``
without a ``NULL`` check.
- Make c.cellptr equal to c when a cell is created, so that
LOAD_GLOBAL_CELL can always dereference c.cellptr without a NULL
- Make ``c.cellptr`` equal to c when a cell is created, so that
``LOAD_GLOBAL_CELL`` can always dereference ``c.cellptr`` without a ``NULL``
check.
With these two additional ideas added, here's Python pseudo-code
for LOAD_GLOBAL_CELL:
for ``LOAD_GLOBAL_CELL``::
def LOAD_GLOBAL_CELL(self, i):
# self is the frame
@ -222,7 +227,7 @@ Additional Ideas
WRT #2, the set of builtins in the scheme above is captured at the
time a module dict is first created. Mutations to the set of builtin
names following that don't get reflected in the module dicts. Example:
consider files main.py and cheater.py:
consider files ``main.py`` and ``cheater.py``::
[main.py]
import cheater
@ -236,15 +241,15 @@ Additional Ideas
import __builtin__
__builtin__.pachinko = lambda: 666
If main.py is run under Python 2.2 (or before), 666 is printed. But
under the proposal, __builtin__.pachinko doesn't exist at the time
main's __dict__ is initialized. When the function object for
f is created, main.__dict__ grows a pachinko cell mapping to two
NULLs. When cheat() is called, __builtin__.__dict__ grows a pachinko
cell too, but main.__dict__ doesn't know-- and will never know --about
If ``main.py`` is run under Python 2.2 (or before), 666 is printed. But
under the proposal, ``__builtin__.pachinko`` doesn't exist at the time
main's ``__dict__`` is initialized. When the function object for
f is created, ``main.__dict__`` grows a pachinko cell mapping to two
``NULLs``. When ``cheat()`` is called, ``__builtin__.__dict__`` grows a pachinko
cell too, but ``main.__dict__`` doesn't know-- and will never know --about
that. When f's return stmt references pachinko, in will still find
the double-NULLs in main.__dict__'s pachinko cell, and so raise
NameError.
the double-NULLs in ``main.__dict__``'s ``pachinko`` cell, and so raise
``NameError``.
A similar (in cause) break in compatibility can occur if a module
global foo is del'ed, but a builtin foo was created prior to that
@ -263,18 +268,18 @@ Additional Ideas
synch.
Much of the scheme above remains the same, and most of the rest is
just a little different. A cell changes to:
just a little different. A cell changes to::
class cell(object):
def __init__(self, obj=NULL, builtin=0):
self.objptr = obj
self.builtinflag = builtin
and a celldict maps strings to this version of cells. builtinflag
and a celldict maps strings to this version of cells. ``builtinflag``
is true when and only when objptr contains a value obtained from
the builtins; in other words, it's true when and only when a cell
is acting as a cached value. When builtinflag is false, objptr is
the value of a module global (possibly NULL). celldict changes to:
is acting as a cached value. When ``builtinflag`` is false, objptr is
the value of a module global (possibly ``NULL``). celldict changes to::
class celldict(object):
@ -336,9 +341,9 @@ Additional Ideas
# Etc.
The speed benefit comes from simplifying LOAD_GLOBAL_CELL, which
The speed benefit comes from simplifying ``LOAD_GLOBAL_CELL``, which
I expect is executed more frequently than all other namespace
operations combined:
operations combined::
def LOAD_GLOBAL_CELL(self, i):
# self is the frame
@ -359,7 +364,7 @@ Additional Ideas
existing key, or deleting a key), traverse the list of module dicts
and make corresponding mutations to them. This is straightforward;
for example, if a key is deleted from builtins, execute
reflect_bltin_del in each module:
``reflect_bltin_del`` in each module::
def reflect_bltin_del(self, key):
c = self.__dict.get(key)
@ -371,9 +376,9 @@ Additional Ideas
# Else we're shadowing the builtin, so don't care that
# the builtin went away.
Note that c.builtinflag protects from us erroneously deleting a
Note that ``c.builtinflag`` protects from us erroneously deleting a
module global of the same name. Adding a new (key, value) builtin
pair is similar:
pair is similar::
def reflect_bltin_new(self, key, value):
c = self.__dict.get(key)
@ -390,7 +395,7 @@ Additional Ideas
# We're shadowing it already.
assert not c.builtinflag
Changing the value of an existing builtin:
Changing the value of an existing builtin::
def reflect_bltin_change(self, key, newvalue):
c = self.__dict.get(key)
@ -403,38 +408,42 @@ Additional Ideas
FAQs
====
Q. Will it still be possible to:
a) install new builtins in the __builtin__ namespace and have
* Q: Will it still be possible to:
a) install new builtins in the ``__builtin__`` namespace and have
them available in all already loaded modules right away ?
b) override builtins (e.g. open()) with my own copies
b) override builtins (e.g. ``open()``) with my own copies
(e.g. to increase security) in a way that makes these new
copies override the previous ones in all modules ?
A. Yes, this is the whole point of this design. In the original
approach, when LOAD_GLOBAL_CELL finds a NULL in the second
cell, it should go back to see if the __builtins__ dict has
A: Yes, this is the whole point of this design. In the original
approach, when ``LOAD_GLOBAL_CELL`` finds a ``NULL`` in the second
cell, it should go back to see if the ``__builtins__`` dict has
been modified (the pseudo code doesn't have this yet). Tim's
"more aggressive" alternative also takes care of this.
Q. How does the new scheme get along with the restricted execution
* Q: How does the new scheme get along with the restricted execution
model?
A. It is intended to support that fully.
A: It is intended to support that fully.
Q. What happens when a global is deleted?
* Q: What happens when a global is deleted?
A. The module's celldict would have a cell with a NULL objptr for
A: The module's celldict would have a cell with a ``NULL`` objptr for
that key. This is true in both variations, but the "aggressive"
variation goes on to see whether this unmasks a builtin of the
same name, and if so copies its value (just a pointer-copy of the
ultimate PyObject*) into the cell's objptr and sets the cell's
builtinflag to true.
ultimate ``PyObject*``) into the cell's objptr and sets the cell's
``builtinflag`` to true.
Q. What would the C code for LOAD_GLOBAL_CELL look like?
* Q: What would the C code for ``LOAD_GLOBAL_CELL`` look like?
A. The first version, with the first two bullets under "Additional
ideas" incorporated, could look like this:
A: The first version, with the first two bullets under "Additional
ideas" incorporated, could look like this::
case LOAD_GLOBAL_CELL:
cell = func_cells[oparg];
@ -450,7 +459,7 @@ FAQs
PUSH(x);
continue;
We could even write it like this (idea courtesy of Ka-Ping Yee):
We could even write it like this (idea courtesy of Ka-Ping Yee)::
case LOAD_GLOBAL_CELL:
cell = func_cells[oparg];
@ -466,10 +475,10 @@ FAQs
In modern CPU architectures, this reduces the number of
branches taken for built-ins, which might be a really good
thing, while any decent memory cache should realize that
cell->cellptr is the same as cell for regular globals and hence
``cell->cellptr`` is the same as cell for regular globals and hence
this should be very fast in that case too.
For the aggressive variant:
For the aggressive variant::
case LOAD_GLOBAL_CELL:
cell = func_cells[oparg];
@ -482,18 +491,19 @@ FAQs
... error recovery ...
break;
Q. What happens in the module's top-level code where there is
presumably no func_cells array?
* Q: What happens in the module's top-level code where there is
presumably no ``func_cells`` array?
A. We could do some code analysis and create a func_cells array,
or we could use LOAD_NAME which should use PyMapping_GetItem on
A: We could do some code analysis and create a ``func_cells`` array,
or we could use ``LOAD_NAME`` which should use ``PyMapping_GetItem`` on
the globals dict.
Graphics
========
Ka-Ping Yee supplied a drawing of the state of things after
"import spam", where spam.py contains:
"import spam", where ``spam.py`` contains::
import eggs
@ -510,16 +520,18 @@ Graphics
Comparison
==========
XXX Here, a comparison of the three approaches could be added.
Copyright
=========
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil