Skip to content

Commit 1837ad1

Browse files
committed
Python 2.5-3.3 compatibility (using a single codebase approach)
1 parent af6c21c commit 1837ad1

File tree

3 files changed

+112
-54
lines changed

3 files changed

+112
-54
lines changed

_line_profiler.pyx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,15 @@ cdef class LineProfiler:
125125
""" Record line profiling information for the given Python function.
126126
"""
127127
try:
128-
code = func.func_code
128+
code = func.__code__
129129
except AttributeError:
130-
import warnings
131-
warnings.warn("Could not extract a code object for the object %r" % (func,))
132-
return
130+
try:
131+
# Python 2.x
132+
code = func.func_code
133+
except AttributeError:
134+
import warnings
135+
warnings.warn("Could not extract a code object for the object %r" % (func,))
136+
return
133137
if code not in self.code_map:
134138
self.code_map[code] = {}
135139
self.functions.append(func)

line_profiler.py

Lines changed: 94 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
#!/usr/bin/env python
22
# -*- coding: UTF-8 -*-
3+
from __future__ import with_statement
4+
try:
5+
import cPickle as pickle
6+
except ImportError:
7+
import pickle
8+
9+
try:
10+
from cStringIO import StringIO
11+
except ImportError:
12+
from io import StringIO
313

4-
import cPickle
5-
from cStringIO import StringIO
614
import inspect
715
import linecache
816
import optparse
@@ -11,12 +19,42 @@
1119

1220
from _line_profiler import LineProfiler as CLineProfiler
1321

22+
# Python 2/3 compatibility utils
23+
# ===========================================================
24+
PY3 = sys.version_info[0] == 3
25+
26+
# next:
27+
try:
28+
next_ = next
29+
except NameError:
30+
next_ = lambda obj: obj.next()
31+
32+
# exec (from https://bitbucket.org/gutworth/six/):
33+
if PY3:
34+
import builtins
35+
exec_ = getattr(builtins, "exec")
36+
del builtins
37+
else:
38+
def exec_(_code_, _globs_=None, _locs_=None):
39+
"""Execute code in a namespace."""
40+
if _globs_ is None:
41+
frame = sys._getframe(1)
42+
_globs_ = frame.f_globals
43+
if _locs_ is None:
44+
_locs_ = frame.f_locals
45+
del frame
46+
elif _locs_ is None:
47+
_locs_ = _globs_
48+
exec("""exec _code_ in _globs_, _locs_""")
49+
50+
# ============================================================
1451

1552
CO_GENERATOR = 0x0020
1653
def is_generator(f):
1754
""" Return True if a function is a generator.
1855
"""
19-
isgen = (f.func_code.co_flags & CO_GENERATOR) != 0
56+
func_code = f.__code__ if PY3 else f.func_code
57+
isgen = (func_code.co_flags & CO_GENERATOR) != 0
2058
return isgen
2159

