[PYTHON DOC-SIG] String interpolation module

Ka-Ping Yee kpyee@aw.sgi.com
Sun, 20 Oct 1996 23:27:03 +0900


This is a multi-part message in MIME format.

--------------237C2F1C7DE1
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

I wrote:
> I'm currently thinking of a string interpolation scheme
[...]

Robin Friedrich wrote:
> 
> Dollar signs sends shivers down my spine, but carry on.

Perhaps, but i really don't see much other alternative.
Can you think of something better?

> I think this will be the most controversial of your proposals
> but well worth the debate. I'll include it as a definate topic
> at the workshop.

Thanks.  Yes, i imagine it is controversial because it makes
Python look a little like other languages.  But i don't think
that this reaction should overshadow its utility.  Comparing

    "Here is an @{[&functioncall($with, $arguments)]}." (Perl 5)

to

    "Here is a $functioncall(with, arguments)."  (Python + Itpl)

should help to show people that interpolation does NOT necessarily
mean riddling the entire language with punctuation.  We're talking
one and only one new special character here.

To back up my proposal and give everyone (including myself!) a
chance to actually try this out and see how it feels, i've coded
up the Itpl module as i described.  Because it is so short
(90 lines) i've decided to attach the module to this letter.
I hope that subscribers to this list don't mind.

The module contains the "Itpl" class and three functions:

    printpl(str): interpolate and print the string 'str'

    filter(file): wrap 'file' in an interpolating filter for
        the 'write' method and return a new file-like object

    unfilter(itplfile): unwrap the filtered 'itplfile' and
        return the original file object

By default, filter and unfilter operate on "sys.stdout".

This allows you to do:

    Python 1.4b3 (Oct  3 1996) [C]
    Copyright 1991-1996 Stichting Mathematisch Centrum, Amsterdam
    >>> from Itpl import printpl
    >>> x = 3
    >>> y = ['spam', (4, 6), 'eggs']
    >>>
    >>> printpl("The number of the count shall be $x.")
    The number of the count shall be 3.
    >>> printpl("This list contains $y[0], $y[1], and $y[2].")
    This list contains spam, (4, 6), and eggs.

Or, to make 'print' temporarily assume this functionality:

    Python 1.4b3 (Oct  3 1996) [C]
    Copyright 1991-1996 Stichting Mathematisch Centrum, Amsterdam
    >>> import Itpl, sys
    >>> sys.stdout = Itpl.filter()
    >>> z = 'python'
    >>>
    >>> print "$sys has been imported."
    <module 'sys'> has been imported.
    >>> print "$sys.stdout"
    <interpolated <open file '<stdout>', mode 'w' at 10051700>>
    >>> print "$z[1:]$z[0]ay"
    ythonpay
    >>> sys.stdout = Itpl.unfilter()
    >>> print "$phew, $back $to $normal."
    $phew, $back $to $normal.

Notice how Python's string slicing gives us a bit more convenience
than Perl in generating Pig Latin. :)  Anyway, i'd be very grateful
if you could try it out and send me your comments.


Ping
                                 Developer, Alias|Wavefront Inc. (Tokyo)
http://www.lfw.org/math/ brings math to the Web as easy as <se>?pi?</se>

--------------237C2F1C7DE1
Content-Type: text/plain; charset=us-ascii; name="Itpl.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="Itpl.py"

import sys, string
from types import StringType
from tokenize import tokenprog

ItplError = "ItplError"
class Itpl:
    def __init__(self, fmt):
        if type(fmt) != StringType:
            raise TypeError, "needs string initializer"

        namechars = 'abcdefghijklmnopqrstuvwxyz' \
            'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
        chunks = []
        pos = 0

        try:
            while 1:
                dollar = string.index(fmt, '$', pos)
                nextchar = fmt[dollar+1]

                if nextchar == '{':
                    chunks.append((0, fmt[pos:dollar]))
                    pos, level = dollar+2, 1
                    while level:
                        pos = pos + tokenprog.match(fmt, pos)
                        tstart, tend = tokenprog.regs[3]
                        token = fmt[tstart:tend]
                        if token == '{': level = level+1
                        elif token == '}': level = level-1
                    chunks.append((1, fmt[dollar+2:pos-1]))

                elif nextchar in namechars:
                    chunks.append((0, fmt[pos:dollar]))
                    pos = dollar + 1
                    pos = pos + tokenprog.match(fmt, pos)
                    while pos < len(fmt):
                        if fmt[pos] == '.' and \
                            pos+1 < len(fmt) and fmt[pos+1] in namechars:
                            pos = pos+2
                            pos = pos + tokenprog.match(fmt, pos)
                        elif fmt[pos] in '([':
                            pos, level = pos+1, 1
                            while level:
                                pos = pos + tokenprog.match(fmt, pos)
                                tstart, tend = tokenprog.regs[3]
                                token = fmt[tstart:tend]
                                if token[0] in '([': level = level+1
                                elif token[0] in ')]': level = level-1
                        else: break
                    chunks.append((1, fmt[dollar+1:pos]))

                else:
                    chunks.append((0, fmt[pos:dollar+1]))
                    pos = dollar + 1 + (nextchar == '$')

        except TypeError:       # token regex did not match, regs[] failed
            import traceback
            traceback.print_exc()
            raise ItplError, "unfinished expression"
        except ValueError:      # index did not find a dollar sign
            pass

        if pos < len(fmt): chunks.append((0, fmt[pos:]))
        self.chunks = chunks

    def __repr__(self, prog=None):
        try: 1/0
        except: frame = sys.exc_traceback.tb_frame

        while frame.f_globals['__name__'] == name: frame = frame.f_back
        loc, glob = frame.f_locals, frame.f_globals

        result = []
        for live, chunk in self.chunks:
            if live: result.append(str(eval(chunk, loc, glob)))
            else: result.append(chunk)

        return string.join(result, '')

def printpl(str): print Itpl(str)
def itpl(str): return repr(Itpl(str))

class Itplfile:
    def __init__(self, file): self.file = file
    def __repr__(self): return '<interpolated ' + repr(self.file) + '>'
    def __getattr__(self, attr): return getattr(self.file, attr)
    def write(self, str): self.file.write(repr(Itpl(str)))

def filter(file=sys.stdout): return Itplfile(file)
def unfilter(ifile=None): return ifile and ifile.file or sys.stdout.file

--------------237C2F1C7DE1--



=================
DOC-SIG  - SIG for the Python Documentation Project

send messages to: doc-sig@python.org
administrivia to: doc-sig-request@python.org
=================