ChatGPT解决这个技术问题 Extra ChatGPT

Can I import Python's 3.6's formatted string literals (f-strings) into older 3.x, 2.x Python?

The new Python 3.6 f-strings seem like a huge jump in string usability to me, and I would love to jump in and adopt them whole heartedly on new projects which might be running on older interpreters. 2.7, 3.3-3.5 support would be great but at the very least I would like to use these in Python 3.5 code bases. How can I import 3.6's formatted string literals for use by older interpreters?

I understand that formatted string literals like f"Foo is {age} {units} old" are not breaking changes, so would not be included in a from __future__ import ... call. But the change is not back-ported (AFAIK) I would need to be sure that whatever new code I write with f-strings is only ran on Python 3.6+ which is a deal breaker for a lot of projects.


W
Wayne

future-fstrings brings f-strings to Python 2.7 scripts. (And I assume 3.3-3.5 based on the documentation.)

Once you pip install it via pip install future-fstrings, you have to place a special line at the top of your code. That line is:

# -*- coding: future_fstrings -*-

Then you can use formatted string literals (f-strings) within your code:

# -*- coding: future_fstrings -*-
var = 'f-string'
print(f'hello world, this is an {var}')

Unfortunately this breaks emacs: Warning (mule): Invalid coding system future_fstrings' is specified`
If you remove -*- bits then Python will still recognize it.
I just use it to do automatic code conversion for me: after install, future-fstrings-show main.py. Then I copy/paste the code. This works while I was not able to get it work "out of the box" in jupyter (not sure if it is intended to). But then, you can probably DIY if all you want is automatic code conversion.
instead of adding encoding string to the top, you can try adding this right at the start of your py script: python import future_fstrings future_fstrings.register()
M
MSeifert

Unfortunatly if you want to use it you must require Python 3.6+, same with the matrix multiplication operator @ and Python 3.5+ or yield from (Python 3.4+ I think)

These made changes to how the code is interpreted and thus throw SyntaxErrors when imported in older versions. That means you need to put them somewhere where these aren't imported in older Pythons or guarded by an eval or exec (I wouldn't recommend the latter two!).

So yes, you are right, if you want to support multiple python versions you can't use them easily.


Is there any reason not to use the package `future-fstrings' as mentioned in an answer below? I appreciate this option may not have been available when you answered this question in any case.
@Siwel It's a really clever way to backport this. I don't think there are any serious reasons to not use it. However using a custom encoding could probably conflict with some IDEs. I don't think it's opposed to PEP 263 Defining Python Source Code Encodings but it's not really the use-case the "encoding line" was made for. However, it's a very clever approach and I'll certainly test it in the future.
format_map can be a useful workaround if passed the locals() variables dictionary... haven't tested it myself, but it should work
Y
Yoily L

here's what I use:

text = "Foo is {age} {units} old".format(**locals())

it unpacks (**) the dict returned by locals() which has all your local variables as a dict {variable_name: value}

Note this will not work for variables declared in an outer scope, unless you import it to the local scope with nonlocal (Python 3.0+).

you can also use

text.format(**locals(),**globals())

to include global variables in your string.


This works but this kind of "hacks" should probably be avoided, in my humble opinion!
this is risky if the string is provided by an external source
U
Uriel

The f-strings are created by the interpreter upon tokening the f prefix - that feature alone will kill any compatibility chances.

Your closest shot is to use the keyword formatting, like

'Foo is {age} {units} old'.format(age=age, units=units)

which can be more easily refactored upon the termination of requirement for compatibility.


If using this method, and assuming age and units are already variables, it would probably make more sense to write this as 'Foo is {age} {units} old'.format(age=age, units=units) for python2.7 so it can be quickly be updated to f'Foo is {age} {units} old' when moving to python3.6
What about 'Foo is {age} {units} old'.format(**locals(), **globals())?
@MadPhysicist , works but considered bad form, due to including the kitchen sink in a format call.
@GringoSuave. Agreed, but that's pretty close to how an actual f-string is evaluated.
@MadPhysicist, not really, was an explicit design goal not to do that. The string is parsed at compile time into string and expression parts, and the expression parts are normal Py code. So only the variables named are referenced, not entire namespaces. Somewhat theoretical, but distinct.
J
Jarry Shaw

I just wrote a back-port compiler for f-string, called f2format. Just as you requests, you may write f-string literals in Python 3.6 flavour, and compile to a compatible version for end-users to run, just like Babel for JavaScript.

f2format provides an intelligent, yet imperfect, solution of a back-port compiler. It shall replace f-string literals with str.format methods, whilst maintaining the original layout of source code. You can simply use

f2format /path/to/the/file_or_directory

which will rewrite all Python files in place. For instance,

var = f'foo{(1+2)*3:>5}bar{"a", "b"!r}boo'

will be converted to

var = ('foo{:>5}bar{!r}boo').format(((1+2)*3), ("a", "b"))

String concatenation, conversion, format specification, multi-lines and unicodes are all treated right. Also, f2format will archive original files in case there're any syntax breaches.


I hope it's open source because i'm not letting a random program run over my source code :(
@Walter yes, it is open source under Apache License 2.0; just check out the repo :)
R
Romuald Brunet

I've been using 'str'.format(**locals()) for a while but made this after a while because the additional code was a bit cumbersome for each statement

def f(string):
    """
    Poor man's f-string for older python versions
    """
    import inspect

    frame = inspect.currentframe().f_back

    v = dict(**frame.f_globals)
    v.update(**frame.f_locals)

    return string.format(string, **v)

# Example
GLOBAL = 123


def main():
    foo = 'foo'
    bar = 'bar'

    print(f('{foo} != {bar} - global is {GLOBAL}'))


if __name__ == '__main__':
    main()

s
shouldsee

A dirty solution using simpleeval

import re
import simpleeval
test='_someString'
lst = ['_456']

s = '123123{lst[0]}{test}'

def template__format(template, context=None):
    if context is None:
        frame = inspect.currentframe()
        context = frame.f_back.f_locals        
        del frame
    ptn =  '([^{]?){([^}]+)}'
    class counter():
        i = -1

    def count(m):
        counter.i += 1
        return m.expand('\\1{%d}'%counter.i)

    template = re.sub(ptn,string=s, repl= count)
    exprs = [x[1] for x in re.findall(ptn,s)]
    vals = map(simpleeval.SimpleEval(names=context).eval,exprs)
    res = template.format(*vals)
    return res

print (template__format(s))


d
dreftymac

Using dict() to hold name-value pairs

In addition to the approaches mentioned elsewhere in this thread (such as format(**locals()) ) the developer can create one or more python dictionaries to hold name-value pairs.

This is an obvious approach to any experienced python developer, but few discussions enumerate this option expressly, perhaps because it is such an obvious approach.

This approach is arguably advantageous relative to indiscriminate use of locals() specifically because it is less indiscriminate. It expressly uses one or more dictionaries a namespace to use with your formatted string.

Python 3 also permits unpacking multiple dictionaries (e.g., .format(**dict1,**dict2,**dict3) ... which does not work in python 2.7)

## init dict
  ddvars = dict()

  ## assign fixed values
  ddvars['firname']   =   'Huomer'
  ddvars['lasname']   =   'Huimpson'
  ddvars['age']       =   33
  pass

  ## assign computed values
  ddvars['comname']   =   '{firname} {lasname}'.format(**ddvars)
  ddvars['reprself']  =   repr(ddvars)
  ddvars['nextage']   =   ddvars['age'] + 1
  pass

  ## create and show a sample message
  mymessage = '''
  Hello {firname} {lasname}!
  Today you are {age} years old.
  On your next birthday you will be {nextage} years old!
  '''.format(**ddvars)

  print(mymessage)