Holding state across multiple (independent) stack traces?

I’m working with the trace module and I’m running across an issue when re-instantiating an instance. The code is below:

from sympy import *
from sympy.solvers import solve
import trace
import sys

class Trace(trace.Trace):
    def __init__(self):
        super().__init__(countfuncs=True, ignoredirs=[sys.prefix, sys.exec_prefix])

x = symbols('x')
args = [(x**2-1,x)]

tracer = Trace()
tracer.runfunc(solve, *args[0]) 
r = tracer.results()
print(len(r.calledfuncs)) <------- *

del tracer
del r

tracer = Trace()
tracer.runfunc(solve, *args[0])
r = tracer.results()
print(len(r.calledfuncs)) <------- *

Here solve(f, var) is a sympy function that solves algebraic equations (details not important)

I expect the lengths to differ a bit, but they differ quite substantially (example lengths: 502 and 244) . This doesn’t make sense to me since I’m inputting the same arguments (*args[0])) to tracer.runfunc. Initially, I thought some state wasn’t being erased across runs even after recalling trace = Trace(). My fix was to add a del tracer, del r but that didn’t do anything.

Any ideas? I’ve looked at the trace.py source file, but that hasn’t helped. Maybe, my understanding of stack traces is off?

Python version : 3.10.2

Hey Diane is the Trace() class working and you havent imported it?
nywy, so if you have tried clearing existing state of tracer obj, maybe the problem could now be on the sympy library and not trace… does sympy cache results?

Theres an import trace at the top of the code. One thing, I forgot to add is my Trace() class ( I override it so that I can initialize with countfuncs = True) . I’ve updated accordingly (it was always in my code, I just forgot to add it here).

Even if sympy was caching results, why would it effect the number of called functions (i.e., the results to r.countfuncs)?. In any case it seems only certain functions are cached in sympy. I’ll check it out, but its unlikely that’s the issue.

The fact that the number of calls is that high (a few hundred) suggests a fairly complex call tree under the hood. If something high up the tree were cached, that could potentially prevent many other calls, yes?

The reason why I was suspicious that caching in sympy is the main culprit is because I was running the following experiment yesterday:

Lets assume args now has two arguments. Say args = [(x**2-1,x), (x**3-1), x)]. Rerun the program like so (lets call this example A)

tracer = Trace()
tracer.runfunc(solve, *args[0])  <---- x^2 - 1 
r = tracer.results()
print(len(r.calledfuncs)) 

tracer = Trace()
tracer.runfunc(solve, *args[1]) <---- x^3-1
r = tracer.results()
print(len(r.calledfuncs)) 

The lengths I was getting were 502 and 644 respectively. Now lets say I flip the order, so I call args[1]
(i.e. (x**3-1, x)) first. Lets call that example B. Then the lengths become 637 and 645 respectively. This begs the question:

  1. If we take the length of the function that is run first as truth, so normally running x^2-1 garners a length of 502 like in example A, why would running it second in example B garner such a sharp increase? If caching is somehow helping reduce the length wouldn’t the length of x^2-1 in example B be less than or equal to what it was in example A?

Also, is there a general way to clear the cache from any function call without knowing explicitly what function caused results to be cached?. Because, while I’m using a sympy function to test, I’d want this to work for any function (independent of some library), so being able to clear the cache in sympy, only fixes this for sympy functions

No, because there are arbitrarily many ways to implement a cache. (Also, in the case of something like Sympy, internal caches in the C implementation of mpmath are probably not exposed at the Python level in any form.)

1 Like

that’s unfortunately. Well thank you!