@@ -677,7 +677,6 @@ class _Unparser(NodeVisitor):
677
677
678
678
def __init__ (self , * , _avoid_backslashes = False ):
679
679
self ._source = []
680
- self ._buffer = []
681
680
self ._precedences = {}
682
681
self ._type_ignores = {}
683
682
self ._indent = 0
@@ -720,14 +719,15 @@ def write(self, text):
720
719
"""Append a piece of text"""
721
720
self ._source .append (text )
722
721
723
- def buffer_writer (self , text ):
724
- self ._buffer .append (text )
722
+ @contextmanager
723
+ def buffered (self , buffer = None ):
724
+ if buffer is None :
725
+ buffer = []
725
726
726
- @property
727
- def buffer (self ):
728
- value = "" .join (self ._buffer )
729
- self ._buffer .clear ()
730
- return value
727
+ original_source = self ._source
728
+ self ._source = buffer
729
+ yield buffer
730
+ self ._source = original_source
731
731
732
732
@contextmanager
733
733
def block (self , * , extra = None ):
@@ -1123,70 +1123,72 @@ def _write_str_avoiding_backslashes(self, string, *, quote_types=_ALL_QUOTES):
1123
1123
def visit_JoinedStr (self , node ):
1124
1124
self .write ("f" )
1125
1125
if self ._avoid_backslashes :
1126
- self . _fstring_JoinedStr ( node , self .buffer_writer )
1127
- self ._write_str_avoiding_backslashes ( self . buffer )
1128
- return
1126
+ with self .buffered () as buffer :
1127
+ self ._write_fstring_inner ( node )
1128
+ return self . _write_str_avoiding_backslashes ( "" . join ( buffer ))
1129
1129
1130
1130
# If we don't need to avoid backslashes globally (i.e., we only need
1131
1131
# to avoid them inside FormattedValues), it's cosmetically preferred
1132
1132
# to use escaped whitespace. That is, it's preferred to use backslashes
1133
1133
# for cases like: f"{x}\n". To accomplish this, we keep track of what
1134
1134
# in our buffer corresponds to FormattedValues and what corresponds to
1135
1135
# Constant parts of the f-string, and allow escapes accordingly.
1136
- buffer = []
1136
+ fstring_parts = []
1137
1137
for value in node .values :
1138
- meth = getattr (self , "_fstring_" + type (value ).__name__ )
1139
- meth (value , self .buffer_writer )
1140
- buffer .append ((self .buffer , isinstance (value , Constant )))
1141
- new_buffer = []
1142
- quote_types = _ALL_QUOTES
1143
- for value , is_constant in buffer :
1144
- # Repeatedly narrow down the list of possible quote_types
1138
+ with self .buffered () as buffer :
1139
+ self ._write_fstring_inner (value )
1140
+ fstring_parts .append (
1141
+ ("" .join (buffer ), isinstance (value , Constant ))
1142
+ )
1143
+
1144
+ new_fstring_parts = []
1145
+ quote_types = list (_ALL_QUOTES )
1146
+ for value , is_constant in fstring_parts :
1145
1147
value , quote_types = self ._str_literal_helper (
1146
- value , quote_types = quote_types ,
1147
- escape_special_whitespace = is_constant
1148
+ value ,
1149
+ quote_types = quote_types ,
1150
+ escape_special_whitespace = is_constant ,
1148
1151
)
1149
- new_buffer .append (value )
1150
- value = "" .join (new_buffer )
1152
+ new_fstring_parts .append (value )
1153
+
1154
+ value = "" .join (new_fstring_parts )
1151
1155
quote_type = quote_types [0 ]
1152
1156
self .write (f"{ quote_type } { value } { quote_type } " )
1153
1157
1158
+ def _write_fstring_inner (self , node ):
1159
+ if isinstance (node , JoinedStr ):
1160
+ # for both the f-string itself, and format_spec
1161
+ for value in node .values :
1162
+ self ._write_fstring_inner (value )
1163
+ elif isinstance (node , Constant ) and isinstance (node .value , str ):
1164
+ value = node .value .replace ("{" , "{{" ).replace ("}" , "}}" )
1165
+ self .write (value )
1166
+ elif isinstance (node , FormattedValue ):
1167
+ self .visit_FormattedValue (node )
1168
+ else :
1169
+ raise ValueError (f"Unexpected node inside JoinedStr, { node !r} " )
1170
+
1154
1171
def visit_FormattedValue (self , node ):
1155
- self .write ("f" )
1156
- self ._fstring_FormattedValue (node , self .buffer_writer )
1157
- self ._write_str_avoiding_backslashes (self .buffer )
1172
+ def unparse_inner (inner ):
1173
+ unparser = type (self )(_avoid_backslashes = True )
1174
+ unparser .set_precedence (_Precedence .TEST .next (), inner )
1175
+ return unparser .visit (inner )
1158
1176
1159
- def _fstring_JoinedStr (self , node , write ):
1160
- for value in node .values :
1161
- meth = getattr (self , "_fstring_" + type (value ).__name__ )
1162
- meth (value , write )
1163
-
1164
- def _fstring_Constant (self , node , write ):
1165
- if not isinstance (node .value , str ):
1166
- raise ValueError ("Constants inside JoinedStr should be a string." )
1167
- value = node .value .replace ("{" , "{{" ).replace ("}" , "}}" )
1168
- write (value )
1169
-
1170
- def _fstring_FormattedValue (self , node , write ):
1171
- write ("{" )
1172
- unparser = type (self )(_avoid_backslashes = True )
1173
- unparser .set_precedence (_Precedence .TEST .next (), node .value )
1174
- expr = unparser .visit (node .value )
1175
- if expr .startswith ("{" ):
1176
- write (" " ) # Separate pair of opening brackets as "{ {"
1177
- if "\\ " in expr :
1178
- raise ValueError ("Unable to avoid backslash in f-string expression part" )
1179
- write (expr )
1180
- if node .conversion != - 1 :
1181
- conversion = chr (node .conversion )
1182
- if conversion not in "sra" :
1183
- raise ValueError ("Unknown f-string conversion." )
1184
- write (f"!{ conversion } " )
1185
- if node .format_spec :
1186
- write (":" )
1187
- meth = getattr (self , "_fstring_" + type (node .format_spec ).__name__ )
1188
- meth (node .format_spec , write )
1189
- write ("}" )
1177
+ with self .delimit ("{" , "}" ):
1178
+ expr = unparse_inner (node .value )
1179
+ if "\\ " in expr :
1180
+ raise ValueError (
1181
+ "Unable to avoid backslash in f-string expression part"
1182
+ )
1183
+ if expr .startswith ("{" ):
1184
+ # Separate pair of opening brackets as "{ {"
1185
+ self .write (" " )
1186
+ self .write (expr )
1187
+ if node .conversion != - 1 :
1188
+ self .write (f"!{ chr (node .conversion )} " )
1189
+ if node .format_spec :
1190
+ self .write (":" )
1191
+ self ._write_fstring_inner (node .format_spec )
1190
1192
1191
1193
def visit_Name (self , node ):
1192
1194
self .write (node .id )
0 commit comments