【问题标题】:Python string.format with inline pipes带有内联管道的 Python string.format
【发布时间】:2016-05-03 18:12:45
【问题描述】:

我正在为用户消息格式化很多字符串。可能看起来像这样:

def sms(**kwargs):
  return "Sorry {name}, but your payment was rejects. Please visit {url} and try again.".format(
    name=kwargs.get('name'),
    url=shorten_url(kwargs.get('url'))
  )

如果我不需要重新格式化任何关键字 args,我可以这样做,这很不错:

def sms(**kwargs):
  return "Sorry {name}, but your payment was rejects. Please visit {url} and try again.".format(**kwargs)

所以我希望也许可以做这样的事情:

def sms(**kwargs):
  return "Sorry {name}, but your payment was rejects. Please visit {url|shorten_url} and try again.".format(**kwargs)

所以我可以使用管道格式化字符串内联。这似乎没什么大不了的,但我正在写很多这样的消息。

我注意到 python string.vformat 函数,但我不确定这是否是我正在寻找的。有什么想法吗?

【问题讨论】:

  • 也许你可以使用像jinja2这样的真正的模板引擎?
  • 是的,我见过的很多 Python 模板系统都有这样的特性。 jinja2 的形式为custom filters
  • 是的,我实际上正在远离 jinja 以支持功能组合和字符串连接。 here's a snippet 我正在编写的一些实际代码——我更喜欢这种方式。
  • * 另见stackoverflow.com/q/35574349/42223 了解另一个子类化 string.Formatter 的示例

标签: python


【解决方案1】:

如果您继承string.Formatter,您实际上可以实现自定义转换函数。以下示例基于this post

import string

class Template(string.Formatter):
    def convert_field(self, value, conversion):
        if conversion == 'u': # has to be a single char
            return value[:3] # replace with your shorten_url function
        # otherwise call the default convert_field method
        return super(Template, self).convert_field(value, conversion)

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

输出SOM

另一种选择是在将 kwargs 传递给 format 之前对其进行修改:

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

编辑:

基于您想使用globals() 的事实,您可以使用类似的东西

def bold(s):
  return "<strong>" + s + "</strong>"

def span(s):
  return "<span>" + s + "</span>"

class Template(string.Formatter):
    def get_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)
        for filter in parts:
            obj = globals()[filter](obj) # call remaining parts as filter functions
        return obj, used_key

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

| 字符似乎与字段名称一起传递,因此您可以(ab)根据需要使用它。我建议添加一些错误处理并检查函数的调用顺序是你所期望的。我也不确定使用globals() 是个好主意,尤其是在您要处理不安全的格式字符串时。

【讨论】:

  • 很高兴看到即使是 python stdlib 也提供自定义格式化程序。
  • 对于更复杂的场景,您还可以覆盖format_field,甚至可以扩展格式语法,并允许转换令牌使用多个字符。
  • 嗯。因为有使用!&lt;letter&gt; 语法的内置“转换”?我不知道。奇怪的是它只支持一个字母和一个转换。我希望能够访问命名空间中的所有功能。我忘记了 python 允许你添加对象方法。我不知道该怎么做,但我可以自己创建string.custrom_format 函数吗?
【解决方案2】:

所以这更符合我的要求:

import re

def bold(string):
  return "<strong>" + string + "</strong>"

def format(string, **kwargs):
  # using the global scope, we can pipe kwargs through functions!
  scope = globals()
  def replace(substr):
    pieces = substr.group()[1:-1].split("|")
    value = kwargs.get(pieces[0])
    if len(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"})

它有效,但整个 globals() 事情让我很担心。如果我在另一个文件的另一个作用域中定义了一个我想使用的函数怎么办?

【讨论】:

    【解决方案3】:

    管道或更好的“过滤器”未在 Python 标准库模板中实现。

    标准 Python 库提供各种格式选项(对齐、填充、数字 格式),但它肯定有一些限制。

    许多模板包确实支持自定义过滤器,其中之一是jinja2

    from jinja2 import Environment
    
    
    def dumb_shorten_url(url):
        # just shortening for fun, implement real shortening
        return 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)
    

    jinja2 提供了更多功能(从文件系统、目录、循环读取的模板, 条件表达式,转义 HTML ...),但上面的示例将演示,它适用于 “管道”。

    【讨论】:

    • 是的,我实际上正在远离神社——我在另一条评论中提到了这一点。谢谢你的建议!
    • @Chet 我很想知道原因,你能提供一些链接到你的评论吗?我们经常使用 jinja2 对此非常满意。
    • @Chet 我已经看到您的评论,其中包含指向 pastebin 的链接 - 但它显示了 Jinja2 代码,并没有说明您放弃它的任何原因。我错过了什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-15
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-06
    • 2012-03-21
    • 2017-03-10
    相关资源
    最近更新 更多