Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
6797767
WIP: Mostly works, except repr isn't called (so only string expressio…
ericvsmith May 2, 2019
93df017
WIP: Change !x to !d, to match an earlier discussion on python-ideas.
ericvsmith May 2, 2019
99283dc
Call repr on the resulting expression. Change expr_source to expr_text.
ericvsmith May 2, 2019
1b29503
Add simple tests.
ericvsmith May 2, 2019
2684897
Remove FVC_DEBUG, which I thought I was going to use for this feature…
ericvsmith May 2, 2019
42582d5
Fix some comments and formatting.
ericvsmith May 2, 2019
1ffa2a1
Added blurb.
ericvsmith May 2, 2019
14af246
Use quotes around the string result in the blurb text.
ericvsmith May 2, 2019
f985b10
Call out the conversion specifiers in monospace.
ericvsmith May 2, 2019
4556421
Note that the repr of the expression is used.
ericvsmith May 2, 2019
856485a
Fix conversion character.
ericvsmith May 2, 2019
a384516
Added Whats New entry.
ericvsmith May 2, 2019
6107b54
Don't allow format_spec with !d.
ericvsmith May 2, 2019
3f292c5
Test that we don't allow a format spec.
ericvsmith May 2, 2019
0046262
Use repr(expr) if no format spec, use format(expr, spec) if one is.
ericvsmith May 3, 2019
a697d02
Pre-append the '=' to the end of expr_text at compile time, instead o…
ericvsmith May 4, 2019
49d03e4
Add a coding comment, a note about why I'm using a unicode identifier…
ericvsmith May 4, 2019
f79e21d
Added tests for nested f-strings and for pre- and post-text.
ericvsmith May 4, 2019
ed82083
Add a test for newlines in expressions.
ericvsmith May 4, 2019
aeef3db
Clarify a comment.
ericvsmith May 4, 2019
caf3221
Change from di to =.
ericvsmith May 5, 2019
921d8a1
Fix a comment.
ericvsmith May 5, 2019
bd65ec1
Fix comment.
ericvsmith May 5, 2019
1acc386
Fix comments.
ericvsmith May 5, 2019
d46f83c
Skip all whitespace after the =, not just spaces. Handle unparsing.
ericvsmith May 6, 2019
abff2cb
Hack: Modify tests for f-string annotations. I'm not sure these are e…
ericvsmith May 6, 2019
75ef1ac
Add missing FVC_ASCII.
ericvsmith May 6, 2019
e688fbf
Move the mapping from "no converion specified" to the exact conversion
ericvsmith May 6, 2019
3e7e9ff
Make ast_unparse understand =.
ericvsmith May 6, 2019
6a39185
Add a test for !s.
ericvsmith May 6, 2019
e32563c
Improve comments.
ericvsmith May 6, 2019
9fe98cf
Update blurb for = syntax.
ericvsmith May 6, 2019
6c62fce
Fix whatsnew.
ericvsmith May 6, 2019
d453f03
Fix comments about FVC_ values.
ericvsmith May 6, 2019
bb7d2e2
Restored some f-string tests, this time without !f.
ericvsmith May 7, 2019
40231e6
Fix and improve a comment.
ericvsmith May 7, 2019
34c2cf9
Use Py_ISSPACE instead of isspace.
ericvsmith May 7, 2019
2dde0df
Fix parsing error with >= and <=. Add test for operators containing =.
ericvsmith May 7, 2019
92a0b1a
< and > without a trailing = don't end an expression.
ericvsmith May 7, 2019
0015f11
Add a clarifying comment about a test.
ericvsmith May 7, 2019
46d4611
Add tests for named parameters, to exercise the = parsing.
ericvsmith May 7, 2019
1bfb6e9
Improve the tests for named parameters.
ericvsmith May 7, 2019
5fa0eac
Fix a typo in a comment.
ericvsmith May 7, 2019
451cd27
Removed !f conversion. Switch to repr if using = and a format spec is…
ericvsmith May 7, 2019
8e5a7c5
Add more tests for = and repr/format.
ericvsmith May 7, 2019
8e73948
Fixed string annotations checks.
ericvsmith May 7, 2019
0ec4dae
Removed unreachable code left over from !f.
ericvsmith May 7, 2019
975f0f8
Removed 'f' type conversion.
ericvsmith May 7, 2019
19fb7fa
Fixed a spurious space.
ericvsmith May 7, 2019
c3a8f12
Fixed whitespace.
ericvsmith May 7, 2019
dba7421
Address review findings.
ericvsmith May 8, 2019
1a53ccd
Minor comments and variable renaming.
ericvsmith May 8, 2019
19dc0d0
bpo-36816: Update the self-signed.pythontest.net cert (GH-13192)
gpshead May 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ extensions compiled in release mode and for C extensions compiled with the
stable ABI.
(Contributed by Victor Stinner in :issue:`36722`.)

