Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 329 – Treating Builtins as Constants in the Standard Library

PEP 329 – Treating Builtins as Constants in the Standard Library

Author:
Raymond Hettinger <python at rcn.com>
Status:
Rejected
Type:
Standards Track
Created:
18-Apr-2004
Python-Version:
2.4
Post-History:
18-Apr-2004

Table of Contents

Abstract

The proposal is to add a function for treating builtin references asconstants and to apply that function throughout the standard library.

Status

The PEP is self rejected by the author. Though the ASPN recipe waswell received, there was less willingness to consider this forinclusion in the core distribution.

The Jython implementation does not use byte codes, so its performancewould suffer if the current_len=len optimizations were removed.

Also, altering byte codes is one of the least clean ways to improveperformance and enable cleaner coding. A more robust solution wouldlikely involve compiler pragma directives or metavariables indicatingwhat can be optimized (similar to const/volatile declarations).

Motivation

The library contains code such as_len=len which is intended tocreate fast local references instead of slower global lookups. Thoughnecessary for performance, these constructs clutter the code and areusually incomplete (missing many opportunities).

If the proposal is adopted, those constructs could be eliminated fromthe code base and at the same time improve upon their results in termsof performance.

There are currently over a hundred instances ofwhile1 in thelibrary. They were not replaced with the more readablewhileTruebecause of performance reasons (the compiler cannot eliminate the testbecauseTrue is not known to always be a constant). Conversion ofTrue to a constant will clarify the code while retaining performance.

Many other basic Python operations run much slower because of globallookups. In try/except statements, the trapped exceptions aredynamically looked up before testing whether they match.Similarly, simple identity tests such aswhilexisnotNonerequire theNone variable to be re-looked up on every pass.Builtin lookups are especially egregious because the enclosing globalscope must be checked first. These lookup chains devour cache spacethat is best used elsewhere.

In short, if the proposal is adopted, the code will become cleanerand performance will improve across the board.

Proposal

Add a module called codetweaks.py which contains two functions,bind_constants() andbind_all(). The first function performsconstant binding and the second recursively applies it to everyfunction and class in a target module.

For most modules in the standard library, add a pair of lines nearthe end of the script:

importcodetweaks,syscodetweaks.bind_all(sys.modules[__name__])

In addition to binding builtins, there are some modules (likesre_compile) where it also makes sense to bind module variablesas well as builtins into constants.

Questions and Answers

  1. Will this make everyone divert their attention to optimizationissues?

    Because it is done automatically, it reduces the need to thinkabout optimizations.

  2. In a nutshell, how does it work?

    Every function has attributes with its bytecodes (the language ofthe Python virtual machine) and a table of constants. The bindfunction scans the bytecodes for aLOAD_GLOBAL instruction andchecks to see whether the value is already known. If so, it addsthat value to the constants table and replaces the opcode withLOAD_CONSTANT.

  3. When does it work?

    When a module is imported for the first time, python compiles thebytecode and runs the binding optimization. Subsequent importsjust re-use the previous work. Each session repeats this process(the results are not saved inpyc files).

  4. How do you know this works?

    I implemented it, applied it to every module in library, and the testsuite ran without exception.

  5. What if the module defines a variable shadowing a builtin?

    This does happen. For instance, True can be redefined at the modulelevel asTrue=(1==1). The sample implementation below detects theshadowing and leaves the global lookup unchanged.

  6. Are you the first person to recognize that most global lookups are forvalues that never change?

    No, this has long been known. Skip Montanaro provides an eloquentexplanation inPEP 266.

  7. What if I want to replace the builtins module and supply my ownimplementations?

    Either do this before importing a module, or just reload themodule, or disablecodetweaks.py (it will have a disable flag).

  8. How susceptible is this module to changes in Python’s byte coding?

    It importsopcode.py to protect against renumbering. Also, itusesLOAD_CONST andLOAD_GLOBAL which are fundamental and havebeen around forever. That notwithstanding, the coding scheme couldchange and this implementation would have to change along withmodules likedis which also rely on the current coding scheme.

  9. What is the effect on startup time?

    I could not measure a difference. None of the startup modules arebound except for warnings.py. Also, the binding function is veryfast, making just a single pass over the code string in search oftheLOAD_GLOBAL opcode.

