Если утка-типизация в Python, вы должны проверить isinstance?

У вас есть класс Python, который нуждается в экзамене равным. Python должен использовать утиную печать, но лучше ли (лучше/точнее) включать или исключать тест isinstance в функции eq? Например:

class Trout(object):
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return isinstance(other, Trout) and self.value == other.value

Ответ 1

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

EDIT (спасибо за напоминание, Свен Марнах):

Чтобы сделать его резервным, вы можете вернуть SingleImplemented, как в этом примере:

class Trout(object):
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, Trout):
            return self.value == other.value
        else:
            return NotImplemented

Предположим, что a RainbowTrout умеет сравнивать себя с Trout или с другим RainbowTrout, но Trout знает, как сравнивать себя с Trout. В этом примере, если вы протестируете mytrout == myrainbowtrout, Python сначала вызовет mytrout.__eq__(myrainbowtrout), заметьте, что он терпит неудачу, а затем вызовите myrainbowtrout.__eq__(mytrout), который успешно завершен.

Ответ 2

Использование isintsance() обычно отлично в методах __eq__(). Вы не должны возвращать False сразу, если проверка isinstance() не выполняется, хотя - лучше вернуть NotImplemented, чтобы дать other.__eq__() шанс выполнить:

def __eq__(self, other):
    if isinstance(other, Trout):
        return self.x == other.x
    return NotImplemented

Это станет особенно важным в иерархиях классов, где более чем один класс определяет __eq__():

class A(object):
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        if isinstance(other, A):
            return self.x == other.x
        return NotImplemented
class B(A):
    def __init__(self, x, y):
        A.__init__(self, x)
        self.y = y
    def __eq__(self, other):
        if isinstance(other, B):
            return self.x, self.y == other.x, other.y
        return NotImplemented

Если вы немедленно вернете False, как и в исходном коде, вы потеряете симметрию между A(3) == B(3, 4) и B(3, 4) == A(3).

Ответ 3

Принцип "утиной печати" заключается в том, что вам все равно, что other, если он имеет атрибут value. Поэтому, если ваши атрибуты не разделяют имена с противоречивой семантикой, я бы предложил сделать это следующим образом:

def __eq__(self, other):
    try:
        return self.value == other.value
    except AttributeError:
        return False # or whatever

(В качестве альтернативы вы можете проверить, имеет ли other атрибут value, но "проще просить прощения, чем получить разрешение" )