Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Stagnant Frame Data?

11 views
Skip to first unread message

Mike

unread,
Nov 15, 2009, 10:13:41 AM11/15/09
to
I'll apologize first for this somewhat lengthy example. It does
however recreate the problem I've run into. This is stripped-down code
from a much more meaningful system.

I have two example classes, "AutoChecker" and "Snapshot" that evaluate
variables in their caller's namespace using the frame stack. As
written, the output is not what is expected: the variables evaluate to
"stagnant" values.

However, if the one indicated line is uncommented, then the result is
as expected.

So my questions are: Is this a bug in Python? Is this an invalid use
of frame data? Why does the single line "sys._getframe(1).f_locals"
fix the behavior?

Thanks,

Mike


import sys

class Snapshot(object):
def __init__(self, caller_globals, caller_locals):
self.signals = []
self.caller_globals = caller_globals
self.caller_locals = caller_locals

def get_values(self):
samples = {}
for signal in self.signals:
samples[signal] = eval(signal, self.caller_globals,
self.caller_locals)
return samples

def print_values(self):
print 'snapshot data'
for name, value in self.get_values().items():
print '\t', name, '=', value


class AutoChecker(object):
def __init__(self, statement):
self.statement = statement
self.caller_globals = sys._getframe(1).f_globals
self.caller_locals = sys._getframe(1).f_locals
self.snapshot = Snapshot(self.caller_globals,
self.caller_locals)
self.snapshot_history = []

def check(self):
# uncomment following line to get expected behavior
#sys._getframe(1).f_locals
if eval(self.statement, self.caller_globals,
self.caller_locals) == False:
print self.statement, 'failed'
self.snapshot.print_values()
self.snapshot_history.append(self.snapshot.get_values())

def report(self):
if len(self.snapshot_history):
return
print 'snapshot history'
for samples in self.snapshot_history:
for name, value in samples.items():
print '\t', name, '=', value


def f():
x = 0.0
y = 0.0
ac1 = AutoChecker('x < 2.0')
ac1.snapshot.signals.append('x')
ac1.snapshot.signals.append('y')

for i in range(5):
x = i / 2.0
y = x / 2
print i, x
ac1.check()
ac1.snapshot.print_values()
ac1.report()

Peter Otten

unread,
Nov 15, 2009, 11:00:17 AM11/15/09
to
Mike wrote:

> I'll apologize first for this somewhat lengthy example. It does
> however recreate the problem I've run into. This is stripped-down code
> from a much more meaningful system.
>
> I have two example classes, "AutoChecker" and "Snapshot" that evaluate
> variables in their caller's namespace using the frame stack. As
> written, the output is not what is expected: the variables evaluate to
> "stagnant" values.
>
> However, if the one indicated line is uncommented, then the result is
> as expected.
>
> So my questions are: Is this a bug in Python? Is this an invalid use
> of frame data? Why does the single line "sys._getframe(1).f_locals"
> fix the behavior?

A simplified demonstration of your problem:

>>> def f(update):
... a = locals()
... x = 42
... if update: locals()
... print a
...
>>> f(False)
{'update': False}
>>> f(True)
{'a': {...}, 'x': 42, 'update': True}

The local namespace is not a dictionary, and the locals()/f_locals
dictionary contains a snapshot of the local namespace. Accessing the
f_locals attribute is one way to trigger an update of that snapshot.

What's puzzling is that the same dictionary is reused.

Peter

exa...@twistedmatrix.com

unread,
Nov 15, 2009, 11:19:19 AM11/15/09
to pytho...@python.org

http://bugs.python.org/issue6116 is vaguely related.

Jean-Paul

Terry Reedy

unread,
Nov 15, 2009, 1:36:47 PM11/15/09
to pytho...@python.org
Peter Otten wrote:
> Mike wrote:
>
>> I'll apologize first for this somewhat lengthy example. It does
>> however recreate the problem I've run into. This is stripped-down code
>> from a much more meaningful system.
>>
>> I have two example classes, "AutoChecker" and "Snapshot" that evaluate
>> variables in their caller's namespace using the frame stack. As
>> written, the output is not what is expected: the variables evaluate to
>> "stagnant" values.
>>
>> However, if the one indicated line is uncommented, then the result is
>> as expected.
>>
>> So my questions are: Is this a bug in Python?

No. The existence and use of sys._getframe -- notice the leading
underscore -- is a CPython implementation artifact. Use at your own risk.

>> Is this an invalid use of frame data?

Up to you to decide.

>> Why does the single line "sys._getframe(1).f_locals"
>> fix the behavior?

It updates the dictionary.

> A simplified demonstration of your problem:
>
>>>> def f(update):
> ... a = locals()
> ... x = 42
> ... if update: locals()
> ... print a
> ...
>>>> f(False)
> {'update': False}
>>>> f(True)
> {'a': {...}, 'x': 42, 'update': True}
>
> The local namespace is not a dictionary, and the locals()/f_locals
> dictionary contains a snapshot of the local namespace. Accessing the
> f_locals attribute is one way to trigger an update of that snapshot.
>
> What's puzzling is that the same dictionary is reused.

Efficiency? Consistency? The doc for locals says "locals()
Update and return a dictionary representing the current local symbol
table." In class statements, where (currently) the local namespace *is*
a dict, no update is needed and locals() simply returns the dict, the
same one each time.

Terry Jan Reedy


Mike

unread,
Nov 18, 2009, 5:10:09 AM11/18/09
to

Thanks for the replies.

If Peter's concise and much-appreciated example were to be modified as
such:

>>> def f(update):
.....: a = inspect.stack()[0][0].f_locals
.....: x = 42
.....: if update:
.....: inspect.stack()[0][0].f_locals
.....: print a
.....:


>>> f(False)
{'update': False}
>>> f(True)
{'a': {...}, 'x': 42, 'update': True}

Now there's no use of locals() to perform its documented "Update", but
just a simple access of a dictionary. This behavior is curious.

Further modification by accessing 'a' instead of '...f_locals' upon
update produces:

>>> f(False)
{'update': False}
>>> f(True)
{'update': True}

Checking the id() of 'a' and '...f_locals' shows the same "address".

How is it that '...f_locals' gets updated in this example? Is it done
in "stack()"?

Where exactly is the frame data stored prior to updating the
dictionary and can I get to it?

Thanks,
Mike

Mike

unread,
Nov 18, 2009, 5:10:29 AM11/18/09
to
On Nov 15, 1:36 pm, Terry Reedy <tjre...@udel.edu> wrote:

Thanks for the replies.

If Peter's concise and much-appreciated example were to be modified as
such:

>>> def f(update):
.....: a = inspect.stack()[0][0].f_locals
.....: x = 42
.....: if update:
.....: inspect.stack()[0][0].f_locals
.....: print a
.....:

>>> f(False)
{'update': False}
>>> f(True)
{'a': {...}, 'x': 42, 'update': True}

Now there's no use of locals() to perform its documented "Update", but

0 new messages