True, but they do accept function names, which can be evaluated later. Example from tstring-util · PyPI
from tstring import render
def hello(name):
print(f"hello {name}")
def test_lazy():
who = 'bob'
flavor = 'spicy'
embedx = t'Call function {hello:!fn} {who} {flavor}'
who = 'jane'
r = render(embedx)
assert r == "Call function hello jane spicy"
The key concept is a “t-string” is not so much a string as a complex object designed for an input into a library to do whatever it wants with it.
The stdlib logging library could define format specifiers to do interesting things.
Beyond the “!fn” I used for tellling render to evaluate the proceeding function name, logging could do clever things like conditionally format based on logging level.
basic = "hello"
complex = "yada yada"
msg = t"Message with {basic:info} {"and:debug"} {complex:debug}
logger.info(msg) -> Message with hello
loggin\g.deb(msg) -> Message with hello and yada yada
I’m not saying this is necessarily a good idea, merely that there are a lot of opportunities due to the open-ended nature of the format specifier.
Well the workaround only works if t-strings are supported in the first place so we should still be supportive of the pursuit of this idea unless something better comes around.
It’s baked into the render implementation. The !fn tells render to treat the preceding symbol as a function, inspect the function to see how many parameters it needs, and then consume the next N interpolations.
In hindsight, the example was poor, so I’ve updated it to:
from tstring import render
def double(value):
print(f"twice {value} is {2*value}")
def test_lazy():
number = 1
flavor = 'spicy'
embedx = t'Call function {double:!fn} {number} {flavor}'
number = 2
r = render(embedx)
assert r == "Call function twice 2 is 4 spicy"
print(r)
Python logging with template strings with support for conditional (lazy) execution of functions in log messages.
Provides TStringLogger(logging.Logger) and sets it as the default logger class upon import tstring_logger
A Template String may embed function calls with a !fn format specifier. They will only be executed if the logger is active.
Example
import logging
import tstring_logger
def expensive_function(x):
time.sleep(1)
return 2 * x
logging.basicConfig()
lg = logging.getLogger("demo")
hour = datetime.datetime.now().hour
print(1)
lg.debug(test := t"The thing happened with {expensive_function:!fn} {7} at hour {hour}.")
print(2)
lg.setLevel(logging.DEBUG)
lg.debug(test)
print(3)
will quickly print 1 and 2, then pause a second before 3 appears after DEBUG:demo:The thing happened with 14 at hour 16.
Since expensive_function takes a single argument, the 7 is passed to the function while hour renders in the usual way.
The logger uses the embed function of tstring-util.
Late and optional string conversion and concatenation is EXACTLY what the usage of % templates achieve when logging. It would be just equivalent, but much more convenient to use
But { - str.format like templates would still be less convenient than f-strings. t-strings are on convenience parity: no typing names twice, no positional argument counting