Должен ли я проверять типы аргументов конструктора (и в других местах тоже)?

Python не рекомендует проверять типы. Но во многих случаях это может быть полезно:

  • Проверка аргументов конструктора. например проверка foe Boolean, string, dict и т.д. Если я этого не сделаю и установите члены объекта в аргументы, это вызовет проблемы позже.

  • Проверка аргументов функций.

  • В свойствах. Если кто-то задает неправильное значение или другой тип, я должен быстро ответить.

Ответ 1

Простой ответ Нет, используйте Полиморфизм, Исключения и т.д.

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

  • В свойствах применяется тот же аргумент. Если вы проверяете полученное значение, используйте утверждение, чтобы проверить его диапазон и т.д. Если значение имеет неправильный тип, оно все равно будет терпеть неудачу. Затем обработайте AssertionError.

В Python вы рассматриваете программистов как интеллектуальных существ! Просто хорошо документируйте свой код (сделайте что-то очевидным), поднимите Исключения, где это необходимо, напишите полиморфный код и т.д. Оставьте обработку исключений (где это только подходит)/ошибки в построении для кода клиента.

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

Примечание
В общем, плохие аргументы конструктору - это не то, о чем я беспокоюсь слишком много.

Ответ 2

Ответ почти всегда "нет". Общая идея в Python, Ruby и некоторых других языках называется " Duck Typing. Тебе все равно, что это такое, только как это работает. Другими словами," если все, что вам нужно, - это что-то такое, что вам не нужно проверять, что это на самом деле утка".

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

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

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

Ответ 3

Проверьте все, что вам нравится, просто нужно быть явным. Следующий пример - это конструктор модуль в стандартной библиотеке - он проверяет extrasaction arg:

class DictWriter:
  def __init__(self, f, fieldnames, restval="", extrasaction="raise",
               dialect="excel", *args, **kwds):
      self.fieldnames = fieldnames    # list of keys for the dict
      self.restval = restval          # for writing short dicts
      if extrasaction.lower() not in ("raise", "ignore"):
          raise ValueError, \
                ("extrasaction (%s) must be 'raise' or 'ignore'" %
                 extrasaction)
      self.extrasaction = extrasaction
      self.writer = writer(f, dialect, *args, **kwds)

Ответ 4

Часто бывает хорошо. Проверка на явные типы, вероятно, не так полезна в Python (как говорили другие), но проверка правовых значений может быть хорошей идеей. Причина, по которой это хорошая идея, заключается в том, что программное обеспечение не сможет приблизиться к источнику ошибки (это следует за принципом Fail Fast). Кроме того, проверки действуют как документация для других программистов и вас самих. Еще лучше, это "исполняемая документация", что хорошо, потому что это документация, которая не может лежать.

Быстрый и грязный, но разумный способ проверить ваши аргументы - использовать assert:

def my_sqrt(x):
    assert x >= 0, "must be greater or equal to zero"
    # ...

Утверждение ваших аргументов - это своего рода плохой человек по контракту. (Возможно, вы захотите посмотреть Design by Contract, это интересно.)

Ответ 5

AFAIU, вы хотите удостовериться, что некоторые объекты ведут себя ( "следовать за интерфейсом" ) в более раннее время, чем в случае фактического использования. В вашем примере вы хотите знать, что объекты подходят во время создания экземпляра, а не когда они действительно будут использоваться.

Помня о том, что мы говорим здесь о Python, я не буду предлагать assert (что если python -O или переменная среды PYTHONOPTIMIZE установлена ​​в 1, когда ваша программа работает?) или проверка определенных типов (потому что что излишне ограничивает типы, которые вы можете использовать), но я предлагаю ранние функции тестирования, что-то вроде строк:

def __init__(self, a_number, a_boolean, a_duck, a_sequence):

    self.a_number= a_number + 0

    self.a_boolean= not not a_boolean

    try:
        a_duck.quack
    except AttributeError:
        raise TypeError, "can't use it if it doesn't quack"
    else:
        self.a_duck= a_duck

    try:
        iter(a_sequence)
    except TypeError:
        raise TypeError, "expected an iterable sequence"
    else:
        self.a_sequence= a_sequence

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

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

Ответ 6

"Если я этого не сделаю и установите члены объекта в аргументы, это вызовет проблемы позже".

Пожалуйста, будьте предельно ясны в точном списке "проблем", которые будут вызваны позже.

  • Не будет ли это вообще работать? Для чего нужны try/except blocks.

  • Будет ли он вести себя "странно"? Это действительно редко, и оно ограничено типами "ближнего пролета" и операторами. Стандартным примером является разделение. Если вы ожидали целых чисел, но получили с плавающей точкой, то разделение может не делать то, что вы хотели. Но это исправлено с операторами //, vs./division.

  • Будет ли это просто неправильно, но все же кажется, что оно завершено? Это очень редко, и для этого требуется довольно тщательно продуманный тип, который использовал стандартные имена, но делал нестандартные вещи. Например

    class MyMaliciousList( list ):
        def append( self, value ):
            super( MyMaliciousList, self ).remove( value )
    

Кроме того, трудно заставить вещи "вызывать проблемы позже". Пожалуйста, обновите свой вопрос с помощью конкретных примеров "проблем".

Ответ 7

Как говорит Далке, ответ почти всегда "нет". В Python вам, как правило, не важно, что параметр является определенным типом, а скорее ведет себя как определенный тип. Это называется " Duck Typing". Существует два способа проверить, ведет ли параметр к определенному типу: (1) вы можете использовать его так, как если бы он вел себя так, как вы ожидаете, и генерирует исключение, если оно/нет, или (2) вы можете определить интерфейс, который описывает, как этот тип должен вести себя и проверить соответствие с этим интерфейсом.

zope.interface - моя предпочтительная система интерфейса для Python, но есть и другие. С любым из них вы определяете интерфейс, затем объявляете, что данный тип соответствует этому интерфейсу или определяет адаптер, который превращает ваш тип во что-то, что соответствует этому интерфейсу. Затем вы можете утверждать (или проверять, как пожелаете), что параметры предоставляют (в терминологии zope.interface) этот интерфейс.