Author: Armin Rigo <ar...@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() == _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit