Практика Python: есть ли лучший способ проверить параметры конструктора? - PullRequest
5 голосов
/ 19 января 2012

В своих программах на 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")

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

Ответы [ 3 ]

11 голосов
/ 19 января 2012

Когда у меня возникает такой вопрос, я отправляюсь на поиски кода в стандартной библиотеке, после которого я могу смоделировать свой код. 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')

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

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

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

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

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

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


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


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

4 голосов
/ 19 января 2012

Вы можете использовать декоратор проверки типа, например этот рецепт активного состояния или этот другой для python 3 Они позволяют писать код примерно так:

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

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

2 голосов
/ 19 января 2012

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

>>> 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...