Каковы приемлемые варианты использования для выражения `assert` в python? - PullRequest
30 голосов
/ 26 января 2010

Я часто использую утверждение asthon в python для проверки ввода пользователя и быстрого сбоя, если мы находимся в поврежденном состоянии. Мне известно, что assert удаляется, когда python с флагом -o (optimized). Лично я не запускаю ни одно из своих приложений в оптимизированном режиме, но мне кажется, что на всякий случай я должен держаться подальше от утверждения.

Мне кажется, что писать гораздо чище

assert filename.endswith('.jpg')

чем

if not filename.endswith('.jpg'):
    raise RuntimeError

Это допустимый вариант использования assert? Если нет, то каким был бы действительный вариант использования для оператора assert в python?

Ответы [ 8 ]

26 голосов
/ 26 января 2010

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

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

18 голосов
/ 26 января 2010

Если быть изящным невозможно, будь драматичным

Вот правильная версия вашего кода:

if filename.endswith('.jpg'):
    # convert it to the PNG we want
    try:
        filename = convert_jpg_to_png_tmpfile(filename)
    except PNGCreateError:
        # Tell the user their jpg was crap
        print "Your jpg was crap!"

Это действительный случай, ИМХО, когда:

  1. Ошибка полностью, 100% смертельно, и иметь дело с ней было бы слишком мрачно, чтобы понять
  2. Утверждение должно провалиться, только если что-то вызывает изменение законов логики

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

ASSERT == "Это должно никогда не произойти в реальности, и если это произойдет, мы сдаемся"

Конечно, это не то же самое, что

#control should never get here

Но я всегда делаю

#control should never get here
#but i'm not 100% putting my money where my mouth
#is
assert(False)

Таким образом, я получаю хорошую ошибку. В вашем примере я бы использовал if версию и конвертировал файл в jpg!

9 голосов
/ 26 января 2010

Полностью действителен. Утверждение является формальным утверждением о состоянии вашей программы.

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

Другой пример.

def fastExp( a, b ):
    assert isinstance(b,(int,long)), "This algorithm raises to an integer power"
    etc.

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

# Not provable, essential.
assert len(someList) > 0, "Can't work with an empty list."
x = someList[]
while len(x) != 0:
    startingSize= len(x)
    ... some processing ...
    # Provable.  May be Redundant.
    assert len(x) < startingSize, "Design Flaw of the worst kind."

Еще один.

def sqrt( x ):
    # This may not be provable and is essential.
    assert x >= 0, "Can't cope with non-positive numbers"
    ...
    # This is provable and may be redundant.
    assert abs( n*n - x ) < 0.00001 
    return n

Существует множество причин для формальных утверждений.

6 голосов
/ 26 января 2010

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

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

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

4 голосов
/ 26 января 2010

Лично я использую assert для непредвиденных ошибок или вещей, которые вы не ожидаете в реальной жизни. Исключения следует использовать всякий раз, когда речь идет о вводе данных пользователем или файлом, так как они могут быть перехвачены, и вы можете сказать пользователю: «Эй, я ожидал файл .jpg !!»

2 голосов
/ 04 октября 2013

В Python Wiki есть отличное руководство по эффективному использованию Assertion.

Ответы выше не обязательно разъясняют возражение -O при запуске Python. Цитата на странице выше:

Если Python запускается с параметром -O, то утверждения будут удалены и не оценены.

1 голос
/ 04 октября 2013

Ответ С.Лотта самый лучший. Но это было слишком долго, чтобы просто добавить к его комментарию, поэтому я разместил его здесь. В любом случае, именно так я и думаю об assert, в основном это простой способ сделать #ifdef DEBUG.

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

Выполнение этого на цели находится внутри кода:

def sqrt(x):
    if x<0:
        raise ValueError, 'sqrt requires positive numbers'
    root = <do stuff>
    return root

def some_func(x):
    y = float(raw_input('Type a number:'))
    try:
        print 'Square root is %f'%sqrt(y)
    except ValueError:
        # User did not type valid input
        print '%f must be a positive number!'%y

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

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

def sqrt(x):
    if x<=0:
        raise ValueError, 'sqrt requires positive numbers'
    root = <do stuff>
    return root

def some_func(maxx=100000):
    all_sqrts = [sqrt(x) for x in range(maxx)]
    i = sqrt(-1.0)
    return(all_sqrts)

Теперь эта функция будет вызывать sqrt 100 раз. И каждый раз sqrt будет проверять, является ли значение> = 0. Но мы уже знаем , что оно допустимо из-за того, как мы генерируем эти числа - эти дополнительные действительные проверки просто теряют время выполнения. Разве не было бы хорошо избавиться от них? И затем есть один, который бросит ValueError, и поэтому мы поймаем его и поймем, что допустили ошибку. Я пишу свою программу, опираясь на подфункцию, чтобы проверить меня, и поэтому я просто беспокоюсь о восстановлении, когда оно не работает.

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

# This function valid if x > 0
def sqrt(x):
    root = <do stuff>
    return root

def long_function(maxx=100000):
    # This is a valid function call - every x i pass to sqrt is valid
    sqrtlist1 = [sqrt(x) for x in range(maxx)]
    # This one is a program error - calling function with incorrect arguments
    # But one that can't be statically determined
    # It will throw an exception somewhere in the sqrt code above
    i = sqrt(-1.0)

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

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

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

0 голосов
/ 12 ноября 2016

Нижняя линия:

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

Предполагаемый вариант использования, для которого изначально были созданы ASSERT операторы:

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

Это было большой проблемой в старые времена и все еще в средах, предназначенных для производительности. Вот и вся причина его семантики, скажем, в C ++ / C #:

  1. Вырезан в сборке релиза
  2. Немедленно, незаметно и громко завершать программу.

Python, однако, сознательно и преднамеренно жертвует производительностью кода для производительности программиста (хотите верьте, хотите нет, я недавно получил 100-кратное ускорение, портируя некоторый код с Python на Cython - даже не отключая проверку границ!). Код Python работает в «безопасной» среде, в которой вы не можете полностью «разбить» свой процесс (или всю систему) на неразрешимый segfault / BSoD / bricking - худшее, что вы получите, - необработанное исключение с загрузкой отладочной информации. прилагается, все изящно представлено вам в читабельном виде.

  • Что подразумевает, что код среды выполнения и библиотеки должен включать все виды проверок в соответствующие моменты - всегда, независимо от того, является ли это «режимом отладки».

Кроме того, сильное влияние Python на постоянное предоставление исходных текстов (прозрачная компиляция, строки исходных текстов в трассировке, стандартный отладчик, ожидающий их вместе с .pyc, пригодится) очень сильно стирает грань между «разработкой». "и" использовать "(это одна из причин, по которой setuptools 'автономные .egg s создали обратную реакцию - и почему pip всегда устанавливает их без упаковки : если один установлен упакованным , источник более недоступен и проблемы с ним - диагностируемые).

В совокупности эти свойства практически уничтожают любые варианты использования кода «только для отладки».

И, как вы уже догадались, идея о перепрофилировании assert в качестве проверки общего назначения в конечном итоге всплыла .

...