2260
# Code to exec inside of LineProfiler.__call__ to support PEP-342-style
@@ -30,7 +68,7 @@ def f(*args, **kwds):
3068
# The first iterate will not be a .send()
3169
self.enable_by_count()
3270
try:
33-
item = g.next()
71+
item = next_(g)
3472
finally:
3573
self.disable_by_count()
3674
input = (yield item)
@@ -67,7 +105,7 @@ def __call__(self, func):
67105
if sys.version_info[:2] >= (2,5):
68106
# Delay compilation because the syntax is not compatible with older
69107
# Python versions.
70-
exec pep342_gen_wrapper
108+
exec_(pep342_gen_wrapper)
71109
else:
72110
def wrap_generator(self, func):
73111
""" Wrap a generator to profile it.
@@ -77,7 +115,7 @@ def f(*args, **kwds):
77115
while True:
78116
self.enable_by_count()
79117
try:
80-
item = g.next()
118+
item = next_(g)
81119
finally:
82120
self.disable_by_count()
83121
yield item
@@ -102,7 +140,7 @@ def dump_stats(self, filename):
102140
lstats = self.get_stats()
103141
f = open(filename, 'wb')
104142
try:
105-
cPickle.dump(lstats, f, cPickle.HIGHEST_PROTOCOL)
143+
pickle.dump(lstats, f, pickle.HIGHEST_PROTOCOL)
106144
finally:
107145
f.close()
108146

@@ -116,15 +154,15 @@ def run(self, cmd):
116154
""" Profile a single executable statment in the main namespace.
117155
"""
118156
import __main__
119-
dict = __main__.__dict__
120-
return self.runctx(cmd, dict, dict)
157+
main_dict = __main__.__dict__
158+
return self.runctx(cmd, main_dict, main_dict)
121159

122160
def runctx(self, cmd, globals, locals):
123161
""" Profile a single executable statement in the given namespaces.
124162
"""
125163
self.enable_by_count()
126164
try:
127-
exec cmd in globals, locals
165+
exec_(cmd, globals, locals)
128166
finally:
129167
self.disable_by_count()
130168
return self
@@ -144,22 +182,23 @@ def show_func(filename, start_lineno, func_name, timings, unit, stream=None):
144182
"""
145183
if stream is None:
146184
stream = sys.stdout
147-
print >>stream, "File: %s" % filename
148-
print >>stream, "Function: %s at line %s" % (func_name, start_lineno)
185+
186+
stream.write("File: %s\n" % filename)
187+
stream.write("Function: %s at line %s\n" % (func_name, start_lineno))
149188
template = '%6s %9s %12s %8s %8s %-s'
150189
d = {}
151190
total_time = 0.0
152191
linenos = []
153192
for lineno, nhits, time in timings:
154193
total_time += time
155194
linenos.append(lineno)
156-
print >>stream, "Total time: %g s" % (total_time * unit)
195+
stream.write("Total time: %g s\n" % (total_time * unit))
157196
if not os.path.exists(filename):
158-
print >>stream, ""
159-
print >>stream, "Could not find file %s" % filename
160-
print >>stream, "Are you sure you are running this program from the same directory"
161-
print >>stream, "that you ran the profiler from?"
162-
print >>stream, "Continuing without the function's contents."
197+
stream.write("\n")
198+
stream.write("Could not find file %s\n" % filename)
199+
stream.write("Are you sure you are running this program from the same directory\n")
200+
stream.write("that you ran the profiler from?\n")
201+
stream.write("Continuing without the function's contents.\n")
163202
# Fake empty lines so we can see the timings, if not the code.
164203
nlines = max(linenos) - min(min(linenos), start_lineno) + 1
165204
sublines = [''] * nlines
@@ -173,24 +212,28 @@ def show_func(filename, start_lineno, func_name, timings, unit, stream=None):
173212
'%5.1f' % (100*time / total_time))
174213
linenos = range(start_lineno, start_lineno + len(sublines))
175214
empty = ('', '', '', '')
176-
header = template % ('Line #', 'Hits', 'Time', 'Per Hit', '% Time',
215+
header = template % ('Line #', 'Hits', 'Time', 'Per Hit', '% Time',
177216
'Line Contents')
178-
print >>stream, ""
179-
print >>stream, header
180-
print >>stream, '=' * len(header)
217+
stream.write("\n")
218+
stream.write(header)
219+
stream.write("\n")
220+
stream.write('=' * len(header))
221+
stream.write("\n")
181222
for lineno, line in zip(linenos, sublines):
182223
nhits, time, per_hit, percent = d.get(lineno, empty)
183-
print >>stream, template % (lineno, nhits, time, per_hit, percent,
184-
line.rstrip('\n').rstrip('\r'))
185-
print >>stream, ""
224+
txt = template % (lineno, nhits, time, per_hit, percent,
225+
line.rstrip('\n').rstrip('\r'))
226+
stream.write(txt)
227+
stream.write("\n")
228+
stream.write("\n")
186229

187230
def show_text(stats, unit, stream=None):
188231
""" Show text for the given timings.
189232
"""
190233
if stream is None:
191234
stream = sys.stdout
192-
print >>stream, 'Timer unit: %g s' % unit
193-
print >>stream, ''
235+
236+
stream.write('Timer unit: %g s\n\n' % unit)
194237
for (fn, lineno, name), timings in sorted(stats.items()):
195238
show_func(fn, lineno, name, stats[fn, lineno, name], unit, stream=stream)
196239

@@ -208,7 +251,7 @@ def magic_lprun(self, parameter_s=''):
208251
pager once the statement has completed.
209252
210253
Options:
211-
254+
212255
-f <function>: LineProfiler only profiles functions and methods it is told
213256
to profile. This option tells the profiler about these functions. Multiple
214257
-f options may be used. The argument may be any expression that gives
@@ -243,7 +286,7 @@ def magic_lprun(self, parameter_s=''):
243286

244287
# Escape quote markers.
245288
opts_def = Struct(D=[''], T=[''], f=[])
246-
parameter_s = parameter_s.replace('"',r'\"').replace("'",r"\'")
289+
parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'")
247290
opts, arg_str = self.parse_options(parameter_s, 'rf:D:T:', list_all=True)
248291
opts.merge(opts_def)
249292

@@ -255,21 +298,28 @@ def magic_lprun(self, parameter_s=''):
255298
for name in opts.f:
256299
try:
257300
funcs.append(eval(name, global_ns, local_ns))
258-
except Exception, e:
259-
raise UsageError('Could not find function %r.\n%s: %s' % (name,
301+
except Exception:
302+
# "except Exception as e" is not supported in Python 2.5
303+
# so we're using a hack to get the exception
304+
e = sys.exc_info()[1]
305+
raise UsageError('Could not find function %r.\n%s: %s' % (name,
260306
e.__class__.__name__, e))
261307

262308
profile = LineProfiler(*funcs)
263309

264310
# Add the profiler to the builtins for @profile.
265-
import __builtin__
266-
if 'profile' in __builtin__.__dict__:
311+
if PY3:
312+
import builtins
313+
else:
314+
import __builtin__ as builtins
315+
316+
if 'profile' in builtins.__dict__:
267317
had_profile = True
268-
old_profile = __builtin__.__dict__['profile']
318+
old_profile = builtins.__dict__['profile']
269319
else:
270320
had_profile = False
271321
old_profile = None
272-
__builtin__.__dict__['profile'] = profile
322+
builtins.__dict__['profile'] = profile
273323

274324
try:
275325
try:
@@ -282,7 +332,7 @@ def magic_lprun(self, parameter_s=''):
282332
"profiled.")
283333
finally:
284334
if had_profile:
285-
__builtin__.__dict__['profile'] = old_profile
335+
builtins.__dict__['profile'] = old_profile
286336

287337
# Trap text output.
288338
stdout_trap = StringIO()
@@ -294,24 +344,24 @@ def magic_lprun(self, parameter_s=''):
294344
page(output, screen_lines=self.shell.rc.screen_length)
295345
else:
296346
page(output)
297-
print message,
347+
print(message)
298348

299349
dump_file = opts.D[0]
300350
if dump_file:
301351
profile.dump_stats(dump_file)
302-
print '\n*** Profile stats pickled to file',\
303-
`dump_file`+'.',message
352+
print('\n*** Profile stats pickled to file %r. %s' % (
353+
dump_file, message))
304354

305355
text_file = opts.T[0]
306356
if text_file:
307357
pfile = open(text_file, 'w')
308358
pfile.write(output)
309359
pfile.close()
310-
print '\n*** Profile printout saved to text file',\
311-
`text_file`+'.',message
360+
print('\n*** Profile printout saved to text file %r. %s' % (
361+
text_file, message))
312362

313363
return_value = None
314-
if opts.has_key('r'):
364+
if 'r' in opts:
315365
return_value = profile
316366

317367
return return_value
@@ -327,12 +377,8 @@ def load_stats(filename):
327377
""" Utility function to load a pickled LineStats object from a given
328378
filename.
329379
"""
330-
f = open(filename, 'rb')
331-
try:
332-
lstats = cPickle.load(f)
333-
finally:
334-
f.close()
335-
return lstats
380+
with open(filename, 'rb') as f:
381+
return pickle.load(f)
336382

337383

338384
def main():

setup.py

100644100755
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from Cython.Distutils import build_ext
1010
cmdclass = dict(build_ext=build_ext)
1111
line_profiler_source = '_line_profiler.pyx'
12-
except ImportError, e:
12+
except ImportError:
1313
cmdclass = {}
1414
line_profiler_source = '_line_profiler.c'
1515
if not os.path.exists(line_profiler_source):
@@ -37,7 +37,7 @@
3737
description = 'Line-by-line profiler.',
3838
long_description = long_description,
3939
url = 'http://packages.python.org/line_profiler',
40-
ext_modules = [
40+
ext_modules = [
4141
Extension('_line_profiler',
4242
sources=[line_profiler_source, 'timers.c', 'unset_trace.c'],
4343
depends=['python25.pxd'],
@@ -51,6 +51,14 @@
5151
"Operating System :: OS Independent",
5252
"Programming Language :: C",
5353
"Programming Language :: Python",
54+
'Programming Language :: Python :: 2',
55+
'Programming Language :: Python :: 2.5',
56+
'Programming Language :: Python :: 2.6',
57+
'Programming Language :: Python :: 2.7',
58+
'Programming Language :: Python :: 3',
59+
'Programming Language :: Python :: 3.2',
60+
'Programming Language :: Python :: 3.3',
61+
'Programming Language :: Python :: Implementation :: CPython',
5462
"Topic :: Software Development",
5563
],
5664
py_modules = ['line_profiler'],

0 commit comments

Comments
 (0)