Найкраща практика утвердження Python


483
  1. Чи є проблема продуктивності або обслуговування коду з використанням assertяк частини стандартного коду замість того, щоб використовувати його лише для налагодження?

    Є

    assert x >= 0, 'x is less than zero'

    краще чи гірше, ніж

    if x < 0:
        raise Exception, 'x is less than zero'
  2. Крім того, чи можна встановити ділове правило, як if x < 0 raise errorте, що завжди перевіряється без try/except/finallyцього, якщо в будь-який час у коді xменше 0 виникає помилка, як, наприклад, якщо ви встановите assert x < 0на початку функції, в будь-якому місці функції де xстає менше, ніж 0, виникає виняток?



29
-O і -OO параметри пітона позбавлять ваших тверджень. Це повинно керувати вашими думками про те, для чого це добре.
Пітер Лада

4
Посилання Томаса Зеліньського зірвано, зараз це: mail.python.org/pipermail/python-list/2013-November/660568.html . Я майже впевнений, що в pipermail є нестабільна функція ідентифікатора, я знайшов інші посилання зсередини тієї ж піпермейлі, що вказують на той самий URL з тим же наміром.
quodlibetor

3
У випадку, якщо mail.python.org/pipermail/python-list/2013-November/660568.html переміститься знову, він архівується на archive.is/5GfiG . Заголовок публікації - "Коли потрібно використовувати стверджувати" і є відмінним дописом (стаття дійсно) про кращі практики для Python assert.
клак

Відповіді:


144

Щоб мати можливість автоматично видавати помилку, коли х стає менше нуля протягом усієї функції. Можна використовувати дескриптори класів . Ось приклад:

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero

10
Хоча властивості реалізовані як дескриптори, я б не називав це прикладом їх використання. Це більше приклад властивостей самих по собі: docs.python.org/library/functions.html#property
Джейсон Бейкер

3
Властивості слід використовувати в MyClass при встановленні x. Це рішення занадто загальне.

113
Дуже приємна відповідь, як це, але це стосується НІЧОГО питання, чи не можемо ми позначити відповідь Діестана чи Джона Мей як дійсну відповідь?
Vajk Hermecz

4
Схоже, це не відповідає назви питання. Крім того, це погана альтернатива особливості властивості класу Python.
Dooms101

10
@VajkHermecz: Насправді, якщо ви перечитали питання, це два питання в одному. Люди, які дивляться лише на заголовок, знайомі лише з першим запитанням, на яке ця відповідь не відповідає. Ця відповідь фактично містить відповідь на друге питання.
ArtOfWarfare

742

Затвердження слід використовувати для перевірки умов, які ніколи не повинні відбуватися . Метою є ранній збій у випадку корумпованого стану програми.

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


Наприклад, якщо ви пишете функцію зчитування з файлу конфігурації в файл dict, неправильне форматування у файлі повинно ConfigurationSyntaxErrorпризвести до " a" , тоді як ви можете, assertщо ви не збираєтеся повертатися None.


У вашому прикладі, якщо xзначення встановлено через користувальницький інтерфейс або із зовнішнього джерела, найкращим є виняток.

Якщо xв тій же програмі встановлений лише ваш власний код, перейдіть із твердженням.


126
Це правильний спосіб використання тверджень. Їх не слід використовувати для контролю потоку програми.
Thane Brimhall

41
+1 за останній абзац - хоча ви повинні явно згадати , що assertмістить неявне if __debug__і може бути оптимізований геть - в якості відповіді Джона КІЗ в штатах
Tobias Kienzler

3
Читаючи свою відповідь, я думаю, що ви, мабуть, не мали на увазі умови, які ніколи не повинні мати на увазі як правило, а скоріше, це мета - аварійне завершення роботи у випадку корумпованого стану програми, що зазвичай збігається з умовою, якої ви не очікуєте щоб коли-небудь трапилося .
Bentley4

