<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body text="#000000" bgcolor="#ffffff">
This week I learned something new about trace functions (how to
write a C trace function that survives a
sys.settrace(sys.gettrace()) round-trip), and while writing up what
I learned, I was surprised to discover that trace functions don't
behave the way I thought, or the way the docs say they behave.<br>
<br>
The docs say:<br>
<blockquote>
<p>The trace function is invoked (with <em>event</em> set to <tt
class="docutils literal"><span class="pre">'call'</span></tt>)
whenever a new
local scope is entered; it should return a reference to a local
trace
function to be used that scope, or <tt class="xref docutils
literal"><span class="pre">None</span></tt> if the scope
shouldn’t be traced.</p>
<p>The local trace function should return a reference to itself
(or to another
function for further tracing in that scope), or <tt class="xref
docutils literal"><span class="pre">None</span></tt> to turn
off tracing
in that scope.<br>
</p>
</blockquote>
It's that last part that's wrong: returning None from the trace
function only has an effect on the first call in a new frame. Once
the trace function returns a function for a frame, returning None
from subsequent calls is ignored. A "local trace function" can't
turn off tracing in its scope.<br>
<br>
To demonstrate:<br>
<blockquote><tt>import sys</tt><br>
<br>
<tt>UPTO_LINE = 1</tt><br>
<br>
<tt>def t(frame, event, arg):</tt><br>
<tt> num = frame.f_lineno</tt><br>
<tt> print("line %d" % num)</tt><br>
<tt> if num < UPTO_LINE:</tt><br>
<tt> return t</tt><br>
<br>
<tt>def try_it():</tt><br>
<tt> print("twelve")</tt><br>
<tt> print("thirteen")</tt><br>
<tt> print("fourteen")</tt><br>
<tt> print("fifteen")</tt><br>
<br>
<tt>UPTO_LINE = 1</tt><br>
<tt>sys.settrace(t)</tt><br>
<tt>try_it()</tt><br>
<br>
<tt>UPTO_LINE = 13</tt><br>
<tt>sys.settrace(t)</tt><br>
<tt>try_it()</tt><br>
</blockquote>
Produces:<br>
<blockquote><tt>line 11</tt><br>
<tt>twelve</tt><br>
<tt>thirteen</tt><br>
<tt>fourteen</tt><br>
<tt>fifteen</tt><br>
<tt>line 11</tt><br>
<tt>line 12</tt><br>
<tt>twelve</tt><br>
<tt>line 13</tt><br>
<tt>thirteen</tt><br>
<tt>line 14</tt><br>
<tt>fourteen</tt><br>
<tt>line 15</tt><br>
<tt>fifteen</tt><br>
<tt>line 15</tt><br>
</blockquote>
The first call to try_it() returns None immediately, preventing
tracing for the rest of the function. The second call returns None
at line 13, but the rest of the function is traced anyway. This
behavior is the same in all versions from 2.3 to 3.2, in fact, the
100 lines of code in sysmodule.c responsible for Python tracing
functions are completely unchanged through those versions. (A
deeper mystery that I haven't looked into yet is why Python 3.x
intersperses all of these lines with "line 18" interjections.)<br>
<br>
I'm writing this email because I'm not sure whether this is a
behavior bug or a doc bug. One of them is wrong, since they
disagree. The documented behavior makes sense, and is what people
have all along thought the trace function did. The actual behavior
is a bit more complicated to explain, but is what people have
actually been experiencing. FWIW, PyPy implements the documented
behavior.<br>
<p>Should we fix the code or the docs? I'd be glad to supply a
patch for either.<br>
</p>
<p>--Ned.<br>
</p>
<br>
</body>
</html>