string formatting with mapping & '*'... is this a bug?

Peter Otten __peter__ at web.de
Sat Sep 11 06:16:27 EDT 2004


I've written a class that automatically generates a format string ready for
dual application of '%'. Instead of 

>>> d = dict(val=1.234, width=10, prec=2)
>>> "%%(val)%(width)d.%(prec)df" % d % d
'      1.23'

you can now write

>>> Format("%(val)*(width).*(prec)f") % d
'      1.23'

which is slightly more readable. Not tested beyond what you see.

Peter

import re

class Format:
    """
    Extends the format string to allow dict substitution for width and
    precision.

    >>> Format("%(value)*(width)s") % dict(value=1.234, width=10)
    '     1.234'

    >>> Format("%(value)*(width).*(prec)f") % dict(value=1.234, width=-10,
prec=2)
    '1.23      '

    """
    _cache = {}
    # Generously allow all ascii characters as format specifiers :-)
    rOuter = re.compile(r"(%(\(.*?\)|[^A-Za-z%])*[A-Za-z%])")
    rInner = re.compile(r"\*\(.*?\)")

    def __init__(self, format):
        self.format = self.prepare(format)

    def subInner(self, match):
        # called for every width/prec specifier, e. g. "*(width)"
        s = match.group(0)
        return "%" + s[1:] + "s"

    def subOuter(self, match):
        # called for every complete format, e. g. "%(value)*(width)s"
        s = match.group(0)
        if s == "%%":
            return "%%%%"
        return "%" + self.rInner.sub(self.subInner, s)

    def prepare(self, format):
        """ Modify the format for a two-pass 'format % dict % dict'
            appliction. The first pass replaces width/prec specifiers
            with integer literals
        """
        cached = self._cache.get(format)
        if cached is not None:
            return cached
        result = self._cache[format] = self.rOuter.sub(self.subOuter,
format)
        return result

    def __mod__(self, dict):
        return self.format % dict % dict

if __name__ == "__main__":
    f = Format("%(value)*(width).*(prec)f (literal) "
            "%(string)s [%(integer)3d] %% [%(integer)-*(width)d]")
    print f % dict(value=1.2345, width=5, prec=2, string="so what",
        integer=11)


    # Abusing your code as a test case...
    fmt = { 'wDate':10, 'wOpen':6, 'wHigh':6, 'wLow':6,  # width
            'wClose':6, 'wVolume':10, 'wAdjClose':6,
            'pDate':10, 'pOpen':2, 'pHigh':2, 'pLow':2,  # precision
            'pClose':2, 'pVolume':0, 'pAdjClose':2 }

    # data will be read from several thousand files
    sampledata = [
        "9-Sep-04,19.49,20.03,19.35,19.93,60077400,19.93",
        "8-Sep-04,18.96,19.53,18.92,18.97,52020600,18.96",
        "7-Sep-04,18.98,19.18,18.84,18.85,45498100,18.84",
        ]

    change=["down","up","n/c"]

    for D in sampledata:
        Date, Open, High, Low, Close, Volume, AdjClose = D.split(',')

        map = dict(Date=Date,
                Open=float(Open),
                High=float(High),
                Low=float(Low),
                Close=float(Close),
                Volume=int(Volume),
                AdjClose=float(AdjClose),
                #
                Change=change[int(float(AdjClose) >= float(Open)) +
                                int(float(AdjClose) == float(Open))]
                )
        map.update(fmt)


        new = Format(
            "%(Date)*(wDate).*(pDate)s "
            "%(Open)*(wOpen).*(pOpen)f "
            "%(High)*(wHigh).*(pHigh)f "
            "%(Low)*(wLow).*(pLow)f "
            "%(Close)*(wClose).*(pClose)f "
            "%(Volume)*(wVolume).*(pVolume)d "
            "%(AdjClose)*(wAdjClose).*(pAdjClose)f "
            "%(Change)s") % map

        old = (
            "%%(Date)%(wDate)d.%(pDate)ds "
            "%%(Open)%(wOpen)d.%(pOpen)df "
            "%%(High)%(wHigh)d.%(pHigh)df "
            "%%(Low)%(wLow)d.%(pLow)df "
            "%%(Close)%(wClose)d.%(pClose)df "
            "%%(Volume)%(wVolume)d.%(pVolume)dd "
            "%%(AdjClose)%(wAdjClose)d.%(pAdjClose)df "
            "%%(Change)s") % fmt % map

        assert old == new




More information about the Python-list mailing list