Skip to content

Commit 949c1ed

Browse files
author
Jay Chan
committed
Performance enhancement by using string concat vs list joins.
1 parent 1e6dfca commit 949c1ed

File tree

1 file changed

+57
-36
lines changed

1 file changed

+57
-36
lines changed

pybars/_compiler.py

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
except NameError:
4343
# Python 3 support
4444
str_class = str
45+
basestring = str
4546

4647

4748
# Flag for testing
@@ -163,31 +164,43 @@ class PybarsError(Exception):
163164
pass
164165

165166

166-
class strlist(list):
167+
class strlist(object):
168+
__slots__ = ['value']
169+
def __init__(self, default=None):
170+
self.value = u''
171+
if default:
172+
self.grow(default)
167173

168-
"""A quasi-list to let the template code avoid special casing."""
174+
def __str__(self):
175+
return self.value
176+
177+
def __unicode__(self):
178+
return self.value
169179

170-
def __str__(self): # Python 3
171-
return ''.join(self)
180+
def append(self, other):
181+
self.value += other
172182

173-
def __unicode__(self): # Python 2
174-
return u''.join(self)
183+
def extend(self, other):
184+
if type(other) is strlist:
185+
self.value += other.value
186+
else:
187+
self.value += ''.join(other)
175188

176-
def grow(self, thing):
177-
"""Make the list longer, appending for unicode, extending otherwise."""
178-
if type(thing) == str_class:
179-
self.append(thing)
189+
def __iter__(self):
190+
return iter([self])
180191

181-
# This will only ever match in Python 2 since str_class is str in
182-
# Python 3.
183-
elif type(thing) == str:
184-
self.append(unicode(thing))
192+
def __add__(self, other):
193+
self.value += other.value
194+
return self
185195

196+
def grow(self, other):
197+
if isinstance(other, basestring):
198+
self.value += other
199+
elif type(other) is strlist:
200+
self.value += other.value
186201
else:
187-
# Recursively expand to a flat list; may deserve a C accelerator at
188-
# some point.
189-
for element in thing:
190-
self.grow(element)
202+
for item in other:
203+
self.grow(item)
191204

192205

193206
_map = {
@@ -212,14 +225,15 @@ def escape(something, _escape_re=_escape_re, substitute=substitute):
212225

213226

214227
def pick(context, name, default=None):
215-
if isinstance(name, str) and hasattr(context, name):
228+
if type(name) is str and hasattr(context, name):
216229
return getattr(context, name)
217230
if hasattr(context, 'get'):
218231
return context.get(name)
219232
try:
220233
return context[name]
221234
except (KeyError, TypeError):
222-
return default
235+
pass
236+
return default
223237

224238

225239
sentinel = object()
@@ -270,34 +284,40 @@ def __unicode__(self):
270284
return unicode(self.context)
271285

272286

287+
ITERABLE_TYPES = (list, tuple)
288+
273289
def resolve(context, *segments):
274-
carryover_data = False
275290

291+
context_type = type(context)
276292
# This makes sure that bare "this" paths don't return a Scope object
277-
if segments == ('',) and isinstance(context, Scope):
293+
if segments == ('',) and context_type is Scope:
278294
return context.get('this')
279295

296+
carryover_data = False
280297
for segment in segments:
281298

299+
if context is None:
300+
return None
301+
282302
# Handle @../index syntax by popping the extra @ along the segment path
283303
if carryover_data:
304+
segment = u'@' + segment
284305
carryover_data = False
285-
segment = u'@%s' % segment
286-
if len(segment) > 1 and segment[0:2] == '@@':
306+
307+
if segment[:2] == '@@':
287308
segment = segment[1:]
288309
carryover_data = True
289310

290-
if context is None:
291-
return None
292-
if segment in (None, ""):
311+
if not segment:
293312
continue
294-
if type(context) in (list, tuple):
295-
offset = int(segment)
296-
context = context[offset]
297-
elif isinstance(context, Scope):
313+
314+
if context_type is Scope:
298315
context = context.get(segment)
316+
elif context_type in ITERABLE_TYPES:
317+
context = context[int(segment)]
299318
else:
300319
context = pick(context, segment)
320+
context_type = type(context)
301321
return context
302322

303323

@@ -336,7 +356,7 @@ def prepare(value, should_escape):
336356

337357

338358
def ensure_scope(context, root):
339-
return context if isinstance(context, Scope) else Scope(context, context, root)
359+
return context if type(context) is Scope else Scope(context, context, root)
340360

341361

342362
def _each(this, options, context):
@@ -509,8 +529,8 @@ def start(self):
509529
])
510530
else:
511531
self._result.grow(u"def %s(context, helpers, partials, root):\n" % function_name)
512-
self._result.grow(u" result = strlist()\n")
513532
self._result.grow(u" context = ensure_scope(context, root)\n")
533+
self._result.grow(u" result = strlist()\n")
514534

515535
def finish(self):
516536
lines, ns, function_name = self.stack.pop(-1)
@@ -521,7 +541,7 @@ def finish(self):
521541
self._result.grow(u" result = %s(result)\n" % str_class.__name__)
522542
self._result.grow(u" return result\n")
523543

524-
source = str_class(u"".join(lines))
544+
source = str_class(lines)
525545

526546
self._result = self.stack and self.stack[-1][0]
527547
self._locals = self.stack and self.stack[-1][1]
@@ -578,11 +598,12 @@ def add_block(self, symbol, arguments, nested, alt_nested):
578598
u" value = helper(context, options%s\n" % call,
579599
u" else:\n"
580600
u" value = helpers['blockHelperMissing'](context, options, value)\n"
581-
u" result.grow(value or '')\n"
601+
u" if value:\n"
602+
u" result.grow(value)\n"
582603
])
583604

584605
def add_literal(self, value):
585-
self._result.grow(u" result.append(%s)\n" % repr(value))
606+
self._result.grow(u" result.value += %s\n" % (repr(value),))
586607

587608
def _lookup_arg(self, arg):
588609
if not arg:

0 commit comments

Comments
 (0)