f-strings now support = for quick and easy debugging
-----------------------------------------------------

Add ``=`` specifier to f-strings. ``f'{expr=}'`` expands
to the text of the expression, an equal sign, then the repr of the
evaluated expression. So::

x = 3
print(f'{x*9 + 15=}')

Would print ``x*9 + 15=42``.

(Contributed by Eric V. Smith and Larry Hastings in :issue:`36817`.)


Other Language Changes
======================
Expand Down
7 changes: 4 additions & 3 deletions Include/Python-ast.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 32 additions & 14 deletions Lib/test/selfsigned_pythontestdotnet.pem
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIClTCCAf6gAwIBAgIJAKGU95wKR8pTMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV
BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u
IFNvZnR3YXJlIEZvdW5kYXRpb24xIzAhBgNVBAMMGnNlbGYtc2lnbmVkLnB5dGhv
bnRlc3QubmV0MB4XDTE0MTEwMjE4MDkyOVoXDTI0MTAzMDE4MDkyOVowcDELMAkG
A1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRo
b24gU29mdHdhcmUgRm91bmRhdGlvbjEjMCEGA1UEAwwac2VsZi1zaWduZWQucHl0
aG9udGVzdC5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANDXQXW9tjyZ
Xt0Iv2tLL1+jinr4wGg36ioLDLFkMf+2Y1GL0v0BnKYG4N1OKlAU15LXGeGer8vm
Sv/yIvmdrELvhAbbo3w4a9TMYQA4XkIVLdvu3mvNOAet+8PMJxn26dbDhG809ALv
EHY57lQsBS3G59RZyBPVqAqmImWNJnVzAgMBAAGjNzA1MCUGA1UdEQQeMByCGnNl
bGYtc2lnbmVkLnB5dGhvbnRlc3QubmV0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
AQEFBQADgYEAIuzAhgMouJpNdf3URCHIineyoSt6WK/9+eyUcjlKOrDoXNZaD72h
TXMeKYoWvJyVcSLKL8ckPtDobgP2OTt0UkyAaj0n+ZHaqq1lH2yVfGUA1ILJv515
C8BqbvVZuqm3i7ygmw3bqE/lYMgOrYtXXnqOrz6nvsE6Yc9V9rFflOM=
MIIF9zCCA9+gAwIBAgIUH98b4Fw/DyugC9cV7VK7ZODzHsIwDQYJKoZIhvcNAQEL
BQAwgYoxCzAJBgNVBAYTAlhZMRcwFQYDVQQIDA5DYXN0bGUgQW50aHJheDEYMBYG
A1UEBwwPQXJndW1lbnQgQ2xpbmljMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg
Rm91bmRhdGlvbjEjMCEGA1UEAwwac2VsZi1zaWduZWQucHl0aG9udGVzdC5uZXQw
HhcNMTkwNTA4MDEwMjQzWhcNMjcwNzI0MDEwMjQzWjCBijELMAkGA1UEBhMCWFkx
FzAVBgNVBAgMDkNhc3RsZSBBbnRocmF4MRgwFgYDVQQHDA9Bcmd1bWVudCBDbGlu
aWMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQD
DBpzZWxmLXNpZ25lZC5weXRob250ZXN0Lm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAMKdJlyCThkahwoBb7pl5q64Pe9Fn5jrIvzsveHTc97TpjV2
RLfICnXKrltPk/ohkVl6K5SUZQZwMVzFubkyxE0nZPHYHlpiKWQxbsYVkYv01rix
IFdLvaxxbGYke2jwQao31s4o61AdlsfK1SdpHQUynBBMssqI3SB4XPmcA7e+wEEx
jxjVish4ixA1vuIZOx8yibu+CFCf/geEjoBMF3QPdzULzlrCSw8k/45iZCSoNbvK
DoL4TVV07PHOxpheDh8ZQmepGvU6pVqhb9m4lgmV0OGWHgozd5Ur9CbTVDmxIEz3
TSoRtNJK7qtyZdGNqwjksQxgZTjM/d/Lm/BJG99AiOmYOjsl9gbQMZgvQmMAtUsI
aMJnQuZ6R+KEpW/TR5qSKLWZSG45z/op+tzI2m+cE6HwTRVAWbcuJxcAA55MZjqU
OOOu3BBYMjS5nf2sQ9uoXsVBFH7i0mQqoW1SLzr9opI8KsWwFxQmO2vBxWYaN+lH
OmwBZBwyODIsmI1YGXmTp09NxRYz3Qe5GCgFzYowpMrcxUC24iduIdMwwhRM7rKg
7GtIWMSrFfuI1XCLRmSlhDbhNN6fVg2f8Bo9PdH9ihiIyxSrc+FOUasUYCCJvlSZ
8hFUlLvcmrZlWuazohm0lsXuMK1JflmQr/DA/uXxP9xzFfRy+RU3jDyxJbRHAgMB
AAGjUzBRMB0GA1UdDgQWBBSQJyxiPMRK01i+0BsV9zUwDiBaHzAfBgNVHSMEGDAW
gBSQJyxiPMRK01i+0BsV9zUwDiBaHzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4ICAQCR+7a7N/m+WLkxPPIA/CB4MOr2Uf8ixTv435Nyv6rXOun0+lTP
ExSZ0uYQ+L0WylItI3cQHULldDueD+s8TGzxf5woaLKf6tqyr0NYhKs+UeNEzDnN
9PHQIhX0SZw3XyXGUgPNBfRCg2ZDdtMMdOU4XlQN/IN/9hbYTrueyY7eXq9hmtI9
1srftAMqr9SR1JP7aHI6DVgrEsZVMTDnfT8WmLSGLlY1HmGfdEn1Ip5sbo9uSkiH
AEPgPfjYIvR5LqTOMn4KsrlZyBbFIDh9Sl99M1kZzgH6zUGVLCDg1y6Cms69fx/e
W1HoIeVkY4b4TY7Bk7JsqyNhIuqu7ARaxkdaZWhYaA2YyknwANdFfNpfH+elCLIk
BUt5S3f4i7DaUePTvKukCZiCq4Oyln7RcOn5If73wCeLB/ZM9Ei1HforyLWP1CN8
XLfpHaoeoPSWIveI0XHUl65LsPN2UbMbul/F23hwl+h8+BLmyAS680Yhn4zEN6Ku
B7Po90HoFa1Du3bmx4jsN73UkT/dwMTi6K072FbipnC1904oGlWmLwvAHvrtxxmL
Pl3pvEaZIu8wa/PNF6Y7J7VIewikIJq6Ta6FrWeFfzMWOj2qA1ZZi6fUaDSNYvuV
J5quYKCc/O+I/yDDf8wyBbZ/gvUXzUHTMYGG+bFrn1p7XDbYYeEJ6R/xEg==
-----END CERTIFICATE-----
109 changes: 109 additions & 0 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
# There are tests here with unicode string literals and
# identifiers. There's a code in ast.c that was added because of a
# failure with a non-ascii-only expression. So, I have tests for
# that. There are workarounds that would let me run tests for that
# code without unicode identifiers and strings, but just using them
# directly seems like the easiest and therefore safest thing to do.
# Unicode identifiers in tests is allowed by PEP 3131.

