2828 TokenType .DELIMITER ,
2929)
3030
31+ # Pre-compute common space strings for indentation (avoids repeated multiplication)
32+ _SPACE_CACHE = ["" ] + [" " * i for i in range (1 , 33 )] # Cache up to 32 spaces
33+
34+
35+ def _get_spaces (n : int ) -> str :
36+ """Get n spaces, using cache when possible."""
37+ if n < len (_SPACE_CACHE ):
38+ return _SPACE_CACHE [n ]
39+ return " " * n
40+
3141
3242class ContextNode :
3343 __slots__ = ("context" , "parent_context_node" )
@@ -47,11 +57,12 @@ def get(self, key: str) -> t.Any:
4757 if key == "." :
4858 return self .context
4959
50- chain_iter = iter (key .split ("." ))
60+ # Split only once and cache
61+ keys = key .split ("." )
5162 curr_ctx = self .context
5263 parent_node = self .parent_context_node
5364
54- first_key = next ( chain_iter )
65+ first_key = keys [ 0 ]
5566
5667 # TODO I think this is where changes need to be made if we want to
5768 # support indexing and lambdas.
@@ -72,7 +83,7 @@ def get(self, key: str) -> t.Any:
7283 return None
7384
7485 # Loop through the rest
75- for key in chain_iter :
86+ for key in keys [ 1 :] :
7687 if isinstance (outer_context , list ):
7788 try :
7889 int_key = int (key )
@@ -97,17 +108,11 @@ def open_section(self, key: str) -> t.List[ContextNode]:
97108
98109 # In the case of the list, need a new context for each item
99110 if isinstance (new_context , list ):
100- res_list = []
101-
102- for item in new_context :
103- new_stack = ContextNode (item , self )
104- # new_stack.parent_context_node = self
105- res_list .append (new_stack )
106-
111+ # Pre-allocate list for better performance
112+ res_list = [ContextNode (item , self ) for item in new_context ]
107113 return res_list
108114
109- new_stack = ContextNode (new_context , self )
110- return [new_stack ]
115+ return [ContextNode (new_context , self )]
111116
112117
113118class TagType (enum .Enum ):
@@ -186,6 +191,7 @@ def __repr__(self) -> str:
186191class MustacheRenderer :
187192 mustache_tree : MustacheTreeNode
188193 partials_dict : t .Dict [str , MustacheTreeNode ]
194+ __slots__ = ("mustache_tree" , "partials_dict" )
189195
190196 def __init__ (
191197 self ,
@@ -210,6 +216,7 @@ def render(
210216 html_escape_fn : t .Callable [[str ], str ] = html_escape ,
211217 ) -> str :
212218 res_list = []
219+ res_list_append = res_list .append # Cache method lookup
213220 starting_context = ContextNode (data )
214221 last_was_newline = True # Track if we're at the start of a line
215222
@@ -220,18 +227,20 @@ def render(
220227 (node , starting_context , self .mustache_tree .offset )
221228 for node in self .mustache_tree .children
222229 )
230+ work_deque_popleft = work_deque .popleft # Cache method lookup
231+ work_deque_appendleft = work_deque .appendleft # Cache method lookup
223232 while work_deque :
224- curr_node , curr_context , curr_offset = work_deque . popleft ()
233+ curr_node , curr_context , curr_offset = work_deque_popleft ()
225234 if curr_node .tag_type is TagType .LITERAL :
226- res_list . append (curr_node .data )
235+ res_list_append (curr_node .data )
227236 last_was_newline = curr_node .data .endswith ("\n " )
228237
229238 # Add offset for partials after newline, but only if there's
230239 # more content coming at the same offset level
231240 if last_was_newline and curr_offset > 0 :
232241 # Check if the next node also has the same offset
233242 if work_deque and work_deque [0 ][2 ] == curr_offset :
234- res_list . append ( curr_offset * " " )
243+ res_list_append ( _get_spaces ( curr_offset ) )
235244
236245 elif (
237246 curr_node .tag_type is TagType .VARIABLE
@@ -246,7 +255,7 @@ def render(
246255 if curr_node .tag_type is TagType .VARIABLE :
247256 str_content = html_escape_fn (str_content )
248257
249- res_list . append (str_content )
258+ res_list_append (str_content )
250259 elif curr_node .tag_type is TagType .SECTION :
251260 new_context_stacks = curr_context .open_section (curr_node .data )
252261
@@ -255,7 +264,7 @@ def render(
255264 for new_context_stack in reversed (new_context_stacks ):
256265 for child_node in reversed (curr_node .children ):
257266 # No need to make a copy of the context per-child, it's immutable
258- work_deque . appendleft ((child_node , new_context_stack , 0 ))
267+ work_deque_appendleft ((child_node , new_context_stack , 0 ))
259268
260269 elif curr_node .tag_type is TagType .INVERTED_SECTION :
261270 # No need to add to the context stack, inverted sections
@@ -266,7 +275,7 @@ def render(
266275
267276 if not bool (lookup_data ):
268277 for child_node in reversed (curr_node .children ):
269- work_deque . appendleft ((child_node , curr_context , 0 ))
278+ work_deque_appendleft ((child_node , curr_context , 0 ))
270279
271280 elif curr_node .tag_type is TagType .PARTIAL :
272281 partial_tree = self .partials_dict .get (curr_node .data )
@@ -279,12 +288,12 @@ def render(
279288 # For standalone partials, add indentation at the beginning
280289 # Only add if we're at the start of a line (last output was newline)
281290 if curr_node .offset > 0 and last_was_newline :
282- res_list . append ( curr_node .offset * " " )
291+ res_list_append ( _get_spaces ( curr_node .offset ) )
283292 last_was_newline = False
284293
285294 # Propagate the combined offset through the partial content
286295 for child_node in reversed (partial_tree .children ):
287- work_deque . appendleft (
296+ work_deque_appendleft (
288297 (child_node , curr_context , curr_offset + curr_node .offset )
289298 )
290299
0 commit comments