Руководство по стилю кода и документации - недостающие части#
Это набор рекомендаций по кодированию и документации для SciPy, которые не указаны явно в существующих руководствах и стандартах, включая
Некоторые из них тривиальны и могут не казаться достойными обсуждения, но во многих случаях этот вопрос возникал при ревью пул-реквестов в репозиториях SciPy или NumPy. Если стилевой вопрос достаточно важен, чтобы ревьюер требовал изменения перед слиянием, то он достаточно важен для документирования — по крайней мере, для случаев, когда проблему можно решить простым правилом.
Стиль кодирования и руководства#
Обратите внимание, что строки документации обычно должны состоять из символов ASCII,
несмотря на то, что они в Unicode. Следующий блок кода из файла
tools/check_unicode.py указывает линтеру, какие дополнительные символы
разрешены:
18latin1_letters = set(chr(cp) for cp in range(192, 256))
19greek_letters = set('αβγδεζηθικλμνξoπρστυϕχψω' + 'ΓΔΘΛΞΠΣϒΦΨΩ')
20box_drawing_chars = set(chr(cp) for cp in range(0x2500, 0x2580))
21extra_symbols = set('®ő∫≠≥≤±∞²³·→√✅⛔⚠️')
22allowed = latin1_letters | greek_letters | box_drawing_chars | extra_symbols
Обязательные имена ключевых слов#
Для новых функций или методов с более чем несколькими аргументами все параметры после первых нескольких «очевидных» должны требовать использование ключевого слова
при указании. Это реализовано путём включения * в соответствующей точке
в сигнатуре.
Например, функция foo который работает с одним массивом, но имеет несколько опциональных параметров (скажем method, flag, rtol и atol)
будет определён как:
def foo(x, *, method='basic', flag=False, rtol=1.5e-8, atol=1-12):
...
Для вызова foo, все параметры, кроме x должен быть указан с явным ключевым словом, например, foo(arr, rtol=1e-12, method='better').
Это заставляет вызывающих явно указывать ключевые параметры (что большинство пользователей,
вероятно, делали бы в любом случае даже без использования *), и это означает,
что дополнительные параметры могут быть добавлены к функции в любом месте после
*; новые параметры не обязательно добавлять после существующих параметров.
Возвращаемые объекты#
Для новых функций или методов, которые возвращают два или более концептуально различных
элемента, возвращайте элементы в типе объекта, который не является итерируемым. В частности,
не возвращайте tuple, namedtuple, или «набор» (bunch), созданный
с помощью scipy._lib._bunch.make_tuple_bunch, последний зарезервирован для добавления
новых атрибутов к итерируемым объектам, возвращаемым существующими функциями. Вместо этого используйте
существующий возвращаемый класс (например, OptimizeResult), новый, пользовательский
класс возврата.
Такая практика возврата неитерируемых объектов заставляет вызывающие стороны быть более явными относительно элемента возвращаемого объекта, к которому они хотят получить доступ, и упрощает расширение функции или метода обратно совместимым образом.
Если возвращаемый класс простой и не публичный (т.е. не импортируемый из публичного модуля), он может быть задокументирован как:
Returns
-------
res : MyResultObject
An object with attributes:
attribute1 : ndarray
Customized description of attribute 1.
attribute2 : ndarray
Customized description of attribute 2.
Здесь "MyResultObject" выше не ссылается на внешнюю документацию, так как он достаточно прост, чтобы полностью документировать все атрибуты сразу после его имени.
Некоторые возвращаемые классы достаточно сложны, чтобы заслуживать собственной отрендеренной
документации. Это довольно стандартно, если возвращаемый класс является публичным, но
возвращаемые классы должны быть публичными только если 1) они предназначены для импорта
конечными пользователями и 2) если они были одобрены форумом. Для сложных,
приватных возвращаемых классов, пожалуйста, смотрите как binomtest обобщает
BinomTestResult и ссылки на его документацию, и обратите внимание, что BinomTestResult не может быть импортирован из stats.
В зависимости от сложности "MyResultObject" можно использовать обычный класс или dataclass.
При использовании dataclasses не используйте dataclasses.make_dataclass,
вместо этого используйте правильное объявление. Это позволяет автодополнению перечислять все
атрибуты объекта результата и улучшает статический анализ.
Наконец, скройте приватные атрибуты, если они есть:
@dataclass
class MyResultObject:
statistic: np.ndarray
pvalue: np.ndarray
confidence_interval: ConfidenceInterval
_rho: np.ndarray = field(repr=False)
Тестовые функции из numpy.testing#
В новом коде не используйте assert_almost_equal, assert_approx_equal или assert_array_almost_equal. Это из документации этих функций:
It is recommended to use one of `assert_allclose`,
`assert_array_almost_equal_nulp` or `assert_array_max_ulp`
instead of this function for more consistent floating point
comparisons.
Для получения дополнительной информации о написании модульных тестов см. Руководства по тестированию NumPy.
Тестирование ожидаемых исключений/предупреждений#
При написании нового теста, что вызов функции вызывает исключение или выдает
предупреждение, предпочтительный стиль — использовать pytest.raises/pytest.warns как
менеджер контекста, с кодом, который должен вызвать исключение в
блоке кода, определенном менеджером контекста. match ключевой аргумент
передан с достаточной частью ожидаемого сообщения, прикрепленного к исключению/предупреждению,
чтобы отличить его от других исключений/предупреждений того же класса. Не используйте
np.testing.assert_raises или np.testing.assert_warns, так как они не
поддерживают match параметр.
Например, функция scipy.stats.zmap должен вызывать
ValueError если вход содержит nan и nan_policy является "raise". Тест для этого:
scores = np.array([1, 2, 3])
compare = np.array([-8, -3, 2, 7, 12, np.nan])
with pytest.raises(ValueError, match='input contains nan'):
stats.zmap(scores, compare, nan_policy='raise')
The match аргумент гарантирует, что тест не пройдёт из-за вызова
исключения ValueError который не связан с входными данными, содержащими nan.