The built-in format function makes it easy to process the format specification for a replacement field, but there’s no built-in convert function for handling !a, !r, and !s conversions.
It seems like this function might be useful to have implemented within string.templatelib, so it won’t need to be re-implemented by all t-string parsing utilities.
Just curious though… It sounds like you’re trying to emulate an f-string with a t-string using format and convert, so then for what purpose are you not using an f-string directly?
I do kind of support the idea because it would be useful if/when the logging library supports t-strings as repr is useful primarily for debugging, but just want to ask for clarification of what the OP sees as use cases because I personally don’t quite see many other scenarios where a template library wouldn’t want to always call str on the interpolation’s value if stringification is needed so I doubt that the claim that convert “needs to be re-implemented by all t-string parsing utilities” is remotely true (for example, the talks about adding t-string support for subprocess and DB-API don’t include a mention of the standard convert logics).
Right, the primary use case then is for a library to emulate f-strings while delaying the evaluation of the interpolation’s stringification. Fair enough.
Accept a conversion of None as in the code above, or
Only accept Literal[“a”, “r”, “s”] and always return a str?
Related: in past discussions there was talk of also adding an Interpolation.format_value(…) convenience method that invokes both convert()and the format() builtin in one go. It might make sense to do both at the same time.
I think it should be a module-level function, we could see DSLs that use the conversion flag for entirely different things than formatted string literal-like behaviour if we relax the restriction on conversion flag values in the future. I think it should accept None, though, because that’s the default for an omitted conversion flag.
Unless I’m missing something, can’t the existing string.Formatter.convert_field already be used for this?
As an example, I’ve implemented a function to format a t-string like an f-string like so:
from string import Formatter
from string.templatelib import Template, Interpolation
_FORMATTER = Formatter()
def tstring_as_fstring(tstring):
return "".join(
(
_FORMATTER.format_field(
_FORMATTER.convert_field(x.value, x.conversion),
x.format_spec
)
) if isinstance(x, Interpolation)
else x
for x in tstring
)
Though it’s probably less likely to break if it’s called “properly” via an actual Formatter instance like the first example vs. treating it like a static method.
True, we could suggest that people alias convert = classmethod(string.Formatter.convert_field). My slight argument for putting it in string.templatelib is discoverability and semantics, though.
I also cribbed the PEP code, so having an Interpolation method makes sense to me.
from string.templatelib import Interpolation
from typing import Literal
# dervied from https://raw.githubusercontent.com/davepeck/pep750-examples/refs/heads/main/pep/fstring.py
def iformat(interp: Interpolation) -> str:
"""Format an interpolation like an f-string: apply conversion then format specifier."""
value = interp.value
conv = interp.conversion
# apply conversion (ascii, repr, str) if specified
if conv == "a":
value = ascii(value)
elif conv == "r":
value = repr(value)
elif conv == "s":
value = str(value)
# None or other means leave value as is
# apply the format specifier
return format(value, interp.format_spec)
Personally I would prefer some kind of f-string version to a stand-alone function on nice-syntax and discoverability grounds, but I guess this would require changes to f-strings which is a whole PEP at least.
(Also this makes me think that maybe the Interpolation fields should default to "" rather than None but that ship has sailed)