[pypy-commit] pypy py3.5: Issue #2639

arigo pypy.commits at gmail.com
Wed Aug 23 11:31:58 EDT 2017


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.5
Changeset: r92224:8fabef083b67
Date: 2017-08-23 17:31 +0200
http://bitbucket.org/pypy/pypy/changeset/8fabef083b67/

Log:	Issue #2639

	Assigning '__class__' between ModuleType and subclasses

diff --git a/pypy/interpreter/module.py b/pypy/interpreter/module.py
--- a/pypy/interpreter/module.py
+++ b/pypy/interpreter/module.py
@@ -10,9 +10,10 @@
 class Module(W_Root):
     """A module."""
 
-    _immutable_fields_ = ["w_dict?"]
+    _immutable_fields_ = ["w_dict?", "w_userclass?"]
 
     _frozen = False
+    w_userclass = None
 
     def __init__(self, space, w_name, w_dict=None):
         self.space = space
@@ -148,6 +149,26 @@
                         self)
         return space.call_function(space.w_list, w_dict)
 
+    # These three methods are needed to implement '__class__' assignment
+    # between a module and a subclass of module.  They give every module
+    # the ability to have its '__class__' set, manually.  Note that if
+    # you instantiate a subclass of ModuleType in the first place, then
+    # you get an RPython instance of a subclass of Module created in the
+    # normal way by typedef.py.  That instance has got its own
+    # getclass(), getslotvalue(), etc. but provided it has no __slots__,
+    # it is compatible with ModuleType for '__class__' assignment.
+
+    def getclass(self, space):
+        if self.w_userclass is None:
+            return W_Root.getclass(self, space)
+        return self.w_userclass
+
+    def setclass(self, space, w_cls):
+        self.w_userclass = w_cls
+
+    def user_setup(self, space, w_subtype):
+        self.w_userclass = w_subtype
+
 
 def init_extra_module_attrs(space, w_mod):
     w_dict = w_mod.getdict(space)
diff --git a/pypy/interpreter/test/test_module.py b/pypy/interpreter/test/test_module.py
--- a/pypy/interpreter/test/test_module.py
+++ b/pypy/interpreter/test/test_module.py
@@ -220,3 +220,45 @@
         import sys
         m = type(sys).__new__(type(sys))
         assert not m.__dict__
+
+    def test_class_assignment_for_module(self):
+        import sys
+        modtype = type(sys)
+        class X(modtype):
+            _foobar_ = 42
+
+        m = X("yytest_moduleyy")
+        assert type(m) is m.__class__ is X
+        assert m._foobar_ == 42
+        m.__class__ = modtype
+        assert type(m) is m.__class__ is modtype
+        assert not hasattr(m, '_foobar_')
+
+        m = modtype("xxtest_modulexx")
+        assert type(m) is m.__class__ is modtype
+        m.__class__ = X
+        assert m._foobar_ == 42
+        assert type(m) is m.__class__ is X
+
+        sys.__class__ = modtype
+        assert type(sys) is sys.__class__ is modtype
+        sys.__class__ = X
+        assert sys._foobar_ == 42
+        sys.__class__ = modtype
+
+        class XX(modtype):
+            __slots__ = ['a', 'b']
+
+        x = XX("zztest_modulezz")
+        assert x.__class__ is XX
+        raises(AttributeError, "x.a")
+        x.a = 42
+        assert x.a == 42
+        x.a = 43
+        assert x.a == 43
+        assert 'a' not in x.__dict__
+        del x.a
+        raises(AttributeError, "x.a")
+        raises(AttributeError, "del x.a")
+        raises(TypeError, "x.__class__ = X")
+        raises(TypeError, "sys.__class__ = XX")
diff --git a/pypy/objspace/std/objectobject.py b/pypy/objspace/std/objectobject.py
--- a/pypy/objspace/std/objectobject.py
+++ b/pypy/objspace/std/objectobject.py
@@ -141,13 +141,17 @@
 
 def descr_set___class__(space, w_obj, w_newcls):
     from pypy.objspace.std.typeobject import W_TypeObject
+    from pypy.interpreter.module import Module
+    #
     if not isinstance(w_newcls, W_TypeObject):
         raise oefmt(space.w_TypeError,
                     "__class__ must be set to a class, not '%T' "
                     "object", w_newcls)
-    if not w_newcls.is_heaptype():
+    if not (w_newcls.is_heaptype() or
+            w_newcls is space.gettypeobject(Module.typedef)):
         raise oefmt(space.w_TypeError,
-                    "__class__ assignment: only for heap types")
+                    "__class__ assignment only supported for heap types "
+                    "or ModuleType subclasses")
     w_oldcls = space.type(w_obj)
     assert isinstance(w_oldcls, W_TypeObject)
     if (w_oldcls.get_full_instance_layout() ==


More information about the pypy-commit mailing list