Delayed evaluation of f-strings?
In a recent discussion with a colleague we wondered if it would be possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’. I found https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu... and tweaked it a bit to: import inspect class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars) delayed_fstring = DelayedFString("The current name is {name}") # use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(delayed_fstring) new_scope() While this does what it should it is very slow. So I wondered whether it would be an idea to introduce d-strings (delayed f-strings) and make f-strings syntactic sugar for f"The current name is {name}" = str(d"The current name is {name}") And perhaps access to the variables and conversions specified in the d-string.
for your usecase I'd write: def delayed_fstring(*, name: str) -> str: return "The current name is {name}" def new_scope() -> None: for name in ["foo", "bar"]: print(delayed_fstring(name=name)) for logging I use: class Msg: def __init__(self, fn: Callable[[], str]): self._fn = fn def __str__(self) -> str: return self._fn() ... logger.info(Msg(lambda: f"The current name is {name}"))
On Thu, 24 Jun 2021 at 17:25, Eric Nieuwland <eric.nieuwland@gmail.com> wrote:
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
This isn't quite right as the semantics between f-strings and str.format() are not actually the same (though this isn't well documented): >>> f'{1 + 2}' '3' >>> str(DelayedFString('{1 + 2}')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __str__ KeyError: '1 + 2' >>> d = dict(a=1) >>> f'{d["a"]}' '1' >>> str(DelayedFString('{d["a"]}')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __str__ KeyError: '"a"' Basically, f-strings rely on eval-like semantics. Martin
On Thu, 24 Jun 2021 at 17:37, Martin (gzlist) <gzlist@googlemail.com> wrote:
>>> d = dict(a=1) >>> f'{d["a"]}' '1' >>> str(DelayedFString('{d["a"]}')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __str__ KeyError: '"a"'
And the other side of the attribute lookup: >>> d = dict(a=1) >>> str(DelayedFString('{d[a]}')) '1' >>> f'{d[a]}' Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined Yes, having three different ways of doing string interpolation (not counting other things you can import, like string.Template) is a bit confusing. Martin
I didn’t make myself clear, sorry. The code example was just to give an initial what I was suggesting. The errors you show demonstrate the example is incomplete. I’d like them to work.
On 24 Jun 2021, at 18:37, Martin (gzlist) <gzlist@googlemail.com> wrote:
On Thu, 24 Jun 2021 at 17:25, Eric Nieuwland <eric.nieuwland@gmail.com> wrote:
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
This isn't quite right as the semantics between f-strings and str.format() are not actually the same (though this isn't well documented):
f'{1 + 2}' '3' str(DelayedFString('{1 + 2}')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __str__ KeyError: '1 + 2'
d = dict(a=1) f'{d["a"]}' '1' str(DelayedFString('{d["a"]}')) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __str__ KeyError: '"a"'
Basically, f-strings rely on eval-like semantics.
Martin
What part are you trying to delay, the expression evaluations, or the string building part? There was a recent discussion on python-ideas starting at https://mail.python.org/archives/list/python-ideas@python.org/message/LYAC7J... that might interest you. Eric On 6/24/2021 5:37 AM, Eric Nieuwland wrote:
In a recent discussion with a colleague we wondered if it would be possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’.
I found https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu... <https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu...> and tweaked it a bit to:
import inspect
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
delayed_fstring = DelayedFString("The current name is {name}")
# use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(delayed_fstring)
new_scope()
While this does what it should it is very slow. So I wondered whether it would be an idea to introduce d-strings (delayed f-strings) and make f-strings syntactic sugar for
f"The current name is {name}" = str(d"The current name is {name}")
And perhaps access to the variables and conversions specified in the d-string.
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/GT5DNA7R... Code of Conduct: http://python.org/psf/codeofconduct/
-- Eric V. Smith
I don't think that would be a good idea since we already have .format() which covers that use case and is more flexible than f-strings (it supports positional arguments, as well as *args and **kwargs). I think keeping f-strings simple is a better idea. Best, Luciano On Thu, Jun 24, 2021 at 1:30 PM Eric Nieuwland <eric.nieuwland@gmail.com> wrote:
In a recent discussion with a colleague we wondered if it would be possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’.
I found https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu... and tweaked it a bit to:
import inspect
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
delayed_fstring = DelayedFString("The current name is {name}")
# use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(delayed_fstring)
new_scope()
While this does what it should it is very slow. So I wondered whether it would be an idea to introduce d-strings (delayed f-strings) and make f-strings syntactic sugar for
f"The current name is {name}" = str(d"The current name is {name}")
And perhaps access to the variables and conversions specified in the d-string.
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/GT5DNA7R... Code of Conduct: http://python.org/psf/codeofconduct/
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg
Except I like the mini-language of f-strings much better than format()’s. And there is a performance difference between f-strings and format().
On 24 Jun 2021, at 19:03, Luciano Ramalho <luciano@ramalho.org> wrote:
I don't think that would be a good idea since we already have .format() which covers that use case and is more flexible than f-strings (it supports positional arguments, as well as *args and **kwargs).
I think keeping f-strings simple is a better idea.
Best,
Luciano
On Thu, Jun 24, 2021 at 1:30 PM Eric Nieuwland <eric.nieuwland@gmail.com> wrote:
In a recent discussion with a colleague we wondered if it would be possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’.
I found https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu... and tweaked it a bit to:
import inspect
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
delayed_fstring = DelayedFString("The current name is {name}")
# use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(delayed_fstring)
new_scope()
While this does what it should it is very slow. So I wondered whether it would be an idea to introduce d-strings (delayed f-strings) and make f-strings syntactic sugar for
f"The current name is {name}" = str(d"The current name is {name}")
And perhaps access to the variables and conversions specified in the d-string.
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/GT5DNA7R... Code of Conduct: http://python.org/psf/codeofconduct/
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg
As pointed out already, f-strings and format are subtly different (not counting that one can eval and the other cannot). Besides quoting, the f-sting mini language has diverged from format's
spam="Spam" f"{spam=}" "spam='Spam'" "{spam=}".format(spam=spam) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'spam='
I created a package some time ago to do exactly this https://pypi.org/project/f-yeah/ On Thu, Jun 24, 2021 at 10:07 AM Luciano Ramalho <luciano@ramalho.org> wrote:
I don't think that would be a good idea since we already have .format() which covers that use case and is more flexible than f-strings (it supports positional arguments, as well as *args and **kwargs).
I think keeping f-strings simple is a better idea.
Best,
Luciano
On Thu, Jun 24, 2021 at 1:30 PM Eric Nieuwland <eric.nieuwland@gmail.com> wrote:
In a recent discussion with a colleague we wondered if it would be
possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’.
I found
https://stackoverflow.com/questions/42497625/how-to-postpone-defer-the-evalu... and tweaked it a bit to:
import inspect
class DelayedFString(str): def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.format(**vars)
delayed_fstring = DelayedFString("The current name is {name}")
# use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(delayed_fstring)
new_scope()
While this does what it should it is very slow. So I wondered whether it would be an idea to introduce d-strings
(delayed f-strings) and make f-strings syntactic sugar for
f"The current name is {name}" = str(d"The current name is {name}")
And perhaps access to the variables and conversions specified in the
d-string.
_______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/GT5DNA7R...
Code of Conduct: http://python.org/psf/codeofconduct/
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/LVPDNGUR... Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Jun 24, 2021 at 10:34 AM micro codery <ucodery@gmail.com> wrote:
As pointed out already, f-strings and format are subtly different (not counting that one can eval and the other cannot). Besides quoting, the f-sting mini language has diverged from format's
spam="Spam" f"{spam=}" "spam='Spam'" "{spam=}".format(spam=spam) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'spam='
Eric, what would you think of adding this feature to format()? It seems doable (at least for keyword args -- for positional args I don't think it makes sense). Honestly, the rest of the discussion belongs on python-ideas. -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
On 6/24/2021 11:28 PM, Guido van Rossum wrote:
On Thu, Jun 24, 2021 at 10:34 AM micro codery <ucodery@gmail.com <mailto:ucodery@gmail.com>> wrote:
As pointed out already, f-strings and format are subtly different (not counting that one can eval and the other cannot). Besides quoting, the f-sting mini language has diverged from format's >>> spam="Spam" >>> f"{spam=}" "spam='Spam'" >>> "{spam=}".format(spam=spam) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'spam='
Eric, what would you think of adding this feature to format()? It seems doable (at least for keyword args -- for positional args I don't think it makes sense).
The only problem is that it already has a meaning, as unlikely as it is that someone would use it:
d = {'x = ': 42} "{x = }".format(**d) '42'
Plus there's the issue of whitespace being treated differently with f-strings and str.format().
Honestly, the rest of the discussion belongs on python-ideas.
I totally agree. Eric
24.06.21 12:37, Eric Nieuwland пише:
In a recent discussion with a colleague we wondered if it would be possible to postpone the evaluation of an f-string so we could use it like a regular string and .format() or ‘%’.
You can just use lambda: delayed_fstring = lambda: f"The current name is {name}" print(delayed_fstring()) If you want to get rid of () when convert to string, you can use a wrapper: class LazyStr: def __init__(self, str_func): self._str_func = str_func def __str__(self): return self._str_func() delayed_fstring = LazyStr(lambda: f"The current name is {name}") print(delayed_fstring) but it works only if str() is directly or indirectly called for the value. You cannot pass it open() as a file name, because open() expects only str, bytes, integer of path-like object. Most builtin functions will not and could not work with LazyStr. As for evaluating variables in different scope as in your example, it just does not work in Python. Python uses static binding, not dynamic binding. First than introducing "delayed f-strings" you need to introduce dynamic binding in normal functions.
participants (8)
-
Eric Nieuwland
-
Eric V. Smith
-
Guido van Rossum
-
Luciano Ramalho
-
Martin (gzlist)
-
micro codery
-
Serhiy Storchaka
-
Thomas Grainger