10
стверджувати слід лише для вирішення проблем, коли не відомо жодне відновлення; майже завжди кодові помилки (непогані входи). коли спрацьовує затвердження, це повинно означати, що програма перебуває в стані, який може бути небезпечним для продовження, оскільки може почати розмову з мережею або запис на диск. надійний код переходить "атомно" з дійсного стану в дійсний стан за умови поганого (або зловмисного) введення. верхній рівень кожної нитки повинен мати бар'єрний розлом. бар'єри несправностей, які споживають вхід із зовнішнього світу, як правило, не спрацьовують лише за одну ітерацію бар'єру (час / спробу), відкат / помилка входу.
Роб

10
"Асорти слід використовувати для тестування умов, які ніколи не повинні відбуватися". Так. І сенс другого "повинен" такий: Якщо це трапляється, програмний код невірний.
Lutz Prechelt

362

Оператори "assert" видаляються, коли компіляція оптимізована . Отже, так, є як продуктивні, так і функціональні відмінності.

Поточний генератор коду не видає код для заяви твердження, коли оптимізація вимагається під час компіляції. - Python 2 Docs Python 3 Docs

Якщо ви використовуєте assertдля реалізації функціональності додатків, а потім оптимізуєте розгортання до виробництва, вас постраждають дефекти "but-it-works-in-dev".

Див. PYTHONOPTIMIZE та -O -OO


26
Оце Так! Супер важлива примітка, яка є! Я планував використовувати твердження для перевірки декількох речей, які ніколи не повинні провалюватися, чия невдача вказує на те, що хтось дуже ретельно маніпулює моїми даними, які вони надсилають, намагаючись отримати доступ до даних, до яких вони не повинні мати доступ. Це не спрацювало б, але я хочу швидко закрити їхню спробу з твердженням, так що оптимізоване відведення у виробництві перемогло б мету. Я думаю , я просто замість цього. О, я щойно знайшов влучне ім'я з підкласами в ! Ідеально! raiseExceptionSuspiciousOperation ExceptionDjango
ArtOfWarfare

До речі @ArtOfWarfare, якщо ви працюєте banditна своєму коді, він попередить вас про це.
Нагев

132

Чотири цілі assert

Припустимо, ви працюєте над 200 000 рядків коду з чотирма колегами Алісою, Берндом, Карлом та Дафною. Вони називають ваш код, ви називаєте їх код.

Потім assertмає чотири ролі :

  1. Повідомте Алісу, Бернда, Карла та Дафну про те, що очікує ваш код.
    Припустимо, у вас є метод, який обробляє список кортежів, і логіка програми може порушитися, якщо ці кортежі не є незмінними:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Це надійніше, ніж еквівалентна інформація в документації, і набагато простіше в обслуговуванні.

  2. Повідомте комп’ютеру, що очікує ваш код.
    assertзастосовує належну поведінку від абонентів вашого коду. Якщо ваш код називає код Аліса, а Бернд називає вашим, то без того assert, якщо програма виходить з ладу в коді Alices, Бернд може припустити, що це винна Аліса. його. Багато роботи втрачено.
    З твердженнями, хто не отримає дзвінок неправильно, швидко зможе побачити, що це була їхня вина, а не ваша. Аліса, Бернд, і ти всі отримуєш користь. Економить величезну кількість часу.

  3. Повідомте читачів свого коду (включаючи себе) того, що ваш код досяг у певний момент.
    Припустимо, у вас є список записів, і кожен з них може бути чистим (що добре), або він може бути сморшем, тралею, згорнутим або мерехтливим (які всі неприйнятні). Якщо це smorsh, воно повинно бути розбитим; якщо це трала, вона повинна бути бадуйована; якщо це gullup, його треба рисувати (а потім, можливо, також крокувати); якщо воно моргнуло, воно повинно знову моргати, крім четверга. Ви отримуєте ідею: це складні речі. Але кінцевим результатом є (або повинно бути), що всі записи чисті. Правильна річ (TM), що потрібно зробити, - це підсумувати ефект вашої очисної петлі як

    assert(all(entry.isClean() for entry in mylist))

    Ці твердження рятують від головного болю для всіх, хто намагається зрозуміти, що саме це чудова петля. І найчастіший з цих людей, швидше за все, буде сам.

  4. Поінформуйте комп’ютер про те, що ваш код досяг у певний момент.
    Якщо ви коли-небудь забудете assertскоротити запис, який потребує його після риси, це збереже ваш день і уникне, щоб ваш код зламав дорогу Дафну набагато пізніше.

