__instancecheck__ - перезапись не дает результата - что я делаю не так?

Я пытаюсь сделать так, чтобы мой класс отображался как другой объект, чтобы обойти ленивую проверку типов в пакете, который я использую. В частности, я пытаюсь сделать так, чтобы мой объект отображался как экземпляр другого объекта (в моем случае tuple), хотя на самом деле он даже не является его производным.

Для этого я планирую перезаписать метод __isinstance__, который, согласно docs должны делать именно то, что я хочу. Однако, похоже, я не понял, как именно это сделать, потому что мои попытки не увенчались успехом.

Вот SSCCE, который должен заставить isinstance возвращать False во всех случаях, но этого не происходит.

class FalseInstance(type):
    def __instancecheck__(self, instance):
        return False

class Foo(metaclass=FalseInstance):
    pass

g = Foo()
isinstance(g, Foo)
> True

Что я делаю не так?


person Nearoo    schedule 04.09.2018    source источник
comment
Вы пометили этот python-3.x, но используете способ использования метаклассов python-2.x. Вы неправильно отметили это или это ошибка?   -  person Dunes    schedule 04.09.2018
comment
Я думаю, это была ошибка, я использую 3.x. Что мне делать по-другому?   -  person Nearoo    schedule 04.09.2018
comment
Используйте 1_. Но это все еще не заставляет этот пример работать (из-за уже опубликованного ответа). Однако это сработает для метакласса TrueInstance.   -  person Dunes    schedule 04.09.2018
comment
Как выглядит ваш класс, не являющийся кортежем? И реализует ли он все методы кортежа, которые требуются вашему пакету? Единственное реальное решение, которое я могу придумать для вашей проблемы, - это кортеж подкласса, но переопределить все его методы.   -  person Dunes    schedule 04.09.2018
comment
@Dunes Спасибо, я поправил вопрос. Теперь я также понимаю, почему код, который я предоставил, не работает. Однако, если я сейчас переверну пример и изменю его так, чтобы __isinstance__ всегда возвращал True, он все равно не будет работать так, как я ожидал: isinstance(g, int) по-прежнему возвращает False. В чем проблема?   -  person Nearoo    schedule 04.09.2018
comment
@Dunes На ваш второй вопрос - tuples неизменяемы и поэтому немного громоздки в использовании, верно? Я еще не знаю, какие аспекты кортежа необходимы для работы пакета, но я подумал, что добавлю их всякий раз, когда возникают исключения...   -  person Nearoo    schedule 04.09.2018


Ответы (2)


Помимо проблем с __metaclass__ и быстрым путем для точного совпадения типов, __instancecheck__ работает в направлении, противоположном тому, что вы пытаетесь сделать. __instancecheck__ класса проверяет, считаются ли другие объекты виртуальными экземплярами этого класса, а не считаются ли экземпляры этого класса виртуальными экземплярами других классов.

Если вы хотите, чтобы ваши объекты лгали о своем типе в проверках isinstance (вы действительно не должны этого делать), то способ сделать это — лгать о __class__, а не реализовывать __instancecheck__.

class BadIdea(object):
    @property
    def __class__(self):
        return tuple

print(isinstance(BadIdea(), tuple)) # prints True

Между прочим, если вы хотите получить фактический тип объекта, используйте type, а не проверяйте __class__ или isinstance.

person user2357112 supports Monica    schedule 04.09.2018
comment
Ээээ. Это действительно ужасно. Интересно, это деталь реализации, которую isinstance одурачил этим. - person jsbueno; 05.09.2018
comment
@jsbueno: я не уверен. На него опирается стандартная библиотека, особенно в unittest. .mock, но в документах для isinstance и __class__ об этом не упоминается. Об этом упоминается в C API, но C API — это деталь реализации. - person user2357112 supports Monica; 05.09.2018
comment
Это прямо противоречит описанию PEP, так что, возможно, это ошибка. Но да, быстрый путь проверяет прямые типы перед вызовом __instancecheck__. - person Martijn Pieters; 03.03.2019
comment
См. этот отчет об ошибке. В настоящее время нет единого мнения о том, является ли это ошибкой или проблема с документацией. - person Martijn Pieters; 03.03.2019

Если вы добавите печать внутри FalseInstance.__instancecheck__, вы увидите, что она даже не вызывается. Однако, если вы вызовете isinstance('str', Foo), вы увидите, что FalseInstance.__instancecheck__ вызывается.

Это связано с оптимизацией реализации isinstance, которая немедленно возвращает True, если type(obj) == given_class:

int
PyObject_IsInstance(PyObject *inst, PyObject *cls)
{
    _Py_IDENTIFIER(__instancecheck__);
    PyObject *checker;

    /* Quick test for an exact match */
    if (Py_TYPE(inst) == (PyTypeObject *)cls)
        return 1;
    .
    .
    .
}

Из исходного кода Python

person DeepSpace    schedule 04.09.2018
comment
Действительно? Какой смысл даже предоставлять isinstance, если не изменить именно это поведение? - person Nearoo; 04.09.2018
comment
похоже, что в python 2.7 это сработало...: stackoverflow.com/a/13135792/8179099 - person Moshe Slavin; 04.09.2018
comment
@Nearoo: __instancecheck__ в первую очередь предназначен для того, чтобы пройти больше isinstance проверок, а не меньше. Также нет __isinstance__. - person user2357112 supports Monica; 04.09.2018
comment
Однако, если я сейчас переверну пример и изменю его так, чтобы __isinstance__ всегда возвращал True, он все равно не будет работать так, как я ожидал: isinstance(g, int) теперь возвращает False. В чем проблема? - person Nearoo; 04.09.2018
comment
@Nearoo: Нет __isinstance__. Есть только __instancecheck__, который работает в направлении, противоположном тому, что вы пытаетесь сделать. - person user2357112 supports Monica; 04.09.2018
comment
Извините, я имел в виду __instancecheck__. Но вы правы, я неправильно понял. Это проясняет ситуацию, большое спасибо!! - person Nearoo; 04.09.2018