Skip to content Skip to sidebar Skip to footer

Python String.format With Inline Pipes

I'm formatting a lot of strings for user messages. One might look like this: def sms(**kwargs): return 'Sorry {name}, but your payment was rejects. Please visit {url} and try aga

Solution 1:

You can actually implement custom conversion functions if you subclass string.Formatter. Following example is based on this post

import string

classTemplate(string.Formatter):
    defconvert_field(self, value, conversion):
        if conversion == 'u': # has to be a single charreturn value[:3] # replace with your shorten_url function# otherwise call the default convert_field methodreturnsuper(Template, self).convert_field(value, conversion)

print(Template().format('{url!u}', url='SOME LONG URL'))

Outputs SOM

Another option is to just modify kwargs before you pass it to format:

>>>defsms(**kwargs):...    kwargs['shorturl'] = shorten_url(kwargs['url'])...print('test {shorturl}'.format(**kwargs))

Edit:

Based on the fact that you want to use globals(), you could use something like

defbold(s):
  return"<strong>" + s + "</strong>"defspan(s):
  return"<span>" + s + "</span>"classTemplate(string.Formatter):
    defget_field(self, name, args, kwargs):
        parts = name.split('|')
        # use first part as actual field name ('url' in this case)
        obj, used_key = super(Template, self).get_field(parts.pop(0), args, kwargs)
        forfilterin parts:
            obj = globals()[filter](obj) # call remaining parts as filter functionsreturn obj, used_key

print(Template().format('{url|bold|span}', url='SOME LONG URL'))
# Outputs: <span><strong>SOME LONG URL</strong></span>

The | char seems to be passed through with the field name, so you can (ab)use this as required. I would recommend adding some error handling and checking the call order on the functions is what you expect. I'm also not sure that using globals() is a great idea, especially if you're going to be processing unsafe format strings.

Solution 2:

Pipes, or better "filters", are not implemented in Python stdlib templating.

Standard Python libraries offer various formatting options (justification, padding, number formatting), but it has certainly some limits.

Many templating packages do support custom filters, one of them being jinja2:

from jinja2 import Environment


def dumb_shorten_url(url):
    # just shortening forfun, implement real shorteningreturn url[6:]

env = Environment()
env.filters["shorten_url"] = dumb_shorten_url


templ = env.from_string("Sorry {{name}}, but your payment was rejects. ""Please visit {{url|shorten_url}} and try again.")

kwargs = {"name": "James", "url": "http://acme.com/one/two"}

print templ.render(**kwargs)

There is much more what jinja2 offers (templates read from file system, from directories, loops, conditional expressions, escaping HTML...), but the example above shall demonstrate, it works with "pipes".

Solution 3:

So this is more along the line of what I was looking for:

import re

defbold(string):
  return"<strong>" + string + "</strong>"defformat(string, **kwargs):
  # using the global scope, we can pipe kwargs through functions!
  scope = globals()
  defreplace(substr):
    pieces = substr.group()[1:-1].split("|")
    value = kwargs.get(pieces[0])
    iflen(pieces) > 1:
      pipes = pieces[1:]
      for pipe in pipes:
        value = scope[pipe](value)
    return value
  return re.sub(r"\{\S+\}", replace, string)

format("Hello {name|bold}, {yo}", **{"name":"Joe Schmo", "yo":"gimme more"})

It works, but the whole globals() thing concerns me. What if I define a function in another scope in another file that I want to use?

Post a Comment for "Python String.format With Inline Pipes"