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