using generators with format strings

Peter Otten __peter__ at web.de
Thu Jul 22 14:44:11 CEST 2004


Hans Nowak wrote:

> def format_with_generator(formatstr, gen):
>      arguments = []
>      while 1:
>          try:
>              s = formatstr % tuple(arguments)
>          except TypeError, e:
>              if e.args[0] == 'not enough arguments for format string':
>                  item = gen.next()
>                  arguments.append(item)
>              else:
>                  raise
>          else:
>              return s

Nice. You led me to dump the regexp approach...

Here's a variant that avoids the guessing for all but the first occurence of
a format and tries to get the tuple length right on the first pass of the
while loop:

<code>
def firstN(seq, n):
    next = iter(seq).next
    return [next() for _ in xrange(n)]

def _provoke_error_message():
    try:
        "%s" % ()
    except TypeError, e:
        return e.args[0]
    else:
        raise Exception("cannot cope with severe change "
            "in format string behaviour")

class format(object):
    """ Formatter that accepts a sequence instead of a tuple
        and ignores superfluous items in that sequence.
        XXX StopIteration should be morphed into ValueError.
        XXX No test suite. Use at your own risk.
    """
    _message = _provoke_error_message()
    _cache = {}
    def __new__(cls, fmt):
        cache = cls._cache

        if fmt in cache:
            return cache[fmt]
        else:
            cache[fmt] = obj = object.__new__(cls)
            obj._cnt = None
            obj._fmt = fmt
            return obj

    def __mod__(self, seq):
        if self._cnt is not None:
            if __debug__: print "we know it (%s)" % self._cnt # remove
            return self._fmt % tuple(firstN(seq, self._cnt))
        return self._guess(seq)

    def _guess(self, seq):
        if __debug__: print "guessing (%r)" % self._fmt # remove
        it = iter(seq)
        fmt = self._fmt
        cnt = fmt.count("%") - fmt.count("%%")
        args = tuple(firstN(it, cnt))

        while 1:
            try:
                result = fmt % args
            except TypeError, e:
                if e.args[0] == self._message:
                    args += (it.next(),)
                    cnt += 1
                    if __debug__: print "one more (%s)" % cnt # remove
                else:
                    raise
            else:
                self._cnt = cnt
                return result

if __name__ == "__main__":
    print format("%s") % (1,2,3)
    print format("%s %-*s %*.*f%% %*f %0*d") % ((1,2,3) + (5,)*100)
    print format("%s %-*s %*.*f%% %*f %0*d") % ((1,2,3) + (5,)*100)
    print format("%s") % ("so what",)
</code>






More information about the Python-list mailing list