Opened 5 years ago

Closed 5 years ago

#30546 closed Bug (wontfix)

isinstance() call on SimpleLazyObject doesn't swallow evaluation exceptions on Python3.

Reported by: Sławek Ehlert Owned by: nobody
Component: Utilities Version: dev
Severity: Normal Keywords: SimpleLazyObject lazy evaluation
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Sławek Ehlert)

When running this little script:

from django.utils.functional import *

def a_func():
    raise Exception('naah')

slo = SimpleLazyObject(a_func)

assert not isinstance(slo, type)

I'd expect the assertion to pass. It works as I'd expect it to work on Django 1.11 and Python 2.7. However, it does blow up with the naah exception on both Django 1.11 and 2.2 on Python 3.5 (also on Python 3.6 and 3.7) with the following traceback:

Traceback (most recent call last):
  File "<input>", line 1, in <module>
    assert not isinstance(slo, type)
  File "... /lib/python3.5/site-packages/django/utils/functional.py", line 238, in inner
    self._setup()
  File "... /lib/python3.5/site-packages/django/utils/functional.py", line 386, in _setup
    self._wrapped = self._setupfunc()
  File "<input>", line 2, in a_func
    raise Exception('naah')
Exception: naah

I'm not sure if this is by design, but the behaviour is certainly inconsistent on Py3 vs. Py2.

I found this problem while trying to run our test suite on Python 3. Unittest framework does an obvious if isinstance(obj, type) and issubclass(obj, case.TestCase): check when collecting the test suite (see https://github.com/python/cpython/blob/v3.5.7/Lib/unittest/loader.py#L122), so when we have a module-level SimpleLazyObject (obviously it wraps a different function than the one I've given here, but also a failing one) it gets unexpectedly evaluated and raises an exception, which prevents us from even gathering the tests on Python 3.

I'm not really sure why this is happening. After some brief debugging, it looks like isinstance function on Python 3 is accessing the __class__ attribute (which is wrapped with the new_method_proxy function - see https://github.com/django/django/blob/2.2.2/django/utils/functional.py#L348) causing evaluation of the underlying function, whereas on Python 2.7 isinstance doesn't seem to do that.

Change History (3)

comment:1 by Sławek Ehlert, 5 years ago

Description: modified (diff)

comment:2 by Sławek Ehlert, 5 years ago

To add more info. The behaviour of the example I gave is actually more nuanced than I thought. The function actually does get called on both Python 2 and 3. I've added a simple print("inside a_func") inside that function and it turns out it gets printed in both cases. The exception however occurs only on Python 3 and it seems it's... getting swallowed by isinstance on Python 2 (?). IDK.

comment:3 by Mariusz Felisiak, 5 years ago

Resolution: wontfix
Status: newclosed
Summary: SimpleLazyObject not so lazy on Python3isinstance() call on SimpleLazyObject doesn't swallow evaluation exceptions on Python3.
Triage Stage: UnreviewedAccepted
Version: 2.2master

Thanks for the report. I was able to reproduce this issue, but to be honest Python3 behavior looks more correctly to me. In both cases isinstance() evaluates an objects, so it is not a matter of laziness. We could accept this as a bug for Python2, but first of all this doesn't qualify for a backport and secondly fixing this on Python2 is not what you expect.

Note: See TracTickets for help on using tickets.
Back to Top