Подтвердить что ты не робот

Как обращаться с "утиным набором" в Python?

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

Один из способов - заставить людей подклассифицировать класс "интерфейс". Для меня это больше похоже на Java, чем на Python, и использование issubclass в каждом методе тоже не очень заманчиво.

Мой предпочтительный способ - добросовестно использовать объект, но это приведет к повышению AttributeErrors. Я могу обернуть каждый опасный вызов в блок try/except. Это тоже кажется громоздким:

def foo(obj):
    ...
    # it should be able to sleep
    try:
        obj.sleep()
    except AttributeError:
        # handle error
    ...
    # it should be able to wag it tail
    try:
        obj.wag_tail()
    except AttributeError:
        # handle this error as well

Должен ли я просто пропустить обработку ошибок и ожидать, что люди будут использовать объекты только с помощью необходимых методов? Если я делаю что-то глупое, как [x**2 for x in 1234], я действительно получаю TypeError, а не AttributeError (ints не являются итерабельными), поэтому там должна быть какая-то проверка типа где-то - что делать, если я хочу сделать то же самое?

Этот вопрос будет отчасти открытым, но каков наилучший способ справиться с вышеуказанной проблемой чистым способом? Существуют ли какие-либо установленные передовые методы? Как, например, реализована итеративная "проверка типов" выше?

Изменить

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

>>> ['a', 1].sort()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

Я хочу, чтобы моя библиотека была как можно более полезной.

4b9b3361

Ответ 1

Если вашему коду требуется определенный интерфейс, и пользователь передает объект без этого интерфейса, то в девяти случаях из десяти, неуместно ловить исключение. В большинстве случаев AttributeError не только разумно, но и ожидаемо, когда речь идет о несоответствиях интерфейса.

Иногда может быть уместно поймать AttributeError по одной из двух причин. Либо вы хотите, чтобы какой-либо аспект интерфейса был дополнительным, либо вы хотите создать более конкретное исключение, возможно, подкласс исключения для конкретного пакета. Конечно, вы никогда не должны препятствовать тому, чтобы исключение было брошено, если вы честно не справились с ошибкой и любыми последствиями.

Итак, мне кажется, что ответ на этот вопрос должен быть проблемным и специфичным для домена. Это принципиально вопрос о том, следует ли использовать объект Cow вместо объекта Duck. Если это так, и вы обрабатываете любой необходимый интерфейс, то это хорошо. С другой стороны, нет причин явно проверять, прошел ли кто-нибудь объект Frog, если это не приведет к катастрофическому сбою (т.е. Что-то намного хуже, чем трассировка стека).

Тем не менее, всегда полезно документировать ваш интерфейс - что для чего нужны докстры (между прочим). Когда вы думаете об этом, гораздо эффективнее всего распространять общую ошибку для большинства случаев и сообщать пользователям правильный способ сделать это в docstring, чем пытаться предвидеть все возможные ошибки, которые пользователь может создать, и создать собственное сообщение об ошибке.

Последнее предостережение - возможно, вы думаете о UI здесь - я думаю, что это другая история. Хорошо проверить ввод, который конечный пользователь дает вам убедиться, что он не является злонамеренным или ужасно искаженным, и предоставляет полезную обратную связь вместо трассировки стека. Но для библиотек или подобных вещей вам действительно нужно доверять программисту, использующему ваш код, использовать его разумно и с уважением и понимать ошибки, которые генерирует Python.

Ответ 2

Я не профессионал python, но считаю, что , если, вы не можете попробовать альтернативу, когда параметр не реализует данный метод, вы не предотвращаете исключение исключений. Пусть вызывающий абонент обрабатывает эти исключения. Таким образом, вы будете избегать проблем со стороны разработчиков.

Как я прочитал в Clean Code, если вы хотите искать элемент в коллекции, не проверяйте свои параметры с помощью ìssubclass (списка), но предпочитаем называть getattr(l, "__contains__"). Это даст кому-то, кто использует ваш код возможность передать параметр, который не является списком, но который имеет метод __contains__, и все должно работать одинаково хорошо.

Итак, я думаю, что вы должны кодировать абстрактный, общий способ, накладывающий как несколько ограничений, как вы можете. Для этого вам нужно сделать наименьшее количество допущений. Однако, когда вы сталкиваетесь с чем-то, с которым не можете справиться, создать исключение, и пусть программист знает, какую ошибку он совершил!

Ответ 3

Если вы просто хотите, чтобы нереализованные методы ничего не делали, вы можете попробовать что-то вроде этого, а не многострочную конструкцию try/except:

getattr(obj, "sleep", lambda: None)()

Однако это не обязательно очевидно как вызов функции, поэтому возможно:

hasattr(obj, "sleep") and obj.sleep()

или если вы хотите быть немного увереннее, прежде чем называть то, что на самом деле можно назвать:

hasattr(obj, "sleep") and callable(obj.sleep) and obj.sleep()

Этот шаблон "look-before-you-leap" обычно не является предпочтительным способом сделать это в Python, но он отлично читается и подходит для одной строки.

Другим вариантом, конечно, является абстракция try/except в отдельной функции.

Ответ 4

Хороший вопрос, и вполне открытый. Я считаю, что типичный стиль Python - это не проверять, либо с помощью isinstance, либо вылавливать отдельные исключения. Cerainly, использование isinstance - довольно плохой стиль, так как он побеждает всю точку утиного ввода (хотя использование isinstance на примитивах может быть в порядке - обязательно проверьте как int, так и long для целых входов и проверьте basestring для строк (base класс str и unicode). Если вы проверите, вы можете поднять TypeError.

Не проверяется, как правило, нормально, поскольку в любом случае обычно возникает либо TypeError, либо AttributeError, что вам и нужно. (Хотя это может задержать те ошибки, которые затрудняет отладку клиентского кода).

Причина, по которой вы видите TypeErrors, заключается в том, что примитивный код повышает ее эффективность, потому что она имеет значение isinstance. Цикл for жестко закодирован, чтобы поднять TypeError, если что-то не итерируется.

Ответ 5

Прежде всего, код в вашем вопросе не идеален:

try:
    obj.wag_tail()
except AttributeError:
    ...

Вы не знаете, находится ли AttributeError от поиска wag_tail или произошло ли это внутри функции. То, что вы пытаетесь сделать, это:

try:
    f = getattr(obj, 'wag_tail')
except AttributeError:
    ...
finally:
    f()

Edit: kindall справедливо указывает, что, если вы собираетесь это проверить, вы также должны проверить, что f является вызываемым.

В общем, это не Pythonic. Просто позвоните и включите фильтр исключений, и трассировка стека достаточно информативна, чтобы исправить проблему. Я думаю, вы должны спросить себя, являются ли ваши повторные исключения полезными, чтобы оправдать весь этот код проверки ошибок.

Случай сортировки списка - отличный пример.

  • Сортировка списка очень распространена,
  • передача неупорядоченных типов происходит для значительной части из них, а
  • throw AttributeError в этом случае очень запутан.

Если эти три критерия применимы к вашей проблеме (особенно третья), я согласен с созданием репутационного репликатора.

Вы должны балансировать с тем, что выброс этих довольно ошибок заставит ваш код усложнять чтение, что статистически означает больше ошибок в вашем коде. Это вопрос балансирования плюсов и минусов.

Если вам нужно проверить поведение (например, __real__ и __contains__), не забудьте использовать базовые классы Python, найденные в collections, io и numbers.