Как справиться с "уткой" в Python? - PullRequest
25 голосов
/ 06 июля 2011

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

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

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

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

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

Этот вопрос будет своего рода открытым, но каков наилучший способ решить вышеуказанную проблему чистым способом? Есть ли признанные лучшие практики? Как, например, реализована итеративная «проверка типов» выше?

Редактировать

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

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

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

Ответы [ 5 ]

17 голосов
/ 06 июля 2011

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

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

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

12 голосов
/ 06 июля 2011

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

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

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

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

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

7 голосов
/ 06 июля 2011

Если вы просто хотите, чтобы нереализованные методы ничего не делали, вы можете попробовать что-то вроде этого, а не многострочную 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 вотдельная функция.

5 голосов
/ 06 июля 2011

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

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

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

2 голосов
/ 06 июля 2011

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

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

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

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

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

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

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

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

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

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

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

...