Какова ваша стратегия, чтобы избежать ошибок динамической типизации в Python (NoneType не имеет атрибута x)? - PullRequest
9 голосов
/ 23 марта 2010

Я не уверен, нравится ли мне динамичность Python. Это часто приводит к тому, что я забываю проверить тип, пытаюсь вызвать атрибут и получить NoneType (или любой другой) без атрибута x error. Многие из них довольно безопасны, но если их неправильно обработать, они могут повредить все ваше приложение / процесс / и т. Д.

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

Так что я заинтересован в вашей стратегии, чтобы избежать этого. Вы используете декораторы проверки типа? Может быть, специальные обертки объекта?

Пожалуйста, поделитесь ...

Ответы [ 10 ]

8 голосов
/ 24 марта 2010

забыв проверить тип

Это не имеет особого смысла. Вам так редко нужно «проверять» тип. Вы просто запускаете модульные тесты, и если вы предоставили объект неправильного типа, все закончится неудачей. По моему опыту, вам никогда не нужно много «проверять».

пытается вызвать атрибут и получить NoneType (или любой другой) не имеет атрибута x error.

Неожиданно None - старая ошибка. В 80% случаев я опускал return. Модульные тесты всегда показывают это.

Из тех, которые остаются, в 80% случаев это просто старые ошибки из-за «раннего выхода», который возвращает None, потому что кто-то написал неполное return утверждение. Эти if foo: return структуры легко обнаружить с помощью модульных тестов. В некоторых случаях они должны были быть if foo: return somethingMeaningful, а в других случаях они должны были быть if foo: raise Exception("Foo").

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

Это довольно убедительно покрывает "неожиданные None" дела. Легко для модульного тестирования. Большинство ошибок включают в себя довольно простые для написания тесты для некоторых довольно очевидных видов ошибок: неправильный возврат; невозможность вызвать исключение.

Другие ошибки "не имеют атрибута X" - это действительно дикие ошибки, когда использовался совершенно неправильный тип. Это либо действительно неправильные операторы присваивания, либо действительно неправильные вызовы функций (или методов). Они всегда тщательно продумываются во время модульного тестирования, требуя очень небольших усилий для исправления.

Многие из них довольно безопасны, но если их неправильно обработать, они могут повредить все ваше приложение / процесс / и т. Д.

Хм ... Безвреден? Если это ошибка, я молюсь, чтобы она как можно быстрее закрывала все мое приложение, чтобы я мог ее найти. Ошибка, которая не приводит к сбою моего приложения, является самой ужасной ситуацией, которую только можно представить. «Безвредный» - это не то слово, которое я бы использовал для ошибки, которая не может привести к сбою моего приложения.

4 голосов
/ 24 марта 2010

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

>>> @accepts(int, int, int)
... @returns(float)
... def average(x, y, z):
...     return (x + y + z) / 2
...
>>> average(5.5, 10, 15.0)
TypeWarning:  'average' method accepts (int, int, int), but was given
(float, int, float)
15.25
>>> average(5, 10, 15)
TypeWarning:  'average' method returns (float), but result is (int)
15

Я на самом деле не фанат их, но я вижу их полезность.

4 голосов
/ 23 марта 2010

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

2 голосов
/ 24 марта 2010

Один из инструментов, который поможет вам хорошо совмещать детали, - это интерфейсы . zope.interface - самый известный пакет в мире Python для использования интерфейсов. Проверьте http://wiki.zope.org/zope3/WhatAreInterfaces и http://glyph.twistedmatrix.com/2009/02/explaining-why-interfaces-are-great.html, чтобы начать понимать, как работают интерфейсы и z.i в частности. Интерфейсы могут оказаться очень полезными в больших кодах Python.

Интерфейсы не являются заменой для тестирования. Разумно всестороннее тестирование особенно важно в высокодинамичных языках, таких как Python, где есть типы ошибок, которые не могут существовать в языке статических типов. Тесты также помогут вам выявить ошибки, которые не являются уникальными для динамических языков. К счастью, разработка на Python означает, что тестирование легко (благодаря гибкости), и у вас есть достаточно времени, чтобы написать их, которые вы сохранили, потому что вы используете Python.

1 голос
/ 24 марта 2010

забыв проверить тип

При вводе утки нет необходимости проверять тип. Но это теория, в действительности вам часто нужно проверять входные параметры (например, проверять UUID с помощью регулярного выражения). Для этого я создал несколько удобных декораторов для простой проверки типов и возвращаемых типов, которые называются так:

@decorators.params(0, int, 2, str) # first parameter must be integer / third a string
@decorators.returnsOrNone(int, long) # must return an int/long value or None
def doSomething(integerParam, noMatterWhatParam, stringParam):
    ...

Для всего остального я в основном использую утверждения. Конечно, часто забывают проверить параметр, поэтому необходимо часто проверять и проверять.

пытается вызвать атрибут

Случается со мной очень редко. На самом деле я часто использую методы вместо прямого доступа к атрибутам (иногда «старый добрый» метод получения / установки).

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

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

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

1 голос
/ 24 марта 2010

Одним из преимуществ TDD является то, что вы в конечном итоге пишете код, для которого проще писать тесты.

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

Каждый случай, вероятно, будет отличаться

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

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

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

0 голосов
/ 16 декабря 2015

Вы можете намекнуть на свою IDE через функцию doc, например: http://www.pydev.org/manual_adv_type_hints.html, в JavaScript jsDoc помогает аналогичным образом.

Но в какой-то момент вы столкнетесь с ошибками, которые типизированный язык мог бы избежать немедленно без юнит-тестов (с помощью компиляции IDE и типов / выводов).

Конечно, это не устраняет преимущества юнит-тестов, статического анализа и утверждений. Для больших проектов я склонен использовать статически типизированные языки, потому что они имеют очень хорошую поддержку IDE (отличное автозаполнение, интенсивный рефакторинг ...). Вы все еще можете использовать сценарии или DSL для некоторой части проекта.

0 голосов
/ 23 марта 2010

То, что вы можете использовать для упрощения своего кода, - это шаблон проектирования пустых объектов (о котором я рассказывал в Python Cookbook ).

Грубо говоря, цель с нулевыми объектами состоит в том, чтобы замена часто используемого примитивного типа данных None в Python или Нуль (или нулевые указатели) на других языках. Они используются для многих цели, включая важный случай, когда один член какой-либо группы из других похожих элементов является особенным по любой причине. Наиболее часто это приводит к условным утверждениям, чтобы различать обычные элементы и примитивное значение Null.

Этот объект просто съедает отсутствие атрибута ошибки, и вы можете избежать проверки их существования.

Это не более чем

class Null(object):

    def __init__(self, *args, **kwargs):
        "Ignore parameters."
        return None

    def __call__(self, *args, **kwargs):
        "Ignore method calls."
        return self

    def __getattr__(self, mname):
        "Ignore attribute requests."
        return self

    def __setattr__(self, name, value):
        "Ignore attribute setting."
        return self

    def __delattr__(self, name):
        "Ignore deleting attributes."
        return self

    def __repr__(self):
        "Return a string representation."
        return "<Null>"

    def __str__(self):
        "Convert to a string and return it."
        return "Null"

При этом, если вы сделаете Null("any", "params", "you", "want").attribute_that_doesnt_exists(), он не взорвется, а просто тихо станет эквивалентом pass.

Обычно вы делаете что-то вроде

if obj.attr:
    obj.attr()

С этим вы просто делаете:

obj.attr()

и забудь об этом. Помните, что широкое использование объекта Null потенциально может скрывать ошибки в вашем коде.

0 голосов
/ 23 марта 2010

Я не много занимался программированием на Python, но вообще не программировал на языках со статической типизацией, поэтому я не склонен думать о вещах в терминах переменных типов. Это может объяснить, почему я не сталкивался с этой проблемой. (Хотя небольшой объем программирования на Python, который я сделал, тоже может объяснить это.)

Мне очень нравится пересмотренная обработка строк в Python 3 (т.е. все строки в юникоде, все остальное - просто поток байтов), потому что в Python 2 вы можете не заметить TypeError s, пока не столкнетесь с необычными значениями строк реального мира.

0 голосов
/ 23 марта 2010

Я склонен использовать

if x is None:
    raise ValueError('x cannot be None')

Но это будет работать только с фактическим значением None.

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

def write_data(f):
    # Here we expect f is a file-like object.  But what if it's not?
    if not hasattr(f, 'write'):
        raise ValueError('write_data requires a file-like object')
    # Now we can do stuff with f that assumes it is a file-like object

Смысл этого кода в том, что вместо получения сообщения об ошибке типа «NoneType не имеет атрибута write», вы получаете «write_data требует файлоподобный объект». Фактическая ошибка не в write_data(), и вообще не является проблемой с NoneType. Фактическая ошибка в коде, который вызывает write_data(). Ключ в том, чтобы передать эту информацию как можно более прямо.

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