[pypy-commit] pypy default: Fix copy.copy() on generator objects to make sure they get their own
arigo
noreply at buildbot.pypy.org
Wed May 8 08:54:11 CEST 2013
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r63906:b3975062187f
Date: 2013-05-08 08:53 +0200
http://bitbucket.org/pypy/pypy/changeset/b3975062187f/
Log: Fix copy.copy() on generator objects to make sure they get their own
frame too, rather than sharing it with the original generator (which
crashes completely). Of limited use (see comments in the test).
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -27,7 +27,7 @@
new_inst = mod.get('generator_new')
w = space.wrap
if self.frame:
- w_frame = w(self.frame)
+ w_frame = self.frame._reduce_state(space)
else:
w_frame = space.w_None
@@ -36,7 +36,20 @@
w(self.running),
]
- return space.newtuple([new_inst, space.newtuple(tup)])
+ return space.newtuple([new_inst, space.newtuple([]),
+ space.newtuple(tup)])
+
+ def descr__setstate__(self, space, w_args):
+ from rpython.rlib.objectmodel import instantiate
+ args_w = space.unpackiterable(w_args)
+ w_framestate, w_running = args_w
+ if space.is_w(w_framestate, space.w_None):
+ self.frame = None
+ else:
+ frame = instantiate(space.FrameClass) # XXX fish
+ frame.descr__setstate__(space, w_framestate)
+ GeneratorIterator.__init__(self, frame)
+ self.running = self.space.is_true(w_running)
def descr__iter__(self):
"""x.__iter__() <==> iter(x)"""
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -16,10 +16,9 @@
from rpython.tool.stdlib_opcode import host_bytecode_spec
# Define some opcodes used
-g = globals()
for op in '''DUP_TOP POP_TOP SETUP_LOOP SETUP_EXCEPT SETUP_FINALLY
POP_BLOCK END_FINALLY'''.split():
- g[op] = stdlib_opcode.opmap[op]
+ globals()[op] = stdlib_opcode.opmap[op]
HAVE_ARGUMENT = stdlib_opcode.HAVE_ARGUMENT
class PyFrame(eval.Frame):
@@ -304,11 +303,17 @@
@jit.dont_look_inside
def descr__reduce__(self, space):
from pypy.interpreter.mixedmodule import MixedModule
- from pypy.module._pickle_support import maker # helper fns
w_mod = space.getbuiltinmodule('_pickle_support')
mod = space.interp_w(MixedModule, w_mod)
new_inst = mod.get('frame_new')
- w = space.wrap
+ w_tup_state = self._reduce_state(space)
+ nt = space.newtuple
+ return nt([new_inst, nt([]), w_tup_state])
+
+ @jit.dont_look_inside
+ def _reduce_state(self, space):
+ from pypy.module._pickle_support import maker # helper fns
+ w = space.wrap
nt = space.newtuple
cells = self._getcells()
@@ -359,8 +364,7 @@
w(self.instr_prev_plus_one),
w_cells,
]
-
- return nt([new_inst, nt([]), nt(tup_state)])
+ return nt(tup_state)
@jit.dont_look_inside
def descr__setstate__(self, space, w_args):
diff --git a/pypy/interpreter/test/test_zzpickle_and_slow.py b/pypy/interpreter/test/test_zzpickle_and_slow.py
--- a/pypy/interpreter/test/test_zzpickle_and_slow.py
+++ b/pypy/interpreter/test/test_zzpickle_and_slow.py
@@ -485,3 +485,68 @@
pckl = pickle.dumps(pack.mod)
result = pickle.loads(pckl)
assert pack.mod is result
+
+
+class AppTestGeneratorCloning:
+
+ def setup_class(cls):
+ try:
+ cls.space.appexec([], """():
+ def f(): yield 42
+ f().__reduce__()
+ """)
+ except TypeError, e:
+ if 'pickle generator' not in str(e):
+ raise
+ py.test.skip("Frames can't be __reduce__()-ed")
+
+ def test_deepcopy_generator(self):
+ import copy
+
+ def f(n):
+ for i in range(n):
+ yield 42 + i
+ g = f(4)
+ g2 = copy.deepcopy(g)
+ res = g.next()
+ assert res == 42
+ res = g2.next()
+ assert res == 42
+ g3 = copy.deepcopy(g)
+ res = g.next()
+ assert res == 43
+ res = g2.next()
+ assert res == 43
+ res = g3.next()
+ assert res == 43
+
+ def test_shallowcopy_generator(self):
+ """Note: shallow copies of generators are often confusing.
+ To start with, 'for' loops have an iterator that will not
+ be copied, and so create tons of confusion.
+ """
+ import copy
+
+ def f(n):
+ while n > 0:
+ yield 42 + n
+ n -= 1
+ g = f(2)
+ g2 = copy.copy(g)
+ res = g.next()
+ assert res == 44
+ res = g2.next()
+ assert res == 44
+ g3 = copy.copy(g)
+ res = g.next()
+ assert res == 43
+ res = g2.next()
+ assert res == 43
+ res = g3.next()
+ assert res == 43
+ g4 = copy.copy(g2)
+ for i in range(2):
+ raises(StopIteration, g.next)
+ raises(StopIteration, g2.next)
+ raises(StopIteration, g3.next)
+ raises(StopIteration, g4.next)
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -913,6 +913,7 @@
GeneratorIterator.typedef = TypeDef("generator",
__repr__ = interp2app(GeneratorIterator.descr__repr__),
__reduce__ = interp2app(GeneratorIterator.descr__reduce__),
+ __setstate__ = interp2app(GeneratorIterator.descr__setstate__),
next = interp2app(GeneratorIterator.descr_next,
descrmismatch='next'),
send = interp2app(GeneratorIterator.descr_send,
diff --git a/pypy/module/_pickle_support/maker.py b/pypy/module/_pickle_support/maker.py
--- a/pypy/module/_pickle_support/maker.py
+++ b/pypy/module/_pickle_support/maker.py
@@ -59,11 +59,8 @@
tb = instantiate(PyTraceback)
return space.wrap(tb)
- at unwrap_spec(running=int)
-def generator_new(space, w_frame, running):
- frame = space.interp_w(PyFrame, w_frame, can_be_None=True)
- new_generator = GeneratorIterator(frame)
- new_generator.running = running
+def generator_new(space):
+ new_generator = instantiate(GeneratorIterator)
return space.wrap(new_generator)
@unwrap_spec(current=int, remaining=int, step=int)
More information about the pypy-commit
mailing list