Skip to content

Commit 1d857da

Browse files
authored
GH-77273: Better bytecodes for f-strings (GH-6132)
1 parent 307bcea commit 1d857da

File tree

15 files changed

+519
-479
lines changed

15 files changed

+519
-479
lines changed

Doc/library/dis.rst

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,26 +1465,47 @@ iterations of the loop.
14651465
an argument from two-byte to four-byte.
14661466

14671467

1468-
.. opcode:: FORMAT_VALUE (flags)
1468+
.. opcode:: CONVERT_VALUE (oparg)
14691469

1470-
Used for implementing formatted literal strings (f-strings). Pops
1471-
an optional *fmt_spec* from the stack, then a required *value*.
1472-
*flags* is interpreted as follows:
1470+
Convert value to a string, depending on ``oparg``::
14731471

1474-
* ``(flags & 0x03) == 0x00``: *value* is formatted as-is.
1475-
* ``(flags & 0x03) == 0x01``: call :func:`str` on *value* before
1476-
formatting it.
1477-
* ``(flags & 0x03) == 0x02``: call :func:`repr` on *value* before
1478-
formatting it.
1479-
* ``(flags & 0x03) == 0x03``: call :func:`ascii` on *value* before
1480-
formatting it.
1481-
* ``(flags & 0x04) == 0x04``: pop *fmt_spec* from the stack and use
1482-
it, else use an empty *fmt_spec*.
1472+
value = STACK.pop()
1473+
result = func(value)
1474+
STACK.push(result)
14831475

1484-
Formatting is performed using :c:func:`PyObject_Format`. The
1485-
result is pushed on the stack.
1476+
* ``oparg == 1``: call :func:`str` on *value*
1477+
* ``oparg == 2``: call :func:`repr` on *value*
1478+
* ``oparg == 3``: call :func:`ascii` on *value*
14861479

1487-
.. versionadded:: 3.6
1480+
Used for implementing formatted literal strings (f-strings).
1481+
1482+
.. versionadded:: 3.13
1483+
1484+
1485+
.. opcode:: FORMAT_SIMPLE
1486+
1487+
Formats the value on top of stack::
1488+
1489+
value = STACK.pop()
1490+
result = value.__format__("")
1491+
STACK.push(result)
1492+
1493+
Used for implementing formatted literal strings (f-strings).
1494+
1495+
.. versionadded:: 3.13
1496+
1497+
.. opcode:: FORMAT_SPEC
1498+
1499+
Formats the given value with the given format spec::
1500+
1501+
spec = STACK.pop()
1502+
value = STACK.pop()
1503+
result = value.__format__(spec)
1504+
STACK.push(result)
1505+
1506+
Used for implementing formatted literal strings (f-strings).
1507+
1508+
.. versionadded:: 3.13
14881509

14891510

14901511
.. opcode:: MATCH_CLASS (count)

Include/internal/pycore_opcode.h

Lines changed: 25 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode.h

Lines changed: 44 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/dis.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,8 @@
2525
_have_code = (types.MethodType, types.FunctionType, types.CodeType,
2626
classmethod, staticmethod, type)
2727

28-
FORMAT_VALUE = opmap['FORMAT_VALUE']
29-
FORMAT_VALUE_CONVERTERS = (
30-
(None, ''),
31-
(str, 'str'),
32-
(repr, 'repr'),
33-
(ascii, 'ascii'),
34-
)
28+
CONVERT_VALUE = opmap['CONVERT_VALUE']
29+
3530
SET_FUNCTION_ATTRIBUTE = opmap['SET_FUNCTION_ATTRIBUTE']
3631
FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure')
3732

@@ -579,13 +574,9 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
579574
elif deop in hascompare:
580575
argval = cmp_op[arg>>4]
581576
argrepr = argval
582-
elif deop == FORMAT_VALUE:
583-
argval, argrepr = FORMAT_VALUE_CONVERTERS[arg & 0x3]
584-
argval = (argval, bool(arg & 0x4))
585-
if argval[1]:
586-
if argrepr:
587-
argrepr += ', '
588-
argrepr += 'with format'
577+
elif deop == CONVERT_VALUE:
578+
argval = (None, str, repr, ascii)[arg]
579+
argrepr = ('', 'str', 'repr', 'ascii')[arg]
589580
elif deop == SET_FUNCTION_ATTRIBUTE:
590581
argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS)
591582
if arg & (1<<i))

Lib/importlib/_bootstrap_external.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,9 @@ def _write_atomic(path, data, mode=0o666):
447447
# Python 3.12b1 3531 (Add PEP 695 changes)
448448
# Python 3.13a1 3550 (Plugin optimizer support)
449449
# Python 3.13a1 3551 (Compact superinstructions)
450-
# Python 3.13a1 3552 (Add SET_FUNCTION_ATTRIBUTE)
450+
# Python 3.13a1 3552 (Remove LOAD_FAST__LOAD_CONST and LOAD_CONST__LOAD_FAST)
451+
# Python 3.13a1 3553 (Add SET_FUNCTION_ATTRIBUTE)
452+
# Python 3.13a1 3554 (more efficient bytecodes for f-strings)
451453

452454
# Python 3.14 will start with 3600
453455

@@ -464,7 +466,7 @@ def _write_atomic(path, data, mode=0o666):
464466
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
465467
# in PC/launcher.c must also be updated.
466468

467-
MAGIC_NUMBER = (3552).to_bytes(2, 'little') + b'\r\n'
469+
MAGIC_NUMBER = (3554).to_bytes(2, 'little') + b'\r\n'
468470

469471
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
470472

Lib/opcode.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def pseudo_op(name, op, real_ops):
110110
def_op('CHECK_EXC_MATCH', 36)
111111
def_op('CHECK_EG_MATCH', 37)
112112

113+
def_op('FORMAT_SIMPLE', 40)
114+
def_op('FORMAT_WITH_SPEC', 41)
115+
113116
def_op('WITH_EXCEPT_START', 49)
114117
def_op('GET_AITER', 50)
115118
def_op('GET_ANEXT', 51)
@@ -213,9 +216,9 @@ def pseudo_op(name, op, real_ops):
213216
def_op('RESUME', 151) # This must be kept in sync with deepfreeze.py
214217
def_op('MATCH_CLASS', 152)
215218

216-
def_op('FORMAT_VALUE', 155)
217219
def_op('BUILD_CONST_KEY_MAP', 156)
218220
def_op('BUILD_STRING', 157)
221+
def_op('CONVERT_VALUE', 158)
219222

220223
def_op('LIST_EXTEND', 162)
221224
def_op('SET_UPDATE', 163)

0 commit comments

Comments
 (0)