Sample Implementation

Here is a sample implementation for codetweaks.py:

fromtypesimportClassType,FunctionTypefromopcodeimportopmap,HAVE_ARGUMENT,EXTENDED_ARGLOAD_GLOBAL,LOAD_CONST=opmap['LOAD_GLOBAL'],opmap['LOAD_CONST']ABORT_CODES=(EXTENDED_ARG,opmap['STORE_GLOBAL'])defbind_constants(f,builtin_only=False,stoplist=[],verbose=False):""" Return a new function with optimized global references.    Replaces global references with their currently defined values.    If not defined, the dynamic (runtime) global lookup is left undisturbed.    If builtin_only is True, then only builtins are optimized.    Variable names in the stoplist are also left undisturbed.    If verbose is True, prints each substitution as is occurs.    """import__builtin__env=vars(__builtin__).copy()stoplist=dict.fromkeys(stoplist)ifbuiltin_only:stoplist.update(f.func_globals)else:env.update(f.func_globals)co=f.func_codenewcode=map(ord,co.co_code)newconsts=list(co.co_consts)codelen=len(newcode)i=0whilei<codelen:opcode=newcode[i]ifopcodeinABORT_CODES:returnf# for simplicity, only optimize common casesifopcode==LOAD_GLOBAL:oparg=newcode[i+1]+(newcode[i+2]<<8)name=co.co_names[oparg]ifnameinenvandnamenotinstoplist:value=env[name]try:pos=newconsts.index(value)exceptValueError:pos=len(newconsts)newconsts.append(value)newcode[i]=LOAD_CONSTnewcode[i+1]=pos&0xFFnewcode[i+2]=pos>>8ifverbose:printname,'-->',valuei+=1ifopcode>=HAVE_ARGUMENT:i+=2codestr=''.join(map(chr,newcode))codeobj=type(co)(co.co_argcount,co.co_nlocals,co.co_stacksize,co.co_flags,codestr,tuple(newconsts),co.co_names,co.co_varnames,co.co_filename,co.co_name,co.co_firstlineno,co.co_lnotab,co.co_freevars,co.co_cellvars)returntype(f)(codeobj,f.func_globals,f.func_name,f.func_defaults,f.func_closure)defbind_all(mc,builtin_only=False,stoplist=[],verbose=False):"""Recursively apply bind_constants() to functions in a module or class.    Use as the last line of the module (after everything is defined, but    before test code).    In modules that need modifiable globals, set builtin_only to True.    """fork,vinvars(mc).items():iftype(v)isFunctionType:newv=bind_constants(v,builtin_only,stoplist,verbose)setattr(mc,k,newv)eliftype(v)in(type,ClassType):bind_all(v,builtin_only,stoplist,verbose)deff():passtry:f.func_code.codeexceptAttributeError:# detect non-CPython environmentsbind_all=lambda*args,**kwds:0delfimportsysbind_all(sys.modules[__name__])# Optimizer, optimize thyself!

Note the automatic detection of a non-CPython environment that does nothave bytecodes[2]. In that situation, the bind functions would simplyreturn the original function unchanged. This assures that the twoline additions to library modules do not impact other implementations.

The final code should add a flag to make it easy to disable binding.

References

[1] ASPN Recipe for a non-private implementationhttps://code.activestate.com/recipes/277940/

[2]
Differences between CPython and Jythonhttps://web.archive.org/web/20031018014238/http://www.jython.org/cgi-bin/faqw.py?req=show&file=faq01.003.htp

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-0329.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2026 Movatter.jp