На мій погляд, assertдві цілі документації (1 і 3) та гарантії (2 і 4) однаково цінні.
Інформування людей може бути навіть більш цінним, ніж інформування за комп’ютером, оскільки це може запобігти самим помилкам, котрі мають на assertметі зловити (у випадку 1) та безлічі наступних помилок у будь-якому випадку.


34
5. assert isinstance () допоможе PyCharm (python IDE) знати тип змінної, вона використовується для автозаповнення.
Cjkjvfnby

1
Затверджує припущення про самодокументування коду щодо того, що є істинним у поточний час виконання. Це коментар припущення, який перевіряється.
pyj

9
Щодо 2 і 4: Ви повинні бути дуже обережними, щоб ваші твердження не були надто суворими. Інакше самі твердження можуть бути єдиним, що дозволяє використовувати вашу програму в більш загальних умовах. Особливо стверджують, що типи суперечать типізації качок пітона.
zwirbeltier

9
@Cjkjvfnby Будьте уважні щодо надмірного використання isin substance (), як описано в цьому записі в блозі: " isinstance () вважається шкідливою ". Тепер ви можете використовувати docstrings, щоб вказати типи в Pycharm.
binarysubstrate

2
Використання тверджень в одному із способів забезпечення контракту. Більше інформації про Design by Contract en.wikipedia.org/wiki/Design_by_contract
Лешек Зарна

22

Окрім інших відповідей, стверджують, що вони кидають винятки, але лише AssertionErrors. З утилітарної точки зору, твердження не підходять для того, коли вам потрібен тонкий контроль над зерном, за якими винятками ви потрапляєте.


3
Правильно. Здавалося б, нерозумно сприймати винятки щодо помилок тверджень у виклику.
Раффі Хатчадуріан

Дуже хороший момент. Нюанс, який легко можна не помітити, дивлячись на оригінальні питання з макрорівні. Навіть якби не проблема з відхиленням тверджень при оптимізації, втрата конкретних деталей про помилку зробила б налагодження набагато складнішим. Ура, аути!
cfwschmidt

Вашу відповідь можна прочитати так, ніби ви хочете зловити AssertionErrors, коли все гаразд, коли грубозернистий. Насправді ви не повинні їх ловити.
Томаш

19

Єдине, що насправді не так у цьому підході, це те, що важко зробити дуже описовий виняток, використовуючи заяви твердження. Якщо ви шукаєте простіший синтаксис, пам’ятайте, що ви також можете зробити щось подібне:

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

Ще одна проблема полягає в тому, що використання ASDAT для звичайної перевірки стану полягає в тому, що це ускладнює відключення налагоджувальних тверджень за допомогою прапора -O.


24
Ви можете додати повідомлення про помилку до твердження. Це другий параметр. Це зробить це описовим.
Раффі Хатчадуріан

10

Англійська мова тут стверджується вживається у значенні присягати , стверджувати , ухилятися . Це не означає "перевірити" або "повинно бути" . Це означає, що ви як кодер робите тут присягу :

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42

Якщо код правильний, якщо заборонити події з одноподіями , збої в апаратному забезпеченні тощо, жодне твердження ніколи не вийде з ладу . Ось чому поведінка програми до кінцевого споживача не повинна впливати. Тим більше, ствердження не може провалитися навіть у виняткових програмних умовах . Це просто ніколи не буває. Якщо це трапиться, програміст повинен зациклюватися на цьому.


8

Як було сказано раніше, твердження слід використовувати, коли ваш код НЕ БУДЕ досягати точки, тобто помилка там. Напевно, найбільш корисною причиною, яку я можу побачити, як використовувати твердження, є інваріантність / попередня / постсумовна умова. Це те, що повинно бути правдивим на початку або в кінці кожної ітерації циклу чи функції.

Наприклад, рекурсивна функція (2 окремі функції, так що 1 обробляє неправильний вхід, а інші обробляють поганий код, тому що важко відрізнити рекурсію). Це зробило б очевидним, якби я забув написати заяву if, що пішло не так.

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n

Ці петльові інваріанти часто можуть бути представлені твердженням.


2
Найкраще це робити з декораторами (@precondition та @postcondition)
Caridorc

