[Python-ideas] Draft PEP on string interpolation

Eric Fahlgren ericfahlgren at gmail.com
Sun Aug 30 20:40:05 CEST 2015


On Sat Aug 29 18:41:55 CEST 2015, Ron Adam wrote:
> One is nested evaluations, but that may be fixable.
> 
>      Nesting arguments and more complex examples:
> 
>      >>> for align, text in zip('<^>', ['left', 'center', 'right']):
>      ...     '{0:{fill}{align}16}'.format(text, fill=align, align=align)
>      ...
>      'left<<<<<<<<<<<<'
>      '^^^^^center^^^^^'
>      '>>>>>>>>>>>right'
> 
> I haven't gotten this example to work yet.

I've deployed an implementation of my interpretation of PEP-498 in our 2.7
production code, tested in 3.4.
Here's the function and your test case working properly:

#!/bin/env python
from __future__ import print_function, division

import sys as _sys

try:
    import _string  # Py3
    def _parseFormat(string):
        return _string.formatter_parser(string)
    unicode = str
except ImportError: # Py2
    def _parseFormat(string):
        return string._formatter_parser()

def _stringInterpolater(s, depth=1, evalCallback=None, context=None,
**kwds):
    """
    $uuid:4cbb6191-464a-56dd-88e2-52f4f861527e$

    Experimental implementation of the behavior described in
        https://www.python.org/dev/peps/pep-0498/

    The first extension, ``depth``, allows for reimplementation from a
    function that looks deeper into the call stack for the "current" context
    (see ``printi``, below).

    The second extension, ``evalCallback``, allows us to intercept the value
    before formatting so that we can, for example, convert a Marker object
    to an Adams id (see Simulatable.formatFunction for details).

    ``context`` allows the user to pass a dictionary to completely replace
    the namespace calculations performed below.  It too gets superseded by
    ``kwds``.
    """

    # Local frame is needed for error reporting, so calculate it even
    # when the user supplies a context.
    localFrame = _sys._getframe()
    while depth:
        localFrame = localFrame.f_back
        depth -= 1

    if context:
        localDict  = kwds
        globalDict = context.copy()
    else:
        localDict  = localFrame.f_locals.copy() # Must copy to avoid side
effects.
        localDict.update(kwds)                  # Our **kwds override
locals.
        globalDict = localFrame.f_globals

    def doFormat(formatString):
        """ $uuid:7c45191f-741b-51a0-b75a-a534e99f58cb$ """
        result = list()
        for text, expression, formatSpec, conversion in
_parseFormat(formatString):
            if text:
                result.append(text)

            if expression is None:
                break

            value = eval(expression, globalDict, localDict)
            if evalCallback:
                value = evalCallback(value)

            if conversion == "r":
                if isinstance(value, unicode): # Delete this check in Py3.
                    value = str(value) # Eat the annoying "u" prefix in Py2.
                value = repr(value)
            elif conversion == "s":
                value = str(value)

            formatSpec = doFormat(formatSpec) # Recurse to evaluate embedded
formats.

            try:
                result.append(format(value, formatSpec))
            except ValueError as e:
                raise ValueError("{}, object named '{}'".format(str(e),
expression), localFrame)

        return "".join(result)

    return doFormat(s)

f = _stringInterpolater

def printi(*args, **kwds):
    """
    $uuid:15cccafc-f4ae-58df-ab3e-03553e567bf0$
    """
    sep  = kwds.pop("sep", " ") # Py2 smell, in Py3 you'd just put them in
the signature.
    end  = kwds.pop("end", "\n")
    file = kwds.pop("file", _sys.stdout)
    newArgs = list()
    for arg in args:
        if isinstance(arg, (str, unicode)):
            newArgs.append(f(arg, depth=2, **kwds))
    print(*newArgs, sep=sep, end=end, file=file)


if __name__ == "__main__":
    strings = {
        "<" : "left ",
        "^" : " center ",
        ">" : " right",
    }
    width   = 2*5 + max(len(s) for s in strings.values())

    for align in strings:
        fill = align
        print(f("{strings[align]:{fill}{align}{width}}"))
        printi("{strings[align]:{fill}{align}{width}}")



More information about the Python-ideas mailing list