classification
Title: isinstance(cls_with_metaclass, non_type) raises KeyError
Type: behavior Stage: needs patch
Components: Interpreter Core Versions: Python 3.9, Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: BTaskaya, serhiy.storchaka, terry.reedy
Priority: normal Keywords:

Created on 2020-04-04 19:28 by terry.reedy, last changed 2020-04-04 22:54 by serhiy.storchaka.

Files
File name Uploaded Description Edit
tem3.py terry.reedy, 2020-04-04 19:28
Messages (5)
msg365774 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-04-04 19:28
Consider class Object defined as follows:

import types
class Type(type):
    __class__ = property({}.__getitem__, {}.__setitem__)
class Object(metaclass=Type):
    __slots__ = '__class__'

isinstance(Object, ob) is true for type and Type and false for anything else.  But for the examples of the latter that I tried, (list, int, types.CodeType, types.MethodType, see attached tem3.py), it incorrectly raises
  KeyError: <class '__main__.Object'> 

I cannot find the C source for isinstance.  In Python/bltinmodule.c, function builtin_isinstance_impl wraps
  retval = PyObject_IsInstance(obj, class_or_tuple);
but grepping for PyObject_IsInstance in *.c and *.h only returned other calls.
msg365776 - (view) Author: Batuhan Taskaya (BTaskaya) * (Python committer) Date: 2020-04-04 19:43
What would you expect in this case? Objects/abstract.c:2429 is where the isinstance code. If only returning False would be enough, something like this (untested) would be enough

--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -2436,6 +2436,10 @@ object_isinstance(PyObject *inst, PyObject *cls)
         retval = PyObject_TypeCheck(inst, (PyTypeObject *)cls);
         if (retval == 0) {
             retval = _PyObject_LookupAttrId(inst, &PyId___class__, &icls);
+            if (retval == NULL && PyErr_Occurred()) {
+                PyErr_Clear();
+                retval = 0;
+            }
msg365791 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-04-04 21:41
All works as expected to me. Your __class__ attribute raise an arbitrary exception, and it is expected, that the code which uses it passes it to you.

I am against silencing all exceptions. It may hide bugs.

It is even documented. See What's New in Python 3.8:

* The CPython interpreter can swallow exceptions in some circumstances.
  In Python 3.8 this happens in fewer cases.  In particular, exceptions
  raised when getting the attribute from the type dictionary are no longer
  ignored. (Contributed by Serhiy Storchaka in :issue:`35459`.)
msg365797 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-04-04 21:58
(Serhiy posted while I wrote this.)
Sorry, I should have quoted the doc.  " If object is not an object of the given type, the function always returns False."  Raising instead is a bug -- even of the object itself is somewhat buggy.

My knowledge of the C codebase is insufficient to review, let alone write, non-trivial changes.  But I was curious where and why Object itself was being used as a key. I presume
  retval = _PyObject_LookupAttrId(inst, &PyId___class__, &icls);
is roughly equivalent to Object.__class__ and indeed, that raises the KeyError.  I presume the why has something to do with the interaction between metaclasses, properties, and slots.  So if the details are correct, your patch should plug this hole.  Can you submit a PR with added test?

Issue background.  The example is from Dan Snider, OP of #38689.  When he typed 'Object(' into IDLE, IDLE tried to pop up a calltip.  But inspect.signature(Object) failed with the (unexpected and undocumented) KeyError when it called isinstance(Object, types.MethodTypes), and IDLE froze.  Given Python's flexibility, it can be hard to write code that works with any user code thrown at it.
msg365801 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-04-04 22:54
> Sorry, I should have quoted the doc.  " If object is not an object of the given type, the function always returns False."  Raising instead is a bug -- even of the object itself is somewhat buggy.

You take it too literally. It does not mean that the function always returns a value. It can also raise an exception. If you press Ctrl-C it may raise an exception. If there is no memory to create some temporary objects, it may raise an exception. If you turn of the computer, it may neither return a value nor raise an exception.

You created a class whose __class__ attribute always raises an exception. What do you expect to get when you use this attribute? {}.__getitem__ always raise a KeyError, because an empty dict does not contain any key.
History
Date User Action Args
2020-04-04 22:54:24serhiy.storchakasetmessages: + msg365801
2020-04-04 21:58:52terry.reedysetnosy: + serhiy.storchaka
2020-04-04 21:58:22terry.reedysetnosy: - serhiy.storchaka
messages: + msg365797
2020-04-04 21:41:52serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg365791
2020-04-04 19:43:47BTaskayasetnosy: + BTaskaya
messages: + msg365776
2020-04-04 19:28:49terry.reedycreate