2012-01-19 5 views
5

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

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     try: 
      self.num_threads= int(num_threads) 
      if self.num_threads <= 0: 
       raise ValueError() 
     except ValueError: 
      raise ValueError("invalid thread count") 

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

+2

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

ответ

11

Когда у меня возникает такой вопрос, я отправляюсь на охоту в стандартную библиотеку для кода, после которого я смогу смоделировать мой код. multiprocessing/pool.py имеет класс несколько близко к вашему:

class Pool(object): 

    def __init__(self, processes=None, initializer=None, initargs=(), 
       maxtasksperchild=None): 
     ... 
     if processes is None: 
      try: 
       processes = cpu_count() 
      except NotImplementedError: 
       processes = 1 
     if processes < 1: 
      raise ValueError("Number of processes must be at least 1") 

     if initializer is not None and not hasattr(initializer, '__call__'): 
      raise TypeError('initializer must be a callable') 

Обратите внимание, что это не говорит

processes = int(processes) 

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

Он делает рейз ValueError, если processes < 1, и он проверяет, что initializer, при условии, может быть выведено.

Итак, если мы возьмем multiprocessing.Pool как модель, ваш класс должен выглядеть следующим образом:

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     self.num_threads = num_threads 
     if self.num_threads < 1: 
      raise ValueError('Number of threads must be at least 1') 

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

Я думаю, что проверка обычно упреждающие типа идет вразрез с философией дизайна Пайтона (dynamic-, уток-типирование).

Утка набирает возможности программистов на Python для отличной выразительной мощности, и быстрой разработки кода, но (некоторые могут сказать) является опасным, поскольку он не делает попыток поймать ошибки типа .

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

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

Вам просто нужно решить для себя.


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


PPS. Существуют декораторы для проверки типа (Python 2) и (Python 3). Это отделит код проверки типов от остальной части функции и позволит вам более легко отключить проверку типов в будущем, если вы этого захотите.

+0

Разве этот подход не может оказаться непредсказуемым для некоторых условий? Например, передача строки не будет восприниматься как ошибка вообще (так как они никогда не будут сравниваться как '<1'). Мое намерение с помощью превентивных проверок заключается в том, чтобы гарантировать некоторую согласованность в состоянии объекта, обнаруживая ошибки как можно раньше, вместо того, чтобы позже было вызвано случайное связанное исключение. Является ли это «более питоническим», чтобы просто позволить вещам быть и потерпеть неудачу в более позднее время? – danielkza

4

Вы можете использовать декоративный декоратор, такой как this activestate recipe или this other one for python 3. Они позволяют писать код что-то вроде этого:

@require("x", int, float) 
@require("y", float) 
def foo(x, y): 
    return x+y 

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

+2

Эти рецепты декоратора хороши (и их легко отключить, если они вам больше не нужны). –

+0

Это, кажется, самое элегантное решение, я обязательно проверю его. На несвязанной заметке активирует ли activestate.com Firefox (10.0) для кого-то еще? – danielkza

2

Это субъективно, но вот контраргумент:

>>> obj = ClassWithThreads("potato") 
ValueError: invalid thread count 

Подождите, что? Это должно быть TypeError. Я бы сделал это:

if not isinstance(num_threads, int): 
    raise TypeError("num_threads must be an integer") 
if num_threads <= 0: 
    raise ValueError("num_threads must be positive") 

Хорошо, поэтому это нарушает принципы «утиной печати». Но я бы не использовал утиную типизацию для примитивных объектов, таких как int.

Смежные вопросы