Author: Armin Rigo <[email protected]>
Branch:
Changeset: r85719:d6a44698760d
Date: 2016-07-15 10:03 +0200
http://bitbucket.org/pypy/pypy/changeset/d6a44698760d/
Log: hg merge issue2343
Copy CPython's logic more closely for isinstance() and issubclass()
as well as type.__instancecheck__() and type.__subclasscheck__().
Add tests.
diff --git a/pypy/module/__builtin__/abstractinst.py
b/pypy/module/__builtin__/abstractinst.py
--- a/pypy/module/__builtin__/abstractinst.py
+++ b/pypy/module/__builtin__/abstractinst.py
@@ -46,9 +46,65 @@
raise # propagate other errors
return space.type(w_obj)
+
+# ---------- isinstance ----------
+
+
+def p_recursive_isinstance_w(space, w_inst, w_cls):
+ # Copied straight from CPython 2.7. Does not handle 'cls' being a tuple.
+ if (isinstance(w_cls, W_ClassObject) and
+ isinstance(w_inst, W_InstanceObject)):
+ return w_inst.w_class.is_subclass_of(w_cls)
+
+ if space.isinstance_w(w_cls, space.w_type):
+ return p_recursive_isinstance_type_w(space, w_inst, w_cls)
+
+ check_class(space, w_cls, "isinstance() arg 2 must be a class, type,"
+ " or tuple of classes and types")
+ try:
+ w_abstractclass = space.getattr(w_inst, space.wrap('__class__'))
+ except OperationError as e:
+ if e.async(space): # ignore most exceptions
+ raise
+ return False
+ else:
+ return p_abstract_issubclass_w(space, w_abstractclass, w_cls)
+
+
+def p_recursive_isinstance_type_w(space, w_inst, w_type):
+ # subfunctionality of p_recursive_isinstance_w(): assumes that w_type is
+ # a type object. Copied straight from CPython 2.7.
+ if space.isinstance_w(w_inst, w_type):
+ return True
+ try:
+ w_abstractclass = space.getattr(w_inst, space.wrap('__class__'))
+ except OperationError as e:
+ if e.async(space): # ignore most exceptions
+ raise
+ else:
+ if w_abstractclass is not space.type(w_inst):
+ if space.isinstance_w(w_abstractclass, space.w_type):
+ return space.issubtype_w(w_abstractclass, w_type)
+ return False
+
+
@jit.unroll_safe
def abstract_isinstance_w(space, w_obj, w_klass_or_tuple,
allow_override=False):
"""Implementation for the full 'isinstance(obj, klass_or_tuple)'."""
+ # Copied from CPython 2.7's PyObject_Isinstance(). Additionally,
+ # if 'allow_override' is False (the default), then don't try to
+ # use a custom __instancecheck__ method.
+
+ # WARNING: backward compatibility function name here. CPython
+ # uses the name "abstract" to refer to the logic of handling
+ # class-like objects, with a "__bases__" attribute. This function
+ # here is not related to that and implements the full
+ # PyObject_IsInstance() logic.
+
+ # Quick test for an exact match
+ if space.type(w_obj) is w_klass_or_tuple:
+ return True
+
# -- case (anything, tuple)
# XXX it might be risky that the JIT sees this
if space.isinstance_w(w_klass_or_tuple, space.w_tuple):
@@ -58,68 +114,59 @@
return False
# -- case (anything, type)
- try:
- if allow_override:
- w_result = space.isinstance_allow_override(w_obj, w_klass_or_tuple)
- else:
- w_result = space.isinstance(w_obj, w_klass_or_tuple)
- except OperationError as e: # if w_klass_or_tuple was not a type, ignore
it
- if not e.match(space, space.w_TypeError):
- raise # propagate other errors
- else:
- if space.is_true(w_result):
- return True
- # From now on we know that w_klass_or_tuple is indeed a type.
- # Try also to compare it with obj.__class__, if this is not
- # the same as type(obj).
- try:
- w_pretendtype = space.getattr(w_obj, space.wrap('__class__'))
- if space.is_w(w_pretendtype, space.type(w_obj)):
- return False # common case: obj.__class__ is type(obj)
- if not allow_override:
- return space.issubtype_w(w_pretendtype, w_klass_or_tuple)
- w_result = space.issubtype_allow_override(w_pretendtype,
- w_klass_or_tuple)
- except OperationError as e:
- if e.async(space):
- raise
- return False # ignore most exceptions
- else:
- return space.is_true(w_result)
+ if allow_override:
+ w_check = space.lookup(w_klass_or_tuple, "__instancecheck__")
+ if w_check is not None:
+ # this is the common case: all type objects have a method
+ # __instancecheck__. The one in the base 'type' type calls
+ # back p_recursive_isinstance_type_w() from the present module.
+ return space.is_true(space.get_and_call_function(
+ w_check, w_klass_or_tuple, w_obj))
- # -- case (old-style instance, old-style class)
- if isinstance(w_klass_or_tuple, W_ClassObject):
- if isinstance(w_obj, W_InstanceObject):
- return w_obj.w_class.is_subclass_of(w_klass_or_tuple)
- return _abstract_isinstance_w_helper(space, w_obj, w_klass_or_tuple)
+ return p_recursive_isinstance_w(space, w_obj, w_klass_or_tuple)
-def _abstract_isinstance_w_helper(space, w_obj, w_klass_or_tuple):
- # -- case (anything, abstract-class)
- check_class(space, w_klass_or_tuple,
- "isinstance() arg 2 must be a class, type,"
- " or tuple of classes and types")
- try:
- w_abstractclass = space.getattr(w_obj, space.wrap('__class__'))
- except OperationError as e:
- if e.async(space): # ignore most exceptions
- raise
- return False
- else:
- return _issubclass_recurse(space, w_abstractclass, w_klass_or_tuple)
+# ---------- issubclass ----------
@jit.unroll_safe
-def _issubclass_recurse(space, w_derived, w_top):
- """Internal helper for abstract cases. Here, w_top cannot be a tuple."""
- if space.is_w(w_derived, w_top):
- return True
- w_bases = _get_bases(space, w_derived)
- if w_bases is not None:
- for w_base in space.fixedview(w_bases):
- if _issubclass_recurse(space, w_base, w_top):
+def p_abstract_issubclass_w(space, w_derived, w_cls):
+ # Copied straight from CPython 2.7, function abstract_issubclass().
+ # Don't confuse this with the function abstract_issubclass_w() below.
+ # Here, w_cls cannot be a tuple.
+ while True:
+ if space.is_w(w_derived, w_cls):
+ return True
+ w_bases = _get_bases(space, w_derived)
+ if w_bases is None:
+ return False
+ bases_w = space.fixedview(w_bases)
+ last_index = len(bases_w) - 1
+ if last_index < 0:
+ return False
+ # Avoid recursivity in the single inheritance case; in general,
+ # don't recurse on the last item in the tuple (loop instead).
+ for i in range(last_index):
+ if p_abstract_issubclass_w(space, bases_w[i], w_cls):
return True
- return False
+ w_derived = bases_w[last_index]
+
+
+def p_recursive_issubclass_w(space, w_derived, w_cls):
+ # From CPython's function of the same name (which as far as I can tell
+ # is not recursive). Copied straight from CPython 2.7.
+ if (space.isinstance_w(w_cls, space.w_type) and
+ space.isinstance_w(w_derived, space.w_type)):
+ return space.issubtype_w(w_derived, w_cls)
+ #
+ if (isinstance(w_derived, W_ClassObject) and
+ isinstance(w_cls, W_ClassObject)):
+ return w_derived.is_subclass_of(w_cls)
+ #
+ check_class(space, w_derived, "issubclass() arg 1 must be a class")
+ check_class(space, w_cls, "issubclass() arg 2 must be a class"
+ " or tuple of classes")
+ return p_abstract_issubclass_w(space, w_derived, w_cls)
@jit.unroll_safe
@@ -127,37 +174,31 @@
allow_override=False):
"""Implementation for the full 'issubclass(derived, klass_or_tuple)'."""
- # -- case (class-like-object, tuple-of-classes)
+ # WARNING: backward compatibility function name here. CPython
+ # uses the name "abstract" to refer to the logic of handling
+ # class-like objects, with a "__bases__" attribute. This function
+ # here is not related to that and implements the full
+ # PyObject_IsSubclass() logic. There is also p_abstract_issubclass_w().
+
+ # -- case (anything, tuple-of-classes)
if space.isinstance_w(w_klass_or_tuple, space.w_tuple):
for w_klass in space.fixedview(w_klass_or_tuple):
if abstract_issubclass_w(space, w_derived, w_klass,
allow_override):
return True
return False
- # -- case (type, type)
- try:
- if not allow_override:
- return space.issubtype_w(w_derived, w_klass_or_tuple)
- w_result = space.issubtype_allow_override(w_derived, w_klass_or_tuple)
- except OperationError as e: # if one of the args was not a type, ignore
it
- if not e.match(space, space.w_TypeError):
- raise # propagate other errors
- else:
- return space.is_true(w_result)
+ # -- case (anything, type)
+ if allow_override:
+ w_check = space.lookup(w_klass_or_tuple, "__subclasscheck__")
+ if w_check is not None:
+ # this is the common case: all type objects have a method
+ # __subclasscheck__. The one in the base 'type' type calls
+ # back p_recursive_issubclass_w() from the present module.
+ return space.is_true(space.get_and_call_function(
+ w_check, w_klass_or_tuple, w_derived))
- # -- case (old-style class, old-style class)
- if isinstance(w_derived, W_ClassObject):
- if isinstance(w_klass_or_tuple, W_ClassObject):
- return w_derived.is_subclass_of(w_klass_or_tuple)
- else:
- check_class(space, w_derived, "issubclass() arg 1 must be a class")
- # from here on, we are sure that w_derived is a class-like object
+ return p_recursive_issubclass_w(space, w_derived, w_klass_or_tuple)
- # -- case (class-like-object, abstract-class)
- check_class(space, w_klass_or_tuple,
- "issubclass() arg 2 must be a class, type,"
- " or tuple of classes and types")
- return _issubclass_recurse(space, w_derived, w_klass_or_tuple)
# ------------------------------------------------------------
# Exception helpers
diff --git a/pypy/module/__builtin__/test/test_abstractinst.py
b/pypy/module/__builtin__/test/test_abstractinst.py
--- a/pypy/module/__builtin__/test/test_abstractinst.py
+++ b/pypy/module/__builtin__/test/test_abstractinst.py
@@ -216,3 +216,26 @@
c = C()
assert isinstance(c, C)
assert not called
+
+ def test_instancecheck_exception_not_eaten(self):
+ class M(object):
+ def __instancecheck__(self, obj):
+ raise TypeError("foobar")
+
+ e = raises(TypeError, isinstance, 42, M())
+ assert str(e.value) == "foobar"
+
+ def test_issubclass_exception_not_eaten(self):
+ class M(object):
+ def __subclasscheck__(self, subcls):
+ raise TypeError("foobar")
+
+ e = raises(TypeError, issubclass, 42, M())
+ assert str(e.value) == "foobar"
+
+ def test_issubclass_no_fallback(self):
+ class M(object):
+ def __subclasscheck__(self, subcls):
+ return False
+
+ assert issubclass(42, M()) is False
diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py
--- a/pypy/objspace/descroperation.py
+++ b/pypy/objspace/descroperation.py
@@ -509,21 +509,6 @@
def isinstance(space, w_inst, w_type):
return space.wrap(space.isinstance_w(w_inst, w_type))
- def issubtype_allow_override(space, w_sub, w_type):
- w_check = space.lookup(w_type, "__subclasscheck__")
- if w_check is None:
- raise oefmt(space.w_TypeError, "issubclass not supported here")
- return space.get_and_call_function(w_check, w_type, w_sub)
-
- def isinstance_allow_override(space, w_inst, w_type):
- if space.type(w_inst) is w_type:
- return space.w_True # fast path copied from cpython
- w_check = space.lookup(w_type, "__instancecheck__")
- if w_check is not None:
- return space.get_and_call_function(w_check, w_type, w_inst)
- else:
- return space.isinstance(w_inst, w_type)
-
# helpers
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
@@ -1091,6 +1091,29 @@
C() # the lookup of '__new__' succeeds in 'int',
# but the lookup of '__init__' fails
+ def test_instancecheck(self):
+ assert int.__instancecheck__(42) is True
+ assert int.__instancecheck__(42.0) is False
+ class Foo:
+ __class__ = int
+ assert int.__instancecheck__(Foo()) is False
+ class Bar(object):
+ __class__ = int
+ assert int.__instancecheck__(Bar()) is True
+
+ def test_subclasscheck(self):
+ assert int.__subclasscheck__(bool) is True
+ assert int.__subclasscheck__(float) is False
+ class Foo:
+ __class__ = int
+ assert int.__subclasscheck__(Foo) is False
+ class Bar(object):
+ __class__ = int
+ assert int.__subclasscheck__(Bar) is False
+ class AbstractClass(object):
+ __bases__ = (int,)
+ assert int.__subclasscheck__(AbstractClass()) is True
+
class AppTestWithMethodCacheCounter:
spaceconfig = {"objspace.std.withmethodcachecounter": True}
@@ -1290,5 +1313,3 @@
assert not self.compares_by_identity(X)
del X.__eq__
assert self.compares_by_identity(X)
-
-
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
@@ -6,6 +6,7 @@
from pypy.interpreter.typedef import weakref_descr, GetSetProperty,\
descr_get_dict, dict_descr, Member, TypeDef
from pypy.interpreter.astcompiler.misc import mangle
+from pypy.module.__builtin__ import abstractinst
from rpython.rlib.jit import (promote, elidable_promote, we_are_jitted,
elidable, dont_look_inside, unroll_safe)
@@ -899,13 +900,15 @@
# ____________________________________________________________
[email protected]_spec(w_obj=W_TypeObject, w_sub=W_TypeObject)
[email protected]_spec(w_obj=W_TypeObject)
def type_issubtype(w_obj, space, w_sub):
- return space.newbool(w_sub.issubtype(w_obj))
+ return space.newbool(
+ abstractinst.p_recursive_issubclass_w(space, w_sub, w_obj))
@gateway.unwrap_spec(w_obj=W_TypeObject)
def type_isinstance(w_obj, space, w_inst):
- return space.newbool(space.type(w_inst).issubtype(w_obj))
+ return space.newbool(
+ abstractinst.p_recursive_isinstance_type_w(space, w_inst, w_obj))
W_TypeObject.typedef = TypeDef("type",
__new__ = gateway.interp2app(descr__new__),
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit