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

Stagnant Frame Data?

1 view
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