@Caridorc яка конкретна користь від цього?
Chiel ten Brinke

@ChieltenBrinke самостійно документуючи код, замість #precondition: n >= 0 і стверджувати, він може просто написати@precondition(lambda n: n >= 0)
Caridorc

@Caridorc Тоді ці вбудовані декоратори? І як з цього можна генерувати документацію?
Chiel ten Brinke

@ChieltenBrinke не вбудований, але простий у виконанні stackoverflow.com/questions/12151182/… . Для документації просто __doc__
закріпіть

4

Чи є проблема з продуктивністю?

  • Будь ласка , пам'ятайте , щоб «зробити його роботу , перш ніж змусити його працювати швидко» .
    Дуже мало відсотків будь-якої програми зазвичай актуальні для її швидкості. Ви завжди можете виганяти або спрощувати його, assertякщо це коли-небудь виявиться проблемою продуктивності - і більшість з них ніколи не буде.

  • Будьте прагматичними :
    припустимо, у вас є метод, який обробляє не порожній список кортежів, і логіка програми порушиться, якщо ці кортежі незмінні. Ви повинні написати:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Це, мабуть, добре, якщо ваші списки мають тенденцію до десяти записів, але це може стати проблемою, якщо вони мають мільйон записів. Але замість того, щоб повністю відкинути цей цінний чек, ви можете його просто зменшити

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!

    що є дешевим, але, швидше за все, вловить більшість фактичних помилок програми.


2
Повинно бути assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
osa

Ні, не повинно. Це було б набагато слабкішим тестом, оскільки воно більше не перевіряє властивість "порожнього", яке перевіряє друге твердження. (Перше - ні, хоча і повинно.)
Люц Пречельт

1
Друге твердження не перевіряє явно непусту властивість; це більше побічний ефект. Якби підняти виняток через те, що список порожній, особа, яка працює з кодом (хтось інший чи автор, через рік після його написання), дивилася б на нього, намагаючись зрозуміти, чи справді задумане було спіймати ситуація з порожнім списком або якщо це помилка в самому затвердженні. Крім того, я не бачу, як не перевірити порожній випадок "набагато слабкіше", тоді як лише перевірка першого елемента - "97% правильна".
оса

3

Ну, це питання відкрите, і у мене є два аспекти, які я хочу торкнутися: коли додати твердження і як написати повідомлення про помилки.

Призначення

Щоб пояснити це початківцю - твердження - це твердження, які можуть викликати помилки, але ви не будете їх вловлювати. І їх зазвичай не слід виховувати, але в реальному житті вони іноді все одно піднімаються. І це серйозна ситуація, з якої код не може відновитись, і ми називаємо «фатальною помилкою».

Далі - це для «налагодження», що, хоч і правильно, звучить дуже зневажливо. Мені подобається формулювання «декларування інваріантів, яке ніколи не повинно бути порушено», хоча воно працює по-різному для різних початківців… Деякі «просто отримують це», а інші або не знаходять для нього ніякої користі, або замінюють звичайні винятки, або навіть контролювати потік за допомогою нього.

Стиль

