1

In a project of mine, I'm passing strings to a Formatter subclass whic formats it using the format specifier mini-language. In my case it is customized (using the features of the Formatter class) by adding additional bang converters : !u converts the resulting string to lowercase, !c to titlecase, !q doubles any square bracket (because reasons), and some others.

For example, using a = "toFu", "{a!c}" becomes "Tofu"

How could I make my system use f-string syntax, so I can have "{a+a!c}" be turned into "Tofutofu" ?

NB: I'm not asking for a way of making f"{a+a!c}" (note the presence of an f) resolve itself as "Tofutofu", which is what hook into the builtin python f-string format machinery covers, I'm asking if there is a way for a function or any form of python code to turn "{a+a!c}" (note the absence of an f) into "Tofutofu".

Gouvernathor
  • 92
  • 1
  • 12
  • 1
    Does this answer your question? [hook into the builtin python f-string format machinery](https://stackoverflow.com/questions/55876683/hook-into-the-builtin-python-f-string-format-machinery). See also: https://stackoverflow.com/questions/47081521/can-you-overload-the-python-3-6-f-strings-operator – Stuart May 31 '22 at 11:49
  • You can't add your own `!`-magic, but you can control how an _object_ is formatted with some modifiers after `:`, like `x = 42069; print(f"{x:_}")`. See [this article by nedbat](https://nedbatchelder.com/blog/202204/python_custom_formatting.html) – decorator-factory May 31 '22 at 11:52
  • I reopened your question because I feel I'm not expert enough to judge in this field. But I have to say your last edit confused me even more: you want to change the way Python parses regular strings? i.e. to enforce format specifiers on simple string literals, without `f`? – Tomerikoo May 31 '22 at 15:21
  • @Tomerikoo I'm asking how it would be possible for a **function** (or for some sort of python code, as I said) to take the string with brackets, and return the one with titlecased tofu. – Gouvernathor May 31 '22 at 15:25
  • Could be done using regex I guess... Will the `!x` always be at the end of the `{...}`? – Tomerikoo May 31 '22 at 15:31
  • @Tomerikoo I'm not sure, I think there could be spaces after it. But even that aside, what would you seek with the regex: the `!x`, or the value to interpolate ? If the former, it's probably not that hard to do, but what do I do next ? If the latter, how would a regex capture the insane complexity of, for example, the `=` which can be included in f-string interpolations (even more in a forward-compatible way) ? – Gouvernathor May 31 '22 at 15:45
  • 1
    Yep this sounds like a complex and interesting problem, maybe far more than I can still grasp it. Anyway, this is why I reopened it - hopefully someone can better help, and I posted an answer with what I could understand. Who knows, might even be a starting ground for someone to get to the answer you need... Good luck! – Tomerikoo May 31 '22 at 15:53

3 Answers3

1

Not sure I still fully understand what you need, but from the details given in the question and some comments, here is a function that parses strings with the format you specified and gives the desired results:

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"))

Outputs:

blah Tofu
Tofutofublah

This uses the function variation of the repl argument of the re.sub function. This function (replacement) can be further extended to support all other !xs.

Main disadvantages:

  • Using eval is evil.
  • This doesn't take in count regular format specifiers, i.e. :0.3

Maybe someone can take it from here and improve.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
  • Thanks a lot! I'll take it from here, I think I can make it work. I'll post here if I end up with something workable. – Gouvernathor Jun 01 '22 at 00:05
0

Evolved from @Tomerikoo 's life-saving answer, here's the code:

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)

The massive regex results in the three groups I commented about at the top.

Caveat: it still uses eval (no acceptable way around it anyway), it doesn't allow for multiline replacement fields, and it may cause issues and/or discrepancies to put spaces between the ! and the :. But these are acceptable for the use I have.

Gouvernathor
  • 92
  • 1
  • 12
  • 1
    I'm so happy to see my silly code helped you get to a solution in the end ^_^ – Tomerikoo Jun 01 '22 at 09:04
  • Regarding your last issue, I'm might not fully understand (again...) but I think if you just add a `\s*` between the `!` part and the `:` part it will be solved. i.e. something like `{a!c :s}` would work (maybe the `+` of the `!` part needs to be lazy - `+?`) – Tomerikoo Jun 01 '22 at 09:08
  • @Tomerikoo maybe add the `\s*` rather between the `?:` of the last group and the `:` just after it ? Or inside the last capturing group, but outside of the last non-capturing group ? That way it's captured in the last group, which I'm just passing through unchanged... I'll check that tonight if I keep it in mind. – Gouvernathor Jun 01 '22 at 11:44
-2

Please check specifcation only those characters are allowed : 's', 'r', or 'a'

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

  • 1
    The OP specifically said they are adding their own customized converters. The question is how (or if possible) to add them to the f-strings syntax – Tomerikoo May 31 '22 at 12:12