Имеет ли смысл этот подход к утиному набору Python, смешанному с isinstance()?

Допустим, у нас есть следующие классы:

class Duck(object):
    pass

class OldFashionedDuck(Organism, Duck):
    def look(self):
        self.display_biological_appearance()
    def walk(self):
        self.keep_balance_on_two_feet()
    def quack(self):
        self.make_noise_with_lungs("Quack!")

class ArtificialDuck(Robot, Duck):
    def look(self):
        self.display_imitation_biological_appearance()
    def walk(self):
        self.engage_leg_clockwork()
    def quack(self):
        self.play_sound("quack.au")

В этом примере OldFashionedDuck и ArtificialDuck не имеют общей реализации, но по своей конструкции они оба будут возвращать True для isinstance(..., Duck).

Это не идеально, но я подумал, что это может помочь уважать как утиную типизацию, так и (через пустое наследование миксинов) разрешать isinstance(). По сути, он предлагает контракт на соответствие интерфейсу, поэтому на самом деле он вызывает isinstance() не на основе класса, который выполняет всю работу, а на основе интерфейса, на который может подписаться любой.

Я видел статьи, основанные на том, что «isinstance() считается вредным», потому что это нарушает утиную печать. Тем не менее, я, по крайней мере, как программист, хотел бы знать, если не обязательно, откуда объект получает функцию, но реализует ли он интерфейс.

Полезен ли этот подход, и если да, то можно ли его улучшить?


person Christos Hayward    schedule 21.01.2013    source источник
comment
Непонятно, как и почему вы хотите использовать isinstance(). Это затрудняет решение вопроса о том, правильно ли это делать.   -  person Marcelo Cantos    schedule 21.01.2013
comment
Подойдут ли для этого абстрактные базовые классы?   -  person    schedule 21.01.2013


Ответы (2)


Я видел статьи, основанные на том, что «isinstance() считается вредным», потому что это нарушает утиную печать. Тем не менее, я, по крайней мере, как программист, хотел бы знать, если не обязательно, откуда объект получает функцию, но реализует ли он интерфейс.

Я думаю, вы упускаете суть.

Когда мы говорим о «утиной печати», на самом деле мы имеем в виду неформализацию наших интерфейсов. Таким образом, то, что вы пытаетесь сделать, сводится к попытке ответить на вопрос «как я могу формализовать свой интерфейс, не формализуя при этом свой интерфейс?».

Мы ожидаем, что объект, который нам дали, будет реализовывать интерфейс — тот, который мы описали, не сделав базовый класс, а написав кучу документации и описав поведение (а если мы чувствуем себя особенно резво, настроив какой-нибудь набора тестов) - потому что мы сказали, что это то, что мы ожидаем (опять же в нашей документации). Мы проверяем, что объект реализует интерфейс, пытаясь использовать его так, как если бы он это делал, и рассматривая любые возникающие ошибки как ответственность вызывающей стороны. Вызов кода, который дает нам неправильный объект, непослушен, и именно здесь необходимо исправить ошибку. (Опять же, тесты помогают нам отслеживать эти вещи.)

Короче говоря, тестирование isinstance(this_fuzzball_that_was_handed_to_me, Duck) не очень помогает:

  • Он может пройти проверку isinstance, но реализовать методы таким образом, который не соответствует нашим ожиданиям (или, скажем, return NotImplemented). Здесь подойдет только реальное тестирование.

  • Он может пройти проверку, но на самом деле полностью не реализовать один или несколько методов; в конце концов, база Duck не содержит никаких реализаций, и у Python нет причин проверять их в производном классе.

  • Возможно, что еще более важно, он может не пройти проверку, несмотря на то, что его вполне можно использовать в качестве пушистого комка. Может быть, это какой-то несвязанный объект, который имеет функции quack, walk и look, вручную прикрепленные к нему как атрибуты (в отличие от атрибутов его класса, которые становятся методами при поиске).

Итак, "не делай этого", говоришь ты. Но теперь вы делаете больше работы для всех; если клиенты не всегда соглашаются, то для кода, использующего утку, бесполезно и опасно выполнять проверку. А между тем, что вы получаете?

Это связано с принципом EAFP: не пытайтесь понять, является ли что-то уткой, глядя на это; выясните, является ли это уткой, рассматривая ее как утку и разбираясь с кровавым месивом, если это не так.


Но если вас не волнует философия утиной типизации, и вам абсолютно необходимо придать вещам хоть какое-то подобие строгости, вас может заинтересовать модуль стандартной библиотеки abc.

person Karl Knechtel    schedule 21.01.2013

Хоть я и не хочу переоценивать этот термин, но: Ваш подход не питонический. Печатайте утиным шрифтом или не делайте этого.

Если вы хотите быть уверены, что ваша реализация «интерфейса» реализует все, что должна: протестируйте ее!

Для небольших проектов легко запомнить, что вам нужно. А можно просто попробовать.

Я согласен, что для более крупных проектов или даже сотрудничества в командах лучше убедиться, что у вашего типа есть все, что ему нужно. В таком сценарии вам определенно следует использовать модульные тесты, чтобы убедиться, что ваш тип завершен. Даже без дактипинга вам нужны тесты, так что, вероятно, вам не понадобятся дополнительные тесты.

Гвидо ван Россум поделился некоторыми интересными мыслями об утином наборе в этом выступлении. Это так вдохновляет, и определенно стоит посмотреть.

person Thorsten Kranz    schedule 21.01.2013