У Python assert- це оператор, а не функція! (пам'ятаю assert(False, 'is true'), не піднімуть. Але, вибивши це з шляху:

Коли і як написати необов'язкове "повідомлення про помилку"?

Це acually відноситься до одиничних тестування рамок, які часто мають багато спеціалізованих методів робити твердження ( assertTrue(condition), і assertFalse(condition), assertEqual(actual, expected)т.д.). Вони часто також надають спосіб коментувати твердження.

У викидальному коді можна обійтися без повідомлень про помилки.

У деяких випадках до твердження немає чого додати:

def dump (щось): стверджувати речовина (щось, Dumpable) # ...

Але крім цього, повідомлення корисне для спілкування з іншими програмістами (які іноді є інтерактивними користувачами вашого коду, наприклад, в Ipython / Jupyter тощо).

Дайте їм інформацію, а не лише протікайте внутрішні деталі впровадження.

замість:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

написати:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

а може навіть:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

Я знаю, я знаю - це не стосується статичного твердження, але я хочу вказати на інформаційну цінність повідомлення.

Негативне чи позитивне повідомлення?

Це може бути суперечливим, але мені боляче читати такі речі, як:

assert a == b, 'a is not equal to b'
  • це дві суперечливі речі, написані поруч з іншим. Тож, коли я маю вплив на кодову базу, я наполягаю на вказівці того, що ми хочемо, використовуючи додаткові дієслова на зразок "must" і "should", а не говорити те, чого ми не хочемо.

    стверджуємо a == b, "a має бути рівним b"

Тоді отримання AssertionError: a must be equal to bтакож читається, і вислів виглядає логічно в коді. Крім того, ви можете отримати щось із цього, не прочитавши прослідкування (яке іноді може бути навіть недоступним).


1

І використання, assertі збільшення винятків стосуються спілкування.

  • Твердження - це твердження про правильність коду, адресованого розробникам : Заява в коді інформує читачів коду про умови, які повинні бути виконані, щоб код був правильним. Твердження, яке не вдається під час виконання, повідомляє розробників про наявність дефекту в коді, який потребує виправлення.

  • Виняток становлять вказівки на нетипові ситуації, які можуть трапитися під час виконання, але не можуть бути вирішені кодом, який використовується, адресованим за кодом виклику, який обробляється там. Виникнення виключення не означає, що в коді є помилка.

Найкраща практика

Тому, якщо ви розглядаєте виникнення конкретної ситуації під час виконання як помилку, про яку ви хотіли б повідомити розробників ("Привіт розробнику, ця умова вказує на те, що десь є помилка. Виправте код.") Тоді піти на твердження. Якщо твердження перевіряє вхідні аргументи вашого коду, як правило, слід додати до документації, що ваш код має "невизначене поведінку", коли вхідні аргументи порушують ці умови.

Якщо замість цього саме ця ситуація не є вказівкою на помилку у ваших очах, а натомість (можливо, рідкісна, але) можлива ситуація, яку, на вашу думку, краще вирішити за допомогою клієнтського коду, створіть виняток. Ситуації, коли виникає виняток, повинні бути частиною документації відповідного коду.

Чи є проблеми з продуктивністю [...] щодо використання assert

Оцінка тверджень потребує певного часу. Однак їх можна усунути під час компіляції. Це має певні наслідки, проте дивіться нижче.

Чи існує проблема з технічним обслуговуванням коду [...] assert

Зазвичай твердження покращують ремонтопридатність коду, оскільки покращують читабельність, роблячи припущення явними та під час роботи регулярно перевіряючи ці припущення. Це також допоможе наздогнати регресії. Однак слід пам’ятати про одне питання: вирази, які використовуються у твердженнях, не повинні мати побічних ефектів. Як було сказано вище, твердження можуть бути усунені під час компіляції - це означає, що також можливі побічні ефекти зникнуть. Це може - ненавмисно - змінити поведінку коду.


1

Assert повинен перевірити -
1. дійсна умова,
2. дійсне твердження,
3. справжня логіка;
вихідного коду. Замість провалу всього проекту він дає сигнал про те, що у вашому вихідному файлі щось не підходить.

У прикладі 1, оскільки змінна 'str' не є нульовою. Тому жодне твердження чи виняток не піднімається.

Приклад 1:

#!/usr/bin/python

str = 'hello Python!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

У прикладі 2, var 'str' є нульовим. Таким чином , ми економимо користувач йти вперед несправної програми, стверджують , заяву.

Приклад 2:

#!/usr/bin/python

str = ''
strNull = 'NULL String'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
AssertionError: NULL String

У той момент, коли ми не хочемо налагодження та зрозуміли проблему твердження у вихідному коді. Вимкніть прапор оптимізації

python -O assertStatement.py
нічого не отримає друк


0

У IDE, таких як PTVS, PyCharm, Wing, assert isinstance()можна використовувати для включення завершення коду для деяких незрозумілих об'єктів.


Це, здається, передувало використанню анотацій типу або typing.cast.
Acumenus

-1

Для чого варто, якщо ви маєте справу з кодом, на який покладається assertфункціонування належним чином, то додавання наступного коду забезпечить активацію тверджень:

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass

2
Це не відповідає на питання ОП, що стосується найкращих практик.
codeforester
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.