This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: RuntimeError when profiling Decimal
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.1, Python 3.2, Python 2.7, Python 2.6
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: belopolsky, ezio.melotti, mark.dickinson, rhettinger, skrah, ubershmekel
Priority: normal Keywords: patch

Created on 2010-06-30 22:18 by ubershmekel, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
issue9136.patch mark.dickinson, 2010-07-01 12:59
issue9136-blue-bikeshed.patch skrah, 2010-07-01 18:32
issue9136-blue-bikeshed-2.patch skrah, 2010-07-01 20:10
issue9136-py3k-3.patch skrah, 2010-07-02 10:38
issue9136-trunk-3.patch skrah, 2010-07-02 10:38
Messages (30)
msg109023 - (view) Author: Yuval Greenfield (ubershmekel) * Date: 2010-06-30 22:18
I'm using Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit (Intel)] on win32. Running the following code:


    import profile
    import math
    import decimal
    
    
    def show_bug():
        x = decimal.Decimal.from_float(math.e)**(-2**31)
    
    profile.run('show_bug()')

And I get this stack trace:

Traceback (most recent call last):
  File "C:/Users/Me/Documents/python/temptest", line 15, in <module>
    profile.run('show_bug()')
  File "C:\Python31\lib\profile.py", line 70, in run
    prof = prof.run(statement)
  File "C:\Python31\lib\profile.py", line 456, in run
    return self.runctx(cmd, dict, dict)
  File "C:\Python31\lib\profile.py", line 462, in runctx
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "C:/Users/Me/Documents/python/temptest", line 8, in show_bug
    x = Decimal.from_float(math.e)**(-2**31)
  File "C:\Python31\lib\decimal.py", line 2212, in __pow__
    context = getcontext()
  File "C:\Python31\lib\decimal.py", line 447, in getcontext
    context = Context()
  File "C:\Python31\lib\decimal.py", line 3777, in __init__
    for name, val in locals().items():
RuntimeError: dictionary changed size during iteration
msg109035 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 09:27
I can't reproduce this on Linux.  However, I've seen an informal report of something like this happening before.  It looks a lot like something threading related, but I don't see any threads in what you're doing.  (Though I don't know the details of how the profile module works, on either Linux or Windows.)

The use of locals() in the Context constructor is an unnecessary hack that's easy to fix;  I'll do that.  But it would still be good to understand exactly where this is coming from.
msg109036 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 09:28
BTW, is the behaviour consistent, or does it only occur on some runs?
msg109037 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 09:41
Okay, I can reproduce by adding a 'time.sleep(0.01)' delay into the body of the 'for name, val in locals().items():' loop.  I then get (with py3k):

dickinsm@alberti:~/Source/py3k> ./python
Python 3.2a0 (py3k:82413M, Jul  1 2010, 10:21:02)
[GCC 4.2.1 (SUSE Linux)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import decimal, profile
>>> def show_bug(): decimal.Decimal(1.2)**(-2**31)
...
>>> profile.run('show_bug()')
Traceback (most recent call last):
  File "/home/dickinsm/Source/py3k/Lib/decimal.py", line 447, in getcontext
    return _local.__decimal_context__
AttributeError: '_thread._local' object has no attribute '__decimal_context__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/dickinsm/Source/py3k/Lib/profile.py", line 70, in run
    prof = prof.run(statement)
  File "/home/dickinsm/Source/py3k/Lib/profile.py", line 442, in run
    return self.runctx(cmd, dict, dict)
  File "/home/dickinsm/Source/py3k/Lib/profile.py", line 448, in runctx
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "<stdin>", line 1, in show_bug
  File "/home/dickinsm/Source/py3k/Lib/decimal.py", line 2222, in __pow__
    context = getcontext()
  File "/home/dickinsm/Source/py3k/Lib/decimal.py", line 449, in getcontext
    context = Context()
  File "/home/dickinsm/Source/py3k/Lib/decimal.py", line 3822, in __init__
    for name, val in locals().items():
RuntimeError: dictionary changed size during iteration
msg109039 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 10:29
Mark Dickinson <report@bugs.python.org> wrote:
> I can't reproduce this on Linux.  However, I've seen an informal report of something like this happening before.  It looks a lot like something threading related, but I don't see any threads in what you're doing.  (Though I don't know the details of how the profile module works, on either Linux or Windows.)

I can reproduce it consistently, also --without-threads. I'm lacking knowledge
about the profile module, but it has a comment "(old profiler used to write into
the frames local dictionary!!". Perhaps there's still something like that going on.
msg109040 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 10:32
Removing the decimal module from the equation, the following is enough to trigger this for me.  Stefan's suggestion about the profile module writing to locals sounds right on target.


import profile

class Context(object):
    def __init__(self):
        for name, val in locals().items():
            setattr(self, name, val)

profile.run("Context()")
msg109041 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 10:47
class Context(object):
    def __init__(self):
        for name, val in locals().items():
            setattr(self, name, val)


Isn't this dodgy anyway, since 'name' and 'val' end up going into locals()?  I wonder why the RuntimeError *isn't* raised for a normal 'Context()' call (i.e., not via profile).
msg109042 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 10:53
Still clueless about profile.py, but I think it creates a parallel stack,
and overwriting of locals legitimately occurs in that stack, producing
the Exception.

So it would appear that by design one simply cannot iterate over locals
when using the profile module.
msg109043 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 11:10
Mark Dickinson <report@bugs.python.org> wrote:
> 
> Mark Dickinson <dickinsm@gmail.com> added the comment:
> 
> class Context(object):
>     def __init__(self):
>         for name, val in locals().items():
>             setattr(self, name, val)
> 
> 
> Isn't this dodgy anyway, since 'name' and 'val' end up going into locals()?  I wonder why the RuntimeError *isn't* raised for a normal 'Context()' call (i.e., not via profile).

Indeed, and it seems to depend on how name and vals are used:

...     print(locals())
... 
{'name': '__builtins__', 'val': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '__package__': None, 'Context': <class '__main__.Context'>, '__name__': '__main__', '__doc__': None}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

...     pass
... 
>>> locals()
{'name': '__doc__', 'val': None, '__builtins__': <module 'builtins' (built-in)>, '__package__': None, 'Context': <class '__main__.Context'>, '__name__': '__main__', '__doc__': None}
>>>
msg109044 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 11:13
The tracker doesn't handle code when posted by mail. Here's the
code again:

>>> for name, val in locals().items():
...     print(locals())
... 
{'name': '__builtins__', 'val': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

 
>>> for name, val in locals().items():
...     pass
... 
>>> 
>>> locals()
{'name': '__doc__', 'val': None, '__builtins__': <module 'builtins' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}
>>>
msg109045 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 11:48
Ah, it looks like 'locals()' is somewhat magical.  Its docstring says:

"Update and return a dictionary containing the current scope's local variables."

So I think this explains your (Stefan's) results:  in either case, you evaluate locals() (as the target of the for statement) and get a dictionary back.  But that dictionary isn't updated to include 'name' and 'val' until you call locals() for a second time.  (And possibly there are other activities besides an explicit locals() call that would cause that dict to be updated, but I'm not sure.)

I still don't understand how things work when profile is added into the mix, but I'm willing to accept that the profile module affects locals() in strange and possibly timing-dependent ways.

Anyway, the fix for decimal is clear:  get rid of that locals call.
msg109046 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 11:53
> it looks like 'locals()' is somewhat magical

... and Objects/frameobject.c is the place to go for a full understanding.  PyFrame_FastToLocals is the 'updating' function that updates the locals dict from the 'fast locals' object.
msg109047 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 12:59
Here's a patch.  It's a little ugly, but I don't see a better way that avoids locals().  Using **kwargs is an option, but would mean abandoning the current nice feature that unexpected keyword arguments raise TypeError (or alternatively, implementing that check separately).

This should be backported to 2.x after 2.7 has been released;  for that backport, the dictionary comprehensions should be replaced by dict(<list_comprehension>), so that the code remains valid with older Python versions.
msg109065 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 18:32
Mark, the patch looks good. I don't find it ugly, but anyway, here's a
bike shed version. :)

The main point is that I'd like the flags and traps sections to be
set apart visually while compressing the boilerplate.
msg109066 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 18:47
Nice shade of blue!  Just a couple of red spots that I'd prefer repainted, namely: (1) please put bodies of 'try:' and 'except:' on separate lines, and (2) please use 'except NameError' instead of a bare except.

And a couple of points that I think also applied to my patch:

- {s: 0 for s in _signals} could also be spelt dict.fromkeys(_signals, 0)

- I think dict(dc.traps) should be dc.traps.copy() instead.  I can't entirely see why it would matter, except that it's conceivable that dc.traps could by a dict subclass with its own copy method.
msg109068 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 19:11
Stefan, one other thought: please also bear in mind that we're restricted to Python 2.3 syntax in the 2.x version of decimal, so any post-Python 2.3-isms will have to be rewritten when the patch is backported.  (E.g., use of conditional expressions.)

While I don't see any particular reason for the 3.x version of decimal to restrict its syntax in this manner, it might be worth sticking to simple syntax just so that the 2.x and 3.x decimal versions don't differ too much, to make maintenance easier.
msg109071 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-01 20:10
Mark, good point about 2.3 compatibility. The unit tests diverge quite a
bit though between 2.5, 2.6 and 2.7.

I like {s: 0 for s in _signals} slightly better here (but I like
dict/list comprehensions in general).


So, the new patch still sets the flags/traps sections apart, doesn't
use the ternary construct and uses traps.copy().


IOW, I only painted the door blue. ;)
msg109072 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 20:39
Looks good to me.  Please go ahead and apply it to the 3.2 and 3.1 branches, if you want.  (Or just assign back to me if you prefer.)

It should also be applied to 2.7 and 2.6, eventually, but we should probably wait until after the 2.7 release this weekend for that.
msg109074 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-07-01 21:16
Stylistically, it would be nice to eliminate the local variable reassignment entirely:

self.Emin = DefaultContext.Emin if Emin is None else Emin
self.Emax = DefaultContext.Emax if Emax is None else Emax
self._ignored_flags = [] if _ignored_flags is None else _ignored_flags

Also, to keep the code consistent between versions, it may be better to avoid set/dict comprehensions and stick with the old:

    dict([(s, int(s in flags)) for s in _signals])

or slightly more modern generator comprehension:

    dict((s, int(s in flags)) for s in _signals)
msg109075 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 21:19
[Raymond]
> self.Emin = DefaultContext.Emin if Emin is None else Emin

I agree that looks better.  But we can't spell it that way in 2.x (since conditional expressions aren't 2.3 compatible), and I'd prefer to keep the 2.x and 3.x versions reasonably close, at least while 2.x is still being maintained.

Still, no strong feelings either way.
msg109077 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-07-01 21:35
I'm not sure that I care about 2.3 compatibility anymore (I was the one who made the rule about 2.3 compatibility and made sure that it was just a preference, not an absolute rule -- as time goes on, it is of less and less value).  ISTM, 2.5 compatible is probably good enough.
msg109079 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-01 21:41
> ISTM, 2.5 compatible is probably good enough.

Okay;  that's fine with me.  Now we can finally turn that "from_float = classmethod(from_float)" into a proper "@classmethod" decorator.  :)

I also don't think that the 2.x-to-3.x maintenance issue is that big a deal any more;  it would be surprising if the 2.x version of Decimal sees anything more than minor changes (doc fixes, etc.) from this point on.  So perhaps the 3.x version of the decimal module should be free to make full use of 3.x syntax?
msg109081 - (view) Author: Yuval Greenfield (ubershmekel) * Date: 2010-07-01 21:52
Now that we're on the subject of "from_float", I just recalled this slight
issue:

Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit (Intel)]
on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import decimal
>>> x = decimal.Decimal()
>>> decimal.Decimal.from_float(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python31\lib\decimal.py", line 687, in from_float
    n, d = abs(f).as_integer_ratio()
AttributeError: 'Decimal' object has no attribute 'as_integer_ratio'
>>>

It seems from_float doesn't like it when a Decimal arrives. Personally, I
think there should be an idiomatic way of saying "this number must be a
Decimal" without an "isinstance".

Should I open another ticket?
msg109086 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2010-07-02 01:04
> I also don't think that the 2.x-to-3.x maintenance issue 
> is that big a deal any more;  it would be surprising if 
> the 2.x version of Decimal sees anything more than minor 
> changes (doc fixes, etc.) from this point on.  So perhaps 
> the 3.x version of the decimal module should be free to 
> make full use of 3.x syntax?

I would like to still leave us at 2.5 compatible.
The internal comments promise that spec updates
will be treated as bug fixes.  This is all the
more likely if 2.7 is the last, but long lived
version in the 2.x series.

Besides, I don't think the set comprehension
notation was a big win in readability over
what is there now :-)
msg109091 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-02 07:38
2.5 compatibility is fine for the 2.x version of decimal.py.

For the 3.x version of decimal.py, I don't see what it buys us.
msg109097 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-02 10:38
In the new patches I reinstated the ternary construct. I think it
would be nice to use dict comprehensions in py3k. People read the
stdlib for guidance, and it would help if ultimately only the most
concise idioms remained in py3k.

If I'm not mistaken, quite often the unit tests already use the latest
constructs available.


That said, there are always issues that only become apparent after
maintaining a module for years, so I guess I'll wait for a decision.
msg109284 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2010-07-05 01:36
+        try:
+            dc = DefaultContext
+        except NameError:
+            pass
+
+        self.prec = dc.prec if prec is None else prec

I don't quite understand the point of catching NameError here, but it looks like if it is caught, it will just be raised by the next line because dc will not be set.

Also, I am not sure ternary expressions are a big readability win here, but if you want to keep them, consider 

self.prec = prec if prec is not None else dc.prec
msg109309 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-05 07:09
> I don't quite understand the point of catching NameError here

So that the initialization of DefaultContext itself doesn't fail.
msg109332 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2010-07-05 15:55
Alexander, for me the ternary expressions have the advantage that they
take up less vertical space and you have to keep less state in mind.


Mark, I'm sidestepping the dict syntax question and reassign to you :)
msg109613 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2010-07-08 21:31
Fixed in revisions r82654 through r82657.  Thanks everyone for the input.
History
Date User Action Args
2022-04-11 14:57:03adminsetgithub: 53382
2010-07-08 21:31:29mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg109613

stage: commit review -> resolved
2010-07-05 15:55:16skrahsetassignee: skrah -> mark.dickinson
messages: + msg109332
2010-07-05 07:09:05mark.dickinsonsetmessages: + msg109309
2010-07-05 01:36:06belopolskysetnosy: + belopolsky
messages: + msg109284
2010-07-02 10:38:32skrahsetfiles: + issue9136-trunk-3.patch
2010-07-02 10:38:10skrahsetfiles: + issue9136-py3k-3.patch

messages: + msg109097
2010-07-02 09:43:54skrahsetfiles: - unnamed
2010-07-02 07:38:04mark.dickinsonsetmessages: + msg109091
2010-07-02 01:04:40rhettingersetmessages: + msg109086
2010-07-01 21:52:44ubershmekelsetfiles: + unnamed

messages: + msg109081
2010-07-01 21:41:40mark.dickinsonsetmessages: + msg109079
2010-07-01 21:35:03rhettingersetmessages: + msg109077
2010-07-01 21:19:31mark.dickinsonsetmessages: + msg109075
2010-07-01 21:16:46rhettingersetnosy: + rhettinger
messages: + msg109074
2010-07-01 20:39:26mark.dickinsonsetassignee: mark.dickinson -> skrah
messages: + msg109072
2010-07-01 20:10:12skrahsetfiles: + issue9136-blue-bikeshed-2.patch

messages: + msg109071
2010-07-01 19:11:44mark.dickinsonsetmessages: + msg109068
2010-07-01 18:47:12mark.dickinsonsetmessages: + msg109066
2010-07-01 18:32:28skrahsetfiles: + issue9136-blue-bikeshed.patch

messages: + msg109065
2010-07-01 12:59:55mark.dickinsonsetfiles: + issue9136.patch
versions: + Python 2.6, Python 2.7, Python 3.2
messages: + msg109047

keywords: + patch
stage: needs patch -> commit review
2010-07-01 11:53:46mark.dickinsonsetmessages: + msg109046
2010-07-01 11:48:52mark.dickinsonsetmessages: + msg109045
stage: test needed -> needs patch
2010-07-01 11:13:40skrahsetmessages: + msg109044
2010-07-01 11:10:34skrahsetmessages: + msg109043
2010-07-01 10:53:48skrahsetmessages: + msg109042
2010-07-01 10:47:21mark.dickinsonsetmessages: + msg109041
2010-07-01 10:32:28mark.dickinsonsetmessages: + msg109040
2010-07-01 10:29:42skrahsetmessages: + msg109039
2010-07-01 09:41:09mark.dickinsonsetmessages: + msg109037
2010-07-01 09:28:18mark.dickinsonsetmessages: + msg109036
2010-07-01 09:27:21mark.dickinsonsetassignee: mark.dickinson
messages: + msg109035
2010-06-30 23:16:19skrahsetnosy: + skrah
2010-06-30 22:19:48ezio.melottisetnosy: + mark.dickinson, ezio.melotti

stage: test needed
2010-06-30 22:18:21ubershmekelcreate