150 lines
6.3 KiB
ReStructuredText
150 lines
6.3 KiB
ReStructuredText
:orphan:
|
|
|
|
.. _missing-bits:
|
|
|
|
Code and Documentation Style Guide - The Missing Bits
|
|
=====================================================
|
|
|
|
This is a collection of coding and documentation guidelines for SciPy that
|
|
are not explicitly stated in the existing guidelines and standards, including
|
|
|
|
* `PEP-8 <https://www.python.org/dev/peps/pep-0008>`_ Style Guide for Python Code
|
|
* `PEP-257 <https://www.python.org/dev/peps/pep-0257>`_ Docstring Conventions
|
|
* `NumPy docstring standard
|
|
<https://numpydoc.readthedocs.io/en/latest/format.html>`_
|
|
* :doc:`NumPy Testing Guidelines <numpy:reference/testing>`
|
|
|
|
Some of these are trivial, and might not seem worth discussing, but in many
|
|
cases, the issue has come up in a pull request review in either the SciPy
|
|
or NumPy repositories. If a style issue is important enough that a reviewer
|
|
will require a change before merging, then it is important enough to be
|
|
documented--at least for cases where the issue can be resolved with a simple
|
|
rule.
|
|
|
|
|
|
Coding Style and Guidelines
|
|
---------------------------
|
|
Note that docstrings should be generally made up of ASCII characters
|
|
in spite of being Unicode. The following code block from the file
|
|
``tools/check_unicode.py`` tells the linter which additional characters
|
|
are allowed:
|
|
|
|
.. literalinclude:: ../../../tools/check_unicode.py
|
|
:start-after: # BEGIN_INCLUDE_RST
|
|
:end-before: # END_INCLUDE_RST
|
|
:lineno-match:
|
|
|
|
|
|
Required keyword names
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
For new functions or methods with more than a few arguments, all parameters
|
|
after the first few "obvious" ones should *require* the use of the keyword
|
|
when given. This is implemented by including ``*`` at the appropriate point
|
|
in the signature.
|
|
|
|
For example, a function ``foo`` that operates on a single array but that has
|
|
several optional parameters (say ``method``, ``flag``, ``rtol`` and ``atol``)
|
|
would be defined as::
|
|
|
|
def foo(x, *, method='basic', flag=False, rtol=1.5e-8, atol=1-12):
|
|
...
|
|
|
|
To call ``foo``, all parameters other than ``x`` must be given with an
|
|
explicit keyword, e.g. ``foo(arr, rtol=1e-12, method='better')``.
|
|
|
|
This forces callers to give explicit keyword parameters (which most users
|
|
would probably do anyway even without the use of ``*``), *and* it means
|
|
additional parameters can be added to the function anywhere after the
|
|
``*``; new parameters do not have to be added after the existing parameters.
|
|
|
|
|
|
Return Objects
|
|
~~~~~~~~~~~~~~
|
|
For new functions or methods that return two or more conceptually distinct
|
|
elements, return the elements in an object type that is not iterable. In
|
|
particular, do not return a ``tuple``, ``namedtuple``, or a "bunch" produced
|
|
by ``scipy._lib._bunch.make_tuple_bunch``, the latter being reserved for adding
|
|
new attributes to iterables returned by existing functions. Instead, use an
|
|
existing return class (e.g. `~scipy.optimize.OptimizeResult`), a new, custom
|
|
return class.
|
|
|
|
This practice of returning non-iterable objects forces callers to be more
|
|
explicit about the element of the returned object that they wish to access,
|
|
and it makes it easier to extend the function or method in a backward
|
|
compatible way.
|
|
|
|
If the return class is simple and not public (i.e. importable from a public
|
|
module), it may be documented like::
|
|
|
|
Returns
|
|
-------
|
|
res : MyResultObject
|
|
An object with attributes:
|
|
|
|
attribute1 : ndarray
|
|
Customized description of attribute 1.
|
|
attribute2 : ndarray
|
|
Customized description of attribute 2.
|
|
|
|
Here "MyResultObject" above does not link to external documentation because it
|
|
is simple enough to fully document all attributes immediately below its name.
|
|
|
|
Some return classes are sufficiently complex to deserve their own rendered
|
|
documentation. This is fairly standard if the return class is public, but
|
|
return classes should only be public if 1) they are intended to be imported by
|
|
end-users and 2) if they have been approved by the forum. For complex,
|
|
private return classes, please see how `~scipy.stats.binomtest` summarizes
|
|
`~scipy.stats._result_classes.BinomTestResult` and links to its documentation,
|
|
and note that ``BinomTestResult`` cannot be imported from `~scipy.stats`.
|
|
|
|
Depending on the complexity of "MyResultObject", a normal class or a dataclass
|
|
can be used. When using dataclasses, do not use ``dataclasses.make_dataclass``,
|
|
instead use a proper declaration. This allows autocompletion to list all
|
|
the attributes of the result object and improves static analysis.
|
|
Finally, hide private attributes if any::
|
|
|
|
@dataclass
|
|
class MyResultObject:
|
|
statistic: np.ndarray
|
|
pvalue: np.ndarray
|
|
confidence_interval: ConfidenceInterval
|
|
_rho: np.ndarray = field(repr=False)
|
|
|
|
|
|
Test functions from `numpy.testing`
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
In new code, don't use `assert_almost_equal`, `assert_approx_equal` or
|
|
`assert_array_almost_equal`. This is from the docstrings of these functions::
|
|
|
|
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.
|
|
|
|
For more information about writing unit tests, see the
|
|
:doc:`NumPy Testing Guidelines <numpy:reference/testing>`.
|
|
|
|
|
|
Testing expected exceptions/ warnings
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
When writing a new test that a function call raises an exception or emits a
|
|
warning, the preferred style is to use ``pytest.raises``/``pytest.warns`` as
|
|
a context manager, with the code that is supposed to raise the exception in
|
|
the code block defined by the context manager. The ``match`` keyword argument
|
|
is given with enough of the expected message attached to the exception/warning
|
|
to distinguish it from other exceptions/warnings of the same class. Do not use
|
|
``np.testing.assert_raises`` or ``np.testing.assert_warns``, as they do not
|
|
support a ``match`` parameter.
|
|
|
|
For example, the function `scipy.stats.zmap` is supposed to raise a
|
|
``ValueError`` if the input contains ``nan`` and ``nan_policy`` is ``"raise"``.
|
|
A test for this is::
|
|
|
|
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`` argument ensures that the test doesn't pass by raising
|
|
a ``ValueError`` that is not related to the input containing ``nan``.
|