import ast
import types
import decimal
Expand Down Expand Up @@ -878,6 +887,12 @@ def test_not_equal(self):
self.assertEqual(f'{3!=4!s}', 'True')
self.assertEqual(f'{3!=4!s:.3}', 'Tru')

def test_equal_equal(self):
# Because an expression ending in = has special meaning,
# there's a special test for ==. Make sure it works.

self.assertEqual(f'{0==1}', 'False')

def test_conversions(self):
self.assertEqual(f'{3.14:10.10}', ' 3.14')
self.assertEqual(f'{3.14!s:10.10}', '3.14 ')
Expand Down Expand Up @@ -1049,6 +1064,100 @@ def test_backslash_char(self):
self.assertEqual(eval('f"\\\n"'), '')
self.assertEqual(eval('f"\\\r"'), '')

def test_debug_conversion(self):
x = 'A string'
self.assertEqual(f'{x=}', 'x=' + repr(x))
self.assertEqual(f'{x =}', 'x =' + repr(x))
self.assertEqual(f'{x=!s}', 'x=' + str(x))
self.assertEqual(f'{x=!r}', 'x=' + repr(x))
self.assertEqual(f'{x=!a}', 'x=' + ascii(x))

x = 2.71828
self.assertEqual(f'{x=:.2f}', 'x=' + format(x, '.2f'))
self.assertEqual(f'{x=:}', 'x=' + format(x, ''))
self.assertEqual(f'{x=!r:^20}', 'x=' + format(repr(x), '^20'))
self.assertEqual(f'{x=!s:^20}', 'x=' + format(str(x), '^20'))
self.assertEqual(f'{x=!a:^20}', 'x=' + format(ascii(x), '^20'))

