trey
(Trey Hunner)
1
I have been playing with t-strings and I keep finding myself copy-pasting the convert function from the PEP:
def convert(value: object, conversion: Literal["a", "r", "s"] | None) -> object:
if conversion == "a":
return ascii(value)
elif conversion == "r":
return repr(value)
elif conversion == "s":
return str(value)
return value
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.
@dkp
12 Likes
AA-Turner
(Adam Turner)
2
I support this idea, I would also suggest asking the RM to add this to 3.14, if accepted.
A
6 Likes
We might also consider implementing it as a staticmethod of Interpolation.
1 Like
blhsing
(Ben Hsing)
4
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).
trey
(Trey Hunner)
5
This was for this library I made today.
The dedent function in that library uses a t-string in a very similar way to an f-string, but delaying interpolation is important for its purpose.
4 Likes
blhsing
(Ben Hsing)
6
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.
1 Like
dkp
(Dave Peck)
7
I’d like to see this too.
Would we prefer it:
- As a function directly in
templatelib or
- As a staticmethod on
Interpolation?
Would our preference if shipped be for it to:
- 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.
1 Like
AA-Turner
(Adam Turner)
8
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.
A
1 Like
pR0Ps
(Carey Metcalfe)
9
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
)
Also of note is that the string.Formatter.convert_field method doesn’t actually use the bound self arg so it’s possible to implement your function like:
import functools
from string import Formatter
convert = functools.partial(Formatter.convert_value, None)
# or, more verbosely:
def convert(value: object, conversion) -> object:
return Formatter.convert_value(None, value, conversion)
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.
3 Likes
AA-Turner
(Adam Turner)
10
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.
A
trey
(Trey Hunner)
11
I hadn’t known about Formatter.convert_field.
I found that I needed to use either:
from functools import partial
convert = partial(Formatter.convert_field, Formatter)
Or (just classmethod didn’t work since that provided the descriptor but not the bound method):
from string import Formatter
convert = classmethod(Formatter.convert_field).__get__(Formatter)
gerardw
(Gerardwx)
12
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)
yoavrv
(Yoav Ravid)
13
As someone newer to python, I feel there should be an f-string syntax (as it’s the most obvious way to format strings in modern python).
I hoped the inner interpolation method from :format_spec would work but it doesn’t seem to work for the !conversion part.
I.e. This is a somewhat common idiom
formatted_value = f'{value:{format_spec}}'
But this doesn’t work
formatted_value = f'{value!{conversion}:{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)