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

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

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

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

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

Ответы [ 7 ]

11 голосов
/ 02 марта 2009

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

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

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

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

10 голосов
/ 02 марта 2009

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

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

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

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

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

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

4 голосов
/ 02 марта 2009

Отметьте все, что вам нравится, вы просто должны быть явными. Следующий пример - конструктор из модуля в стандартной библиотеке - он проверяет 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)
1 голос
/ 03 марта 2009

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

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

1 голос
/ 02 марта 2009

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

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

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

Утверждение твоих аргументов - это своего рода дизайн бедняка по контракту. (Возможно, вы захотите посмотреть Дизайн по контракту; это интересно.)

0 голосов
/ 03 марта 2009

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

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

0 голосов
/ 02 марта 2009

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

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

  • Не сработает ли вообще? Это то, для чего используются блоки try / кроме

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

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

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

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

...