Python Quack Quack

Сегодня на работе натолкнулись на проблему: на продакшне стала падать страница создания счетов. Трейсбек указывал на строку:

File "python2.6/site-packages/django/db/models/sql/where.py", line 57, in add
    value = list(value)

Код же был прост и элементарен:

Foo.objects.filter(x=y)

Казалось бы, зачем приводить здесь что-то к листу? Вся магия оказалась в проверке строкой выше, в том же файле, в models/sql/where.py:

if isinstance(value, collections.Iterator):
    ...

Но почему isinstance возвращает True? y не является итератором, это просто экземпляр модели!

Вся хитрость оказалась в том, как работает isinstance – это живое воплощение сути дактайпинга. Да-да! Объект y имел определённый метод next, по этой причине проверка проходила положительно, что не очень хорошо. Однако частично правильно. В Python 2.6:

>>> class test(object):
...     def next(self):
...             pass
... 
>>> from collections import Iterator
>>> isinstance(test(), Iterator)
True

В Python 2.7 ошибку воспроизвести не удавалось и понятно почему:

>>> class test(object):
...     def next(self):
...             pass
... 
>>> from collections import Iterator
>>> isinstance(test(), Iterator)
False

В 2.7 в “требования” был добавлен метод __iter__.

>>> class test(object):
...     def next(self):
...             pass
...     def __iter__(self):
...             pass
... 
>>> isinstance(test(), Iterator)
True

Однако, это далеко не единственный подводный камень при использовании isinstance. Если есть необходимость гарантированно знать какого типа объект, то следует использвоать сравнение типов используя функцию type.

Written on March 11, 2013