Skip to content

Commit 3149e8e

Browse files
Jay Chanmichaelmanganiello-eb
Jay Chan
authored andcommitted
[PATCH] Performance enhancement by using string concat vs list joins.
Port of Jay Chan's fix [0] [1], applied to the `0.9.1` tag. We have been using this fix for years, without any issues. [0] justecorruptio@a8d4027 [1] wbond#21
1 parent 1dfb562 commit 3149e8e

File tree

2 files changed

+64
-44
lines changed

2 files changed

+64
-44
lines changed

pybars/_compiler.py

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -163,31 +163,43 @@ class PybarsError(Exception):
163163
pass
164164

165165

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

168-
"""A quasi-list to let the template code avoid special casing."""
173+
def __str__(self):
174+
return self.value
169175

170-
def __str__(self): # Python 3
171-
return ''.join(self)
176+
def __unicode__(self):
177+
return self.value
172178

173-
def __unicode__(self): # Python 2
174-
return u''.join(self)
179+
def append(self, other):
180+
self.value += other
175181

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)
182+
def extend(self, other):
183+
if type(other) is strlist:
184+
self.value += other.value
185+
else:
186+
self.value += ''.join(other)
180187

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))
188+
def __iter__(self):
189+
return iter([self])
185190

191+
def __add__(self, other):
192+
self.value += other.value
193+
return self
194+
195+
def grow(self, other):
196+
if isinstance(other, basestring):
197+
self.value += other
198+
elif type(other) is strlist:
199+
self.value += other.value
186200
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)
201+
for item in other:
202+
self.grow(item)
191203

192204

193205
_map = {
@@ -212,14 +224,15 @@ def escape(something, _escape_re=_escape_re, substitute=substitute):
212224

213225

214226
def pick(context, name, default=None):
215-
if isinstance(name, str) and hasattr(context, name):
227+
try:
228+
return context[name]
229+
except (AttributeError, KeyError, TypeError):
230+
pass
231+
if type(name) is str and hasattr(context, name):
216232
return getattr(context, name)
217233
if hasattr(context, 'get'):
218234
return context.get(name)
219-
try:
220-
return context[name]
221-
except (KeyError, TypeError):
222-
return default
235+
return default
223236

224237

225238
sentinel = object()
@@ -270,34 +283,40 @@ def __unicode__(self):
270283
return unicode(self.context)
271284

272285

286+
ITERABLE_TYPES = (list, tuple)
287+
273288
def resolve(context, *segments):
274-
carryover_data = False
275289

290+
context_type = type(context)
276291
# This makes sure that bare "this" paths don't return a Scope object
277-
if segments == ('',) and isinstance(context, Scope):
292+
if segments == ('',) and context_type is Scope:
278293
return context.get('this')
279294

295+
carryover_data = False
280296
for segment in segments:
281297

298+
if context is None:
299+
return None
300+
282301
# Handle @../index syntax by popping the extra @ along the segment path
283302
if carryover_data:
303+
segment = u'@' + segment
284304
carryover_data = False
285-
segment = u'@%s' % segment
286-
if len(segment) > 1 and segment[0:2] == '@@':
305+
306+
if segment[:2] == '@@':
287307
segment = segment[1:]
288308
carryover_data = True
289309

290-
if context is None:
291-
return None
292-
if segment in (None, ""):
310+
if not segment:
293311
continue
294-
if type(context) in (list, tuple):
295-
offset = int(segment)
296-
context = context[offset]
297-
elif isinstance(context, Scope):
312+
313+
if context_type is Scope:
298314
context = context.get(segment)
315+
elif context_type in ITERABLE_TYPES:
316+
context = context[int(segment)]
299317
else:
300318
context = pick(context, segment)
319+
context_type = type(context)
301320
return context
302321

303322

@@ -336,7 +355,7 @@ def prepare(value, should_escape):
336355

337356

338357
def ensure_scope(context, root):
339-
return context if isinstance(context, Scope) else Scope(context, context, root)
358+
return context if type(context) is Scope else Scope(context, context, root)
340359

341360

342361
def _each(this, options, context):
@@ -378,7 +397,7 @@ def _each(this, options, context):
378397

379398

380399
def _if(this, options, context):
381-
if hasattr(context, '__call__'):
400+
if callable(context):
382401
context = context(this)
383402
if context:
384403
return options['fn'](this)
@@ -403,7 +422,7 @@ def _lookup(this, context, key):
403422

404423

405424
def _blockHelperMissing(this, options, context):
406-
if hasattr(context, '__call__'):
425+
if callable(context):
407426
context = context(this)
408427
if context != u"" and not context:
409428
return options['inverse'](this)
@@ -509,8 +528,8 @@ def start(self):
509528
])
510529
else:
511530
self._result.grow(u"def %s(context, helpers, partials, root):\n" % function_name)
512-
self._result.grow(u" result = strlist()\n")
513531
self._result.grow(u" context = ensure_scope(context, root)\n")
532+
self._result.grow(u" result = strlist()\n")
514533

515534
def finish(self):
516535
lines, ns, function_name = self.stack.pop(-1)
@@ -521,7 +540,7 @@ def finish(self):
521540
self._result.grow(u" result = %s(result)\n" % str_class.__name__)
522541
self._result.grow(u" return result\n")
523542

524-
source = str_class(u"".join(lines))
543+
source = str_class(lines)
525544

526545
self._result = self.stack and self.stack[-1][0]
527546
self._locals = self.stack and self.stack[-1][1]
@@ -574,15 +593,16 @@ def add_block(self, symbol, arguments, nested, alt_nested):
574593
u" value = helper = helpers.get('%s')\n" % symbol,
575594
u" if value is None:\n"
576595
u" value = resolve(context, '%s')\n" % symbol,
577-
u" if helper and hasattr(helper, '__call__'):\n"
596+
u" if helper and callable(helper):\n"
578597
u" value = helper(context, options%s\n" % call,
579598
u" else:\n"
580599
u" value = helpers['blockHelperMissing'](context, options, value)\n"
581-
u" result.grow(value or '')\n"
600+
u" if value:\n"
601+
u" result.grow(value)\n"
582602
])
583603

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

587607
def _lookup_arg(self, arg):
588608
if not arg:
@@ -611,7 +631,7 @@ def find_lookup(self, path, path_type, call):
611631
realname = None
612632
self._result.grow(u" value = %s\n" % path)
613633
self._result.grow([
614-
u" if hasattr(value, '__call__'):\n"
634+
u" if callable(value):\n"
615635
u" value = value(context%s\n" % call,
616636
])
617637
if realname:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
setup(name='pybars3',
23-
version='0.9.1',
23+
version='0.9.1+eventbrite',
2424
description='Handlebars.js templating for Python 3 and 2',
2525
long_description='Documentation is maintained at https://github.com/wbond/pybars3#readme',
2626
author='wbond, mjumbewu',

0 commit comments

Comments
 (0)