将 Formatter 功能扩展到 f-string 语法

Extend Formatter features to f-string syntax

在我的一个项目中,我将字符串传递给 Formatter subclass,后者使用 format specifier mini-language 对其进行格式化。在我的例子中,它是通过添加额外的 bang 转换器来定制的(使用 Formatter class 的特性):!u 将结果字符串转换为小写,!c 转换为标题,!q 将任何方括号加倍(因为原因),和其他一些。

例如,使用a = "toFu""{a!c}"变为"Tofu"

如何让我的系统使用 f-string 语法 ,这样我就可以将 "{a+a!c}" 变成 "Tofutofu"

注意:我不是在寻求一种方法让 f"{a+a!c}"(注意 f 的存在)将自身解析为 "Tofutofu",这就是 封面,我问是否有一种方法可以让函数或任何形式的 python 代码将 "{a+a!c}"(注意没有 f)变成 "Tofutofu" .

请检查规格 只允许使用这些字符:'s'、'r' 或 'a'

https://peps.python.org/pep-0498/

不确定我是否仍然完全理解您的需求,但是根据问题中给出的详细信息和一些评论,这里有一个函数可以使用您指定的格式解析字符串并给出所需的结果:

import re

def formatter(s):
    def replacement(match):
        expr, frmt = match[1].split('!')
        if frmt == 'c':
            return eval(expr).title()

    return re.sub(r"{([^{]+)}", replacement, s)

a = "toFu"
print(formatter("blah {a!c}"))
print(formatter("{a+a!c}blah"))

输出:

blah Tofu
Tofutofublah

这使用了 re.sub 函数的 repl 参数的函数变体。此函数 (replacement) 可以进一步扩展以支持所有其他 !xs。

主要缺点:

  • 使用eval是邪恶的。
  • 这不包括常规格式说明符,即 :0.3

也许有人可以从这里借鉴并改进。

从@Tomerikoo 的life-saving 回答演变而来,代码如下:

import re

def formatter(s):
    def replacement(match):
        pre, bangs, suf = match.group(1, 2, 3)
        # pre : the part before the first bang
        # bangs : the bang (if any) and the characters going with it
        # suf : the colon (if any) and the characters going with it

        if not bangs:
            return eval("f\"{" + pre + suf + "}\"")

        conversion = set(bangs[1:]) # the first character is always a bang
        sra = conversion - set("tiqulc")
        conversion = conversion - sra
        if sra:
            sra = "!" + "".join(sra)

        value = eval("f\"{" + pre + (sra or "") + suf + "}\"")

        if "q" in conversion:
            value = value.replace("{", "{{")

        if "u" in conversion:
            value = value.upper()

        if "l" in conversion:
            value = value.lower()

        if "c" in conversion and value:
            value = value.capitalize()

        return value

    return re.sub(r"{([^!:\n]+)((?:![^!:\n]+)?)((?::[^!:\n]+)?)}", replacement, s)

庞大的正则表达式导致我在顶部评论的三个组。

注意:它仍然使用 eval(无论如何都没有可接受的方法),它不允许多行替换字段,并且可能会导致问题 and/or 在 [=11= 之间放置空格的差异] 和 :。 但是这些对于我的使用来说是可以接受的。