Author: Carl Friedrich Bolz-Tereick <[email protected]>
Branch: py3.6
Changeset: r94462:9e10c6bdc41e
Date: 2018-05-03 10:02 +0200
http://bitbucket.org/pypy/pypy/changeset/9e10c6bdc41e/

Log:    introduces the __init_subclass__ hook (PEP 487)

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
@@ -122,6 +122,8 @@
 def descr___subclasshook__(space, __args__):
     return space.w_NotImplemented
 
+def descr___init_subclass__(space, w_cls):
+    return space.w_None
 
 def descr__init__(space, w_obj, __args__):
     if _excess_args(__args__):
@@ -284,12 +286,14 @@
     __doc__ = "The most base type",
     __new__ = interp2app(descr__new__),
     __subclasshook__ = interp2app(descr___subclasshook__, as_classmethod=True),
+    __init_subclass__ = interp2app(descr___init_subclass__, 
as_classmethod=True),
 
     # these are actually implemented in pypy.objspace.descroperation
     __getattribute__ = interp2app(Object.descr__getattribute__.im_func),
     __setattr__ = interp2app(Object.descr__setattr__.im_func),
     __delattr__ = interp2app(Object.descr__delattr__.im_func),
 
+
     __init__ = interp2app(descr__init__),
     __class__ = GetSetProperty(descr_get___class__, descr_set___class__),
     __repr__ = interp2app(descr__repr__),
diff --git a/pypy/objspace/std/test/test_obj.py 
b/pypy/objspace/std/test/test_obj.py
--- a/pypy/objspace/std/test/test_obj.py
+++ b/pypy/objspace/std/test/test_obj.py
@@ -359,6 +359,11 @@
         assert o.__ge__(o2) is NotImplemented
         assert o.__gt__(o2) is NotImplemented
 
+    def test_init_subclass(self):
+        object().__init_subclass__() # does not crash
+        object.__init_subclass__() # does not crash
+        raises(TypeError, object.__init_subclass__, 1)
+
 def test_isinstance_shortcut():
     from pypy.objspace.std import objspace
     space = objspace.StdObjSpace()
diff --git a/pypy/objspace/std/test/test_typeobject.py 
b/pypy/objspace/std/test/test_typeobject.py
--- a/pypy/objspace/std/test/test_typeobject.py
+++ b/pypy/objspace/std/test/test_typeobject.py
@@ -1556,3 +1556,43 @@
         assert X.a.owner is X
         assert X.a.name == "a"
 
+    def test_type_init_accepts_kwargs(self):
+        type.__init__(type, "a", (object, ), {}, a=1)
+
+    def test_init_subclass_classmethod(self):
+        assert isinstance(object.__dict__['__init_subclass__'], classmethod)
+        class A(object):
+            subclasses = []
+
+            def __init_subclass__(cls):
+                cls.subclass.append(cls)
+        assert isinstance(A.__dict__['__init_subclass__'], classmethod)
+
+    def test_init_subclass(self):
+        class PluginBase(object):
+            subclasses = []
+
+            def __init_subclass__(cls):
+                cls.subclasses.append(cls)
+
+        class B(PluginBase):
+            pass
+
+        class C(PluginBase):
+            pass
+
+        assert PluginBase.subclasses == [B, C]
+
+
+        class X(object):
+            subclasses = []
+
+            def __init_subclass__(cls, **kwargs):
+                cls.kwargs = kwargs
+
+        exec("""if 1:
+        class Y(X, a=1, b=2):
+            pass
+
+        assert Y.kwargs == dict(a=1, b=2)
+        """)
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -749,20 +749,26 @@
         return space.newbool(not space.is_w(self, w_other))
 
 
-def descr__new__(space, w_typetype, w_name, w_bases=None, w_dict=None):
+def descr__new__(space, w_typetype, __args__):
     """This is used to create user-defined classes only."""
+    if len(__args__.arguments_w) not in (1, 3):
+        raise oefmt(space.w_TypeError,
+                    "type.__new__() takes 1 or 3 arguments")
+
+    w_name = __args__.arguments_w[0]
+
     w_typetype = _precheck_for_new(space, w_typetype)
 
     # special case for type(x)
     if (space.is_w(space.type(w_typetype), space.w_type) and
-        w_bases is None and w_dict is None):
+            len(__args__.arguments_w) == 1):
         return space.type(w_name)
-    return _create_new_type(space, w_typetype, w_name, w_bases, w_dict)
+    w_bases = __args__.arguments_w[1]
+    w_dict = __args__.arguments_w[2]
+    return _create_new_type(space, w_typetype, w_name, w_bases, w_dict, 
__args__)
 
 
 def _check_new_args(space, w_name, w_bases, w_dict):
-    if w_bases is None or w_dict is None:
-        raise oefmt(space.w_TypeError, "type() takes 1 or 3 arguments")
     if not space.isinstance_w(w_name, space.w_text):
         raise oefmt(space.w_TypeError,
                     "type() argument 1 must be string, not %T", w_name)
@@ -774,7 +780,7 @@
                     "type() argument 3 must be dict, not %T", w_dict)
 
 
-def _create_new_type(space, w_typetype, w_name, w_bases, w_dict):
+def _create_new_type(space, w_typetype, w_name, w_bases, w_dict, __args__):
     # this is in its own function because we want the special case 'type(x)'
     # above to be seen by the jit.
     _check_new_args(space, w_name, w_bases, w_dict)
@@ -802,6 +808,7 @@
     w_type.ready()
 
     _set_names(space, w_type)
+    _init_subclass(space, w_type, __args__)
     return w_type
 
 def _calculate_metaclass(space, w_metaclass, bases_w):
@@ -831,11 +838,15 @@
             # XXX what happens when the call raises, gets turned into a 
RuntimeError?
             space.get_and_call_function(w_meth, w_value, w_type, 
space.newtext(key))
 
+def _init_subclass(space, w_type, __args__):
+    # bit of a mess, but I didn't feel like implementing the super logic
+    w_super = space.getattr(space.builtin, space.newtext("super"))
+    w_func = space.getattr(space.call_function(w_super, w_type, w_type),
+                           space.newtext("__init_subclass__"))
+    args = __args__.replace_arguments([])
+    space.call_args(w_func, args)
 
 def descr__init__(space, w_type, __args__):
-    if __args__.keywords:
-        raise oefmt(space.w_TypeError,
-                    "type.__init__() takes no keyword arguments")
     if len(__args__.arguments_w) not in (1, 3):
         raise oefmt(space.w_TypeError,
                     "type.__init__() takes 1 or 3 arguments")
@@ -1311,6 +1322,7 @@
     w_self.mro_w = []      # temporarily
     w_self.hasmro = False
     compute_mro(w_self)
+    ensure_classmethod_init_subclass(w_self)
 
 def ensure_static_new(w_self):
     # special-case __new__, as in CPython:
@@ -1320,6 +1332,12 @@
         if isinstance(w_new, Function):
             w_self.dict_w['__new__'] = StaticMethod(w_new)
 
+def ensure_classmethod_init_subclass(w_self):
+    if '__init_subclass__' in w_self.dict_w:
+        w_init_subclass = w_self.dict_w['__init_subclass__']
+        if isinstance(w_init_subclass, Function):
+            w_self.dict_w['__init_subclass__'] = ClassMethod(w_init_subclass)
+
 def ensure_module_attr(w_self):
     # initialize __module__ in the dict (user-defined types only)
     if '__module__' not in w_self.dict_w:
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to