x = 9
self.assertEqual(f'{3*x+15=}', '3*x+15=42')

# There is code in ast.c that deals with non-ascii expression values. So,
# use a unicode identifier to trigger that.
tenπ = 31.4
self.assertEqual(f'{tenπ=:.2f}', 'tenπ=31.40')

# Also test with Unicode in non-identifiers.
self.assertEqual(f'{"Σ"=}', '"Σ"=\'Σ\'')

# Make sure nested fstrings still work.
self.assertEqual(f'{f"{3.1415=:.1f}":*^20}', '*****3.1415=3.1*****')

# Make sure text before and after an expression with = works
# correctly.
pi = 'π'
self.assertEqual(f'alpha α {pi=} ω omega', "alpha α pi='π' ω omega")

# Check multi-line expressions.
self.assertEqual(f'''{
3
=}''', '\n3\n=3')

# Since = is handled specially, make sure all existing uses of
# it still work.

self.assertEqual(f'{0==1}', 'False')
self.assertEqual(f'{0!=1}', 'True')
self.assertEqual(f'{0<=1}', 'True')
self.assertEqual(f'{0>=1}', 'False')
self.assertEqual(f'{(x:="5")}', '5')
self.assertEqual(x, '5')
self.assertEqual(f'{(x:=5)}', '5')
self.assertEqual(x, 5)
self.assertEqual(f'{"="}', '=')

x = 20
# This isn't an assignment expression, it's 'x', with a format
# spec of '=10'. See test_walrus: you need to use parens.
self.assertEqual(f'{x:=10}', ' 20')

# Test named function parameters, to make sure '=' parsing works
# there.
def f(a):
nonlocal x
oldx = x
x = a
return oldx
x = 0
self.assertEqual(f'{f(a="3=")}', '0')
self.assertEqual(x, '3=')
self.assertEqual(f'{f(a=4)}', '3=')
self.assertEqual(x, 4)

# Make sure __format__ is being called.
class C:
def __format__(self, s):
return f'FORMAT-{s}'
def __repr__(self):
return 'REPR'

self.assertEqual(f'{C()=}', 'C()=REPR')
self.assertEqual(f'{C()=!r}', 'C()=REPR')
self.assertEqual(f'{C()=:}', 'C()=FORMAT-')
self.assertEqual(f'{C()=: }', 'C()=FORMAT- ')
self.assertEqual(f'{C()=:x}', 'C()=FORMAT-x')
self.assertEqual(f'{C()=!r:*^20}', 'C()=********REPR********')

def test_walrus(self):
x = 20
# This isn't an assignment expression, it's 'x', with a format
# spec of '=10'.
self.assertEqual(f'{x:=10}', ' 20')

# This is an assignment expression, which requires parens.
self.assertEqual(f'{(x:=10)}', '10')
self.assertEqual(x, 10)


if __name__ == '__main__':
unittest.main()
9 changes: 9 additions & 0 deletions Lib/test/test_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ def test_annotations(self):
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
eq("f'{(lambda x: x)}'")
eq("f'{(None if a else lambda x: x)}'")
eq("f'{x}'")
eq("f'{x!r}'")
eq("f'{x!a}'")
eq("f'{x=!r}'")
eq("f'{x=:}'")
eq("f'{x=:.2f}'")
eq("f'{x=!r}'")
eq("f'{x=!a}'")
eq("f'{x=!s:*^20}'")
eq('(yield from outside_of_generator)')
eq('(yield)')
eq('(yield a + b)')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Add a ``=`` feature f-strings for debugging. This can precede ``!s``,
``!r``, or ``!a``. It produces the text of the expression, followed by
an equal sign, followed by the repr of the value of the expression. So
``f'{3*9+15=}'`` would be equal to the string ``'3*9+15=42'``. If
``=`` is specified, the default conversion is set to ``!r``, unless a
format spec is given, in which case the formatting behavior is
unchanged, and __format__ will be used.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update Lib/test/selfsigned_pythontestdotnet.pem to match self-signed.pythontest.net's new TLS certificate.
2 changes: 1 addition & 1 deletion Parser/Python.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ module Python
-- x < 4 < 3 and (x < 4) < 3
| Compare(expr left, cmpop* ops, expr* comparators)
| Call(expr func, expr* args, keyword* keywords)
| FormattedValue(expr value, int? conversion, expr? format_spec)
| FormattedValue(expr value, int? conversion, expr? format_spec, string? expr_text)
| JoinedStr(expr* values)
| Constant(constant value, string? kind)

Expand Down
35 changes: 29 additions & 6 deletions Python/Python-ast.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading