8000 moved coding guide out of contribute to its own page · matplotlib/matplotlib@81ebeb7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 81ebeb7

Browse files
committed
moved coding guide out of contribute to its own page
1 parent 20d0d14 commit 81ebeb7

File tree

1 file changed

+312
-0
lines changed

1 file changed

+312
-0
lines changed

doc/devel/coding_guide.rst

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
.. _coding_guidelines:
2+
3+
*****************
4+
Coding guidelines
5+
*****************
6+
7+
While the current state of the Matplotlib code base is not compliant with all
8+
of these guidelines, our goal in enforcing these constraints on new
9+
contributions is that it improves the readability and consistency of the code base
10+
going forward.
11+
12+
API changes
13+
===========
14+
15+
If you are adding new features, changing behavior or function signatures, or
16+
removing classes, functions, methods, or properties, please see the :ref:`api_changes`
17+
guide.
18+
19+
PEP8, as enforced by flake8
20+
===========================
21+
22+
Formatting should follow the recommendations of PEP8_, as enforced by flake8_.
23+
Matplotlib modifies PEP8 to extend the maximum line length to 88
24+
characters. You can check flake8 compliance from the command line with ::
25+
26+
python -m pip install flake8
27+
flake8 /path/to/module.py
28+
29+
or your editor may provide integration with it. Note that Matplotlib intentionally
30+
does not use the black_ auto-formatter (1__), in particular due to its inability
31+
to understand the semantics of mathematical expressions (2__, 3__).
32+
33+
.. _PEP8: https://www.python.org/dev/peps/pep-0008/
34+
.. _flake8: https://flake8.pycqa.org/
35+
.. _black: https://black.readthedocs.io/
36+
.. __: https://github.com/matplotlib/matplotlib/issues/18796
37+
.. __: https://github.com/psf/black/issues/148
38+
.. __: https://github.com/psf/black/issues/1984
39+
40+
41+
Package imports
42+
===============
43+
44+
Import the following modules using the standard scipy conventions::
45+
46+
import numpy as np
47+
import numpy.ma as ma
48+
import matplotlib as mpl
49+
import matplotlib.pyplot as plt
50+
import matplotlib.cbook as cbook
51+
import matplotlib.patches as mpatches
52+
53+
In general, Matplotlib modules should **not** import `.rcParams` using ``from
54+
matplotlib import rcParams``, but rather access it as ``mpl.rcParams``. This
55+
is because some modules are imported very early, before the `.rcParams`
56+
singleton is constructed.
57+
58+
Variable names
59+
==============
60+
61+
When feasible, please use our internal variable naming convention for objects
62+
of a given class and objects of any child class:
63+
64+
+------------------------------------+---------------+------------------------------------------+
65+
| base class | variable | multiples |
66+
+====================================+===============+==========================================+
67+
| `~matplotlib.figure.FigureBase` | ``fig`` | |
68+
+------------------------------------+---------------+------------------------------------------+
69+
| `~matplotlib.axes.Axes` | ``ax`` | |
70+
+------------------------------------+---------------+------------------------------------------+
71+
| `~matplotlib.transforms.Transform` | ``trans`` | ``trans_<source>_<target>`` |
72+
+ + + +
73+
| | | ``trans_<source>`` when target is screen |
74+
+------------------------------------+---------------+------------------------------------------+
75+
76+
Generally, denote more than one instance of the same class by adding suffixes to
77+
the variable names. If a format isn't specified in the table, use numbers or
78+
letters as appropriate.
79+
80+
.. _type-hints:
81+
82+
Type hints
83+
==========
84+
85+
If you add new public API or change public API, update or add the
86+
corresponding `mypy <https://mypy.readthedocs.io/en/latest/>`_ type hints.
87+
We generally use `stub files
88+
<https://typing.readthedocs.io/en/latest/source/stubs.html#type-stubs>`_
89+
(``*.pyi``) to store the type information; for example ``colors.pyi`` contains
90+
the type information for ``colors.py``. A notable exception is ``pyplot.py``,
91+
which is type hinted inline.
92+
93+
Type hints are checked by the mypy :ref:`pre-commit hook <pre-commit-hooks>`
94+
and can often be verified using ``tools\stubtest.py`` and occasionally may
95+
require the use of ``tools\check_typehints.py``.
96+
97+
New modules and files: installation
98+
===================================
99+
100+
* If you have added new files or directories, or reorganized existing ones, make sure the
101+
new files are included in the :file:`meson.build` in the corresponding directories.
102+
* New modules *may* be typed inline or using parallel stub file like existing modules.
103+ 9E12
104+
C/C++ extensions
105+
================
106+
107+
* Extensions may be written in C or C++.
108+
109+
* Code style should conform to PEP7 (understanding that PEP7 doesn't
110+
address C++, but most of its admonitions still apply).
111+
112+
* Python/C interface code should be kept separate from the core C/C++
113+
code. The interface code should be named :file:`FOO_wrap.cpp` or
114+
:file:`FOO_wrapper.cpp`.
115+
116+
* Header file documentation (aka docstrings) should be in Numpydoc
117+
format. We don't plan on using automated tools for these
118+
docstrings, and the Numpydoc format is well understood in the
119+
scientific Python community.
120+
121+
* C/C++ code in the :file:`extern/` directory is vendored, and should be kept
122+
close to upstream whenever possible. It can be modified to fix bugs or
123+
implement new features only if the required changes cannot be made elsewhere
124+
in the codebase. In particular, avoid making style fixes to it.
125+
126+
.. _keyword-argument-processing:
127+
128+
Keyword argument processing
129+
===========================
130+
131+
Matplotlib makes extensive use of ``**kwargs`` for pass-through customizations
132+
from one function to another. A typical example is
133+
`~matplotlib.axes.Axes.text`. The definition of `matplotlib.pyplot.text` is a
134+
simple pass-through to `matplotlib.axes.Axes.text`::
135+
136+
# in pyplot.py
137+
def text(x, y, s, fontdict=None, **kwargs):
138+
return gca().text(x, y, s, fontdict=fontdict, **kwargs)
139+
140+
`matplotlib.axes.Axes.text` (simplified for illustration) just
141+
passes all ``args`` and ``kwargs`` on to ``matplotlib.text.Text.__init__``::
142+
143+
# in axes/_axes.py
144+
def text(self, x, y, s, fontdict=None, **kwargs):
145+
t = Text(x=x, y=y, text=s, **kwargs)
146+
147+
and ``matplotlib.text.Text.__init__`` (again, simplified)
148+
just passes them on to the `matplotlib.artist.Artist.update` method::
149+
150+
# in text.py
151+
def __init__(self, x=0, y=0, text='', **kwargs):
152+
super().__init__()
153+
self.update(kwargs)
154+
155+
``update`` does the work looking for methods named like
156+
``set_property`` if ``property`` is a keyword argument. i.e., no one
157+
looks at the keywords, they just get passed through the API to the
158+
artist constructor which looks for suitably named methods and calls
159+
them with the value.
160+
161+
As a general rule, the use of ``**kwargs`` should be reserved for
162+
pass-through keyword arguments, as in the example above. If all the
163+
keyword args are to be used in the function, and not passed
164+
on, use the key/value keyword args in the function definition rather
165+
than the ``**kwargs`` idiom.
166+
167+
In some cases, you may want to consume some keys in the local
168+
function, and let others pass through. Instead of popping arguments to
169+
use off ``**kwargs``, specify them as keyword-only arguments to the local
170+
function. This makes it obvious at a glance which arguments will be
171+
consumed in the function. For example, in
172+
:meth:`~matplotlib.axes.Axes.plot`, ``scalex`` and ``scaley`` are
173+
local arguments and the rest are passed on as
174+
:meth:`~matplotlib.lines.Line2D` keyword arguments::
175+
176+
# in axes/_axes.py
177+
def plot(self, *args, scalex=True, scaley=True, **kwargs):
178+
lines = []
179+
for line in self._get_lines(*args, **kwargs):
180+
self.add_line(line)
181+
lines.append(line)
182+
183+
.. _using_logging:
184+
185+
Using logging for debug messages
186+
================================
187+
188+
Matplotlib uses the standard Python `logging` library to write verbose
189+
warnings, information, and debug messages. Please use it! In all those places
190+
you write `print` calls to do your debugging, try using `logging.debug`
191+
instead!
192+
193+
194+
To include `logging` in your module, at the top of the module, you need to
195+
``import logging``. Then calls in your code like::
196+
197+
_log = logging.getLogger(__name__) # right after the imports
198+
199+
# code
200+
# more code
201+
_log.info('Here is some information')
202+
_log.debug('Here is some more detailed information')
203+
204+
will log to a logger named ``matplotlib.yourmodulename``.
205+
206+
If an end-user of Matplotlib sets up `logging` to display at levels more
207+
verbose than ``logging.WARNING`` in their code with the Matplotlib-provided
208+
helper::
209+
210+
plt.set_loglevel("debug")
211+
212+
or manually with ::
213+
214+
import logging
215+
logging.basicConfig(level=logging.DEBUG)
216+
import matplotlib.pyplot as plt
217+
218+
Then they will receive messages like
219+
220+
.. code-block:: none
221+
222+
DEBUG:matplotlib.backends:backend MacOSX version unknown
223+
DEBUG:matplotlib.yourmodulename:Here is some information
224+
DEBUG:matplotlib.yourmodulename:Here is some more detailed information
225+
226+
Avoid using pre-computed strings (``f-strings``, ``str.format``,etc.) for logging because
227+
of security and performance issues, and because they interfere with style handlers. For
228+
example, use ``_log.error('hello %s', 'world')`` rather than ``_log.error('hello
229+
{}'.format('world'))`` or ``_log.error(f'hello {s}')``.
230+
231+
Which logging level to use?
232+
---------------------------
233+
234+
There are five levels at which you can emit messages.
235+
236+
- `logging.critical` and `logging.error` are really only there for errors that
237+
will end the use of the library but not kill the interpreter.
238+
- `logging.warning` and `._api.warn_external` are used to warn the user,
239+
see below.
240+
- `logging.info` is for information that the user may want to know if the
241+
program behaves oddly. They are not displayed by default. For instance, if
242+
an object isn't drawn because its position is ``NaN``, that can usually
243+
be ignored, but a mystified user could call
244+
``logging.basicConfig(level=logging.INFO)`` and get an error message that
245+
says why.
246+
- `logging.debug` is the least likely to be displayed, and hence can be the
247+
most verbose. "Expected" code paths (e.g., reporting normal intermediate
248+
steps of layouting or rendering) should only log at this level.
249+
250+
By default, `logging` displays all log messages at levels higher than
251+
``logging.WARNING`` to `sys.stderr`.
252+
253+
The `logging tutorial`_ suggests that the difference between `logging.warning`
254+
and `._api.warn_external` (which uses `warnings.warn`) is that
255+
`._api.warn_external` should be used for things the user must change to stop
256+
the warning (typically in the source), whereas `logging.warning` can be more
257+
persistent. Moreover, note that `._api.warn_external` will by default only
258+
emit a given warning *once* for each line of user code, whereas
259+
`logging.warning` will display the message every time it is called.
260+
261+
By default, `warnings.warn` displays the line of code that has the ``warn``
262+
call. This usually isn't more informative than the warning message itself.
263+
Therefore, Matplotlib uses `._api.warn_external` which uses `warnings.warn`,
264+
but goes up the stack and displays the first line of code outside of
265+
Matplotlib. For example, for the module::
266+
267+
# in my_matplotlib_module.py
268+
import warnings
269+
270+
def set_range(bottom, top):
271+
if bottom == top:
272+
warnings.warn('Attempting to set identical bottom==top')
273+
274+
running the script::
275+
276+
from matplotlib import my_matplotlib_module
277+
my_matplotlib_module.set_range(0, 0) # set range
278+
279+
will display
280+
281+
.. code-block:: none
282+
283+
UserWarning: Attempting to set identical bottom==top
284+
warnings.warn('Attempting to set identical bottom==top')
285+
286+
Modifying the module to use `._api.warn_external`::
287+
288+
from matplotlib import _api
289+
290+
def set_range(bottom, top):
291+
if bottom == top:
292+
_api.warn_external('Attempting to set identical bottom==top')
293+
294+
and running the same script will display
295+
296+
.. code-block:: none
297+
298+
UserWarning: Attempting to set identical bottom==top
299+
my_matplotlib_module.set_range(0, 0) # set range
300+
301+
.. _logging tutorial: https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
302+
303+
304+
.. _licence-coding-guide:
305+
306+
.. include:: license.rst
307+
:start-line: 2
308+
309+
.. toctree::
310+
:hidden:
311+
312+
license.rst

0 commit comments

Comments
 (0)
0