Введення качок, перевірка даних та затверджувальне програмування в Python


10

Про набір качок :

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

Про перевірку аргументів (EAFP: простіше просити пробачення, ніж дозволу). Адаптований приклад звідси :

... вважається більш пітонічним зробити:

def my_method(self, key):
    try:
        value = self.a_dict[member]
    except TypeError:
        # do something else

Це означає, що будь-хто інший, що використовує ваш код, не повинен використовувати реальний словник або підклас - вони можуть використовувати будь-який об’єкт, що реалізує інтерфейс відображення.

На жаль, на практиці це не так просто. Що робити, якщо член у наведеному вище прикладі може бути цілим числом? Цілі особи незмінні - тому цілком розумно використовувати їх як словникові ключі. Однак вони також використовуються для індексації об'єктів типу послідовності. Якщо член має ціле число, то приклад два може пропустити списки та рядки, а також словники.

Про асертивне програмування:

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

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

  1. Сильна перевірка. Під сильною валідацією я маю на увазі підняття спеціального винятку ( ApiErrorнаприклад). Якщо моя функція / метод є частиною загальнодоступного API, краще перевірити аргумент, щоб показати гарне повідомлення про помилку про несподіваний тип. Перевіряючи тип, я не маю на увазі лише використання isinstance, але також, якщо переданий об'єкт підтримує необхідний інтерфейс (набирання качки). Хоча я документую API та вказую очікуваний тип, і користувач може захотіти використовувати мою функцію несподівано, я відчуваю себе безпечнішим, коли перевіряю припущення. Я зазвичай використовую, isinstanceі якщо пізніше я хочу підтримувати інші типи або качки, я змінюю логіку перевірки.

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

  3. Якщо моя функція / метод не є частиною API, але передає деякі свої аргументи до іншого коду, не написаного, вивченого чи перевіреного мною, я роблю багато тверджень відповідно до названого інтерфейсу. Моя логіка за цим - краще провал у моєму коді, то десь на 10 рівнів глибше в стек-трасі з незрозумілою помилкою, яка змушує багато налагоджувати, а пізніше додавати твердження до мого коду все одно.

Коментарі та поради щодо того, коли використовувати чи не використовувати перевірку типу / значення, стверджує? Вибачте за не найкращу постановку питання.

Наприклад, розглянемо наступну функцію, де Customer є декларативна модель SQLAlchemy:

def add_customer(self, customer):
    """Save new customer into the database.
    @param customer: Customer instance, whose id is None
    @return: merged into global session customer
    """
    # no validation here at all
    # let's hope SQLAlchemy session will break if `customer` is not a model instance
    customer = self.session.add(customer)
    self.session.commit()
    return customer

Отже, існує декілька способів обробки валідації:

def add_customer(self, customer):
    # this is an API method, so let's validate the input
    if not isinstance(customer, Customer):
        raise ApiError('Invalid type')
    if customer.id is not None:
        raise ApiError('id should be None')

    customer = self.session.add(customer)
    self.session.commit()
    return customer

або

def add_customer(self, customer):
    # this is an internal method, but i want to be sure
    # that it's a customer model instance
    assert isinstance(customer, Customer), 'Achtung!'
    assert customer.id is None

    customer = self.session.add(customer)
    self.session.commit()
    return customer

Коли і навіщо ви використовуєте кожне з них у контексті типи качок, перевірки типу, перевірки даних?


1
ви не повинні видаляти твердження так само, як одиничні тести, якщо тільки з причини продуктивності
Брайан Чен,

Відповіді:


4

Дозвольте дати декілька керівних принципів.

Принцип №1. Як зазначено в http://docs.python.org/2/reference/simple_stmts.html накладні витрати на твердження можна видалити за допомогою параметра командного рядка, зберігаючи при цьому налагодження. Якщо продуктивність є проблемою, зробіть це. Залиште твердження. (Але не робіть нічого важливого в твердженнях!)

Принцип №2. Якщо ви щось запевняєте і будете мати фатальну помилку, тоді використовуйте затвердження. Немає жодної цінності робити щось інше. Якщо хтось пізніше хоче це змінити, він може змінити ваш код або уникнути цього виклику методу.

Принцип №3. Не забороняйте щось тільки тому, що ви думаєте, що це дурно робити. Що робити, якщо ваш метод дозволяє пропускати рядки? Якщо він працює, він працює.

Принцип №4. Забороніть речі, які є ознаками ймовірних помилок. Наприклад, розгляньте можливість передачі словника параметрів. Якщо цей словник містить речі, що не відповідають дійсності, то це знак того, що хтось не зрозумів ваш API, або ж був помилковий помилок. Вибух на цьому швидше зловить помилку, ніж зупинити когось робити щось розумне.

Виходячи з перших 2 принципів, вашу другу версію можна викинути. Які з двох інших ви віддаєте перевагу - це питання смаку. Що ти вважаєш більш ймовірним? Якщо хтось перейде до не-замовника, add_customerі все порушиться (у такому випадку краща версія 3) або що хтось у якийсь момент захоче замінити вашого клієнта проксі-об'єктом якогось типу, який відповідає всім правильним методам (у цьому випадку краща версія 1).

Особисто я бачив обидва режими відмов. Я схильний переходити з версією 1 із загального принципу, що я лінивий, і він менше друкує. (Крім того, подібний збій, як правило, рано чи пізно виявляється досить очевидним чином. І коли я хочу використовувати проксі-об'єкт, я дуже дратуюся людям, які перев'язали мені руки.) Але є програмісти, яких я поважаю пішов би іншим шляхом.


Я віддаю перевагу v.3, особливо при проектуванні інтерфейсу - написанні нових класів та методів. Також я вважаю v.3 корисним для методів API - тому що мій код новий для інших. Я вважаю, що напористий підхід є хорошим компромісом, оскільки він знімається у виробництві, коли працює в оптимізованому режимі. > Вибух на цьому, швидше за все, впіймає помилку, ніж це не дозволяє комусь зробити щось розумне. <Отже, ви не проти мати таке підтвердження?
warvariuc

Давайте викладемо так. Я вважаю, що спадщину погано відображається, як я люблю розвивати проекти. Я віддаю перевагу композиції. Тож я цураюсь твердження, що це повинно бути цього класу. Але я не проти тверджень, де я думаю, що вони мене щось рятують.
btilly
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.