Coding guidelines#
We appreciate these guidelines being followed because it improves the readability,consistency, and maintainability of the code base.
API guidelines
If adding new features, changing behavior or function signatures, or removingpublic interfaces, please consult theAPI guidelines.
PEP8, as enforced by ruff#
Formatting should follow the recommendations ofPEP8, as enforced byruff.Matplotlib modifies PEP8 to extend the maximum line length to 88characters. You can check PEP8 compliance from the command line with
python-mpipinstallruffruffcheck/path/to/module.py
or your editor may provide integration with it. To check all files,and fix any errors in-place (where possible) run
ruffcheck--fix
Matplotlib intentionally does not use theblack auto-formatter (1),in particular due to its inability to understand the semantics ofmathematical expressions (2,3).
Package imports#
Import the following modules using the standard scipy conventions:
importnumpyasnpimportnumpy.maasmaimportmatplotlibasmplimportmatplotlib.pyplotaspltimportmatplotlib.cbookascbookimportmatplotlib.patchesasmpatches
In general, Matplotlib modules shouldnot importrcParams
usingfrommatplotlibimportrcParams
, but rather access it asmpl.rcParams
. Thisis because some modules are imported very early, before thercParams
singleton is constructed.
Variable names#
When feasible, please use our internal variable naming convention for objectsof a given class and objects of any child class:
base class | variable | multiples |
---|---|---|
| ||
| ||
|
| |
Generally, denote more than one instance of the same class by adding suffixes tothe variable names. If a format isn't specified in the table, use numbers orletters as appropriate.
Type hints#
If you add new public API or change public API, update or add thecorrespondingmypy type hints.We generally usestub files(*.pyi
) to store the type information; for examplecolors.pyi
containsthe type information forcolors.py
. A notable exception ispyplot.py
,which is type hinted inline.
Type hints can be validated by thestubtest tool, which can be runlocally usingtox-estubtest
and is a part of theAutomated testssuite. Type hints for existing functions are also checked by the mypypre-commit hook.
New modules and files: installation#
If you have added new files or directories, or reorganized existing ones, make sure thenew files are included in the
meson.build
in the corresponding directories.New modulesmay be typed inline or using parallel stub file like existing modules.
C/C++ extensions#
Extensions may be written in C or C++.
Code style should conform to PEP7 (understanding that PEP7 doesn'taddress C++, but most of its admonitions still apply).
Python/C interface code should be kept separate from the core C/C++code. The interface code should be named
FOO_wrap.cpp
orFOO_wrapper.cpp
.Header file documentation (aka docstrings) should be in Numpydocformat. We don't plan on using automated tools for thesedocstrings, and the Numpydoc format is well understood in thescientific Python community.
C/C++ code in the
extern/
directory is vendored, and should be keptclose to upstream whenever possible. It can be modified to fix bugs orimplement new features only if the required changes cannot be made elsewherein the codebase. In particular, avoid making style fixes to it.
Keyword argument processing#
Matplotlib makes extensive use of**kwargs
for pass-through customizationsfrom one function to another. A typical example istext
. The definition ofmatplotlib.pyplot.text
is asimple pass-through tomatplotlib.axes.Axes.text
:
# in pyplot.pydeftext(x,y,s,fontdict=None,**kwargs):returngca().text(x,y,s,fontdict=fontdict,**kwargs)
matplotlib.axes.Axes.text
(simplified for illustration) justpasses allargs
andkwargs
on tomatplotlib.text.Text.__init__
:
# in axes/_axes.pydeftext(self,x,y,s,fontdict=None,**kwargs):t=Text(x=x,y=y,text=s,**kwargs)
andmatplotlib.text.Text.__init__
(again, simplified)just passes them on to thematplotlib.artist.Artist.update
method:
# in text.pydef__init__(self,x=0,y=0,text='',**kwargs):super().__init__()self.update(kwargs)
update
does the work looking for methods named likeset_property
ifproperty
is a keyword argument. i.e., no onelooks at the keywords, they just get passed through the API to theartist constructor which looks for suitably named methods and callsthem with the value.
As a general rule, the use of**kwargs
should be reserved forpass-through keyword arguments, as in the example above. If all thekeyword args are to be used in the function, and not passedon, use the key/value keyword args in the function definition ratherthan the**kwargs
idiom.
In some cases, you may want to consume some keys in the localfunction, and let others pass through. Instead of popping arguments touse off**kwargs
, specify them as keyword-only arguments to the localfunction. This makes it obvious at a glance which arguments will beconsumed in the function. For example, inplot()
,scalex
andscaley
arelocal arguments and the rest are passed on asLine2D()
keyword arguments:
# in axes/_axes.pydefplot(self,*args,scalex=True,scaley=True,**kwargs):lines=[]forlineinself._get_lines(*args,**kwargs):self.add_line(line)lines.append(line)
Using logging for debug messages#
Matplotlib uses the standard Pythonlogging
library to write verbosewarnings, information, and debug messages. Please use it! In all those placesyou writeprint
calls to do your debugging, try usinglogging.debug
instead!
To includelogging
in your module, at the top of the module, you need toimportlogging
. Then calls in your code like:
_log=logging.getLogger(__name__)# right after the imports# code# more code_log.info('Here is some information')_log.debug('Here is some more detailed information')
will log to a logger namedmatplotlib.yourmodulename
.
If an end-user of Matplotlib sets uplogging
to display at levels moreverbose thanlogging.WARNING
in their code with the Matplotlib-providedhelper:
plt.set_loglevel("debug")
or manually with
importlogginglogging.basicConfig(level=logging.DEBUG)importmatplotlib.pyplotasplt
Then they will receive messages like
DEBUG:matplotlib.backends:backend MacOSX version unknownDEBUG:matplotlib.yourmodulename:Here is some informationDEBUG:matplotlib.yourmodulename:Here is some more detailed information
Avoid using pre-computed strings (f-strings
,str.format
,etc.) for logging becauseof security and performance issues, and because they interfere with style handlers. Forexample, use_log.error('hello%s','world')
rather than_log.error('hello{}'.format('world'))
or_log.error(f'hello{s}')
.
Which logging level to use?#
There are five levels at which you can emit messages.
logging.critical
andlogging.error
are really only there for errors thatwill end the use of the library but not kill the interpreter.logging.warning
and_api.warn_external
are used to warn the user,see below.logging.info
is for information that the user may want to know if theprogram behaves oddly. They are not displayed by default. For instance, ifan object isn't drawn because its position isNaN
, that can usuallybe ignored, but a mystified user could calllogging.basicConfig(level=logging.INFO)
and get an error message thatsays why.logging.debug
is the least likely to be displayed, and hence can be themost verbose. "Expected" code paths (e.g., reporting normal intermediatesteps of layouting or rendering) should only log at this level.
By default,logging
displays all log messages at levels higher thanlogging.WARNING
tosys.stderr
.
Thelogging tutorial suggests that the difference betweenlogging.warning
and_api.warn_external
(which useswarnings.warn
) is that_api.warn_external
should be used for things the user must change to stopthe warning (typically in the source), whereaslogging.warning
can be morepersistent. Moreover, note that_api.warn_external
will by default onlyemit a given warningonce for each line of user code, whereaslogging.warning
will display the message every time it is called.
By default,warnings.warn
displays the line of code that has thewarn
call. This usually isn't more informative than the warning message itself.Therefore, Matplotlib uses_api.warn_external
which useswarnings.warn
,but goes up the stack and displays the first line of code outside ofMatplotlib. For example, for the module:
# in my_matplotlib_module.pyimportwarningsdefset_range(bottom,top):ifbottom==top:warnings.warn('Attempting to set identical bottom==top')
running the script:
frommatplotlibimportmy_matplotlib_modulemy_matplotlib_module.set_range(0,0)# set range
will display
UserWarning: Attempting to set identical bottom==topwarnings.warn('Attempting to set identical bottom==top')
Modifying the module to use_api.warn_external
:
frommatplotlibimport_apidefset_range(bottom,top):ifbottom==top:_api.warn_external('Attempting to set identical bottom==top')
and running the same script will display
UserWarning: Attempting to set identical bottom==topmy_matplotlib_module.set_range(0, 0) # set range
Licenses for contributed code#
Matplotlib only uses BSD compatible code. If you bring in code fromanother project make sure it has a PSF, BSD, MIT or compatible license(see the Open Source Initiativelicenses page for details on individuallicenses). If it doesn't, you may consider contacting the author andasking them to relicense it. GPL and LGPL code are not acceptable inthe main code base, though we are considering an alternative way ofdistributing L/GPL code through an separate channel, possibly atoolkit. If you include code, make sure you include a copy of thatcode's license in the license directory if the code's license requiresyou to distribute the license with it. Non-BSD compatible licensesare acceptable in Matplotlib toolkits (e.g., basemap), but make sure youclearly state the licenses you are using.
Why BSD compatible?#
The two dominant license variants in the wild are GPL-style andBSD-style. There are countless other licenses that place specificrestrictions on code reuse, but there is an important difference to beconsidered in the GPL and BSD variants. The best known and perhapsmost widely used license is the GPL, which in addition to granting youfull rights to the source code including redistribution, carries withit an extra obligation. If you use GPL code in your own code, or linkwith it, your product must be released under a GPL compatiblelicense. i.e., you are required to give the source code to otherpeople and give them the right to redistribute it as well. Many of themost famous and widely used open source projects are released underthe GPL, including linux, gcc, emacs and sage.
The second major class are the BSD-style licenses (which includes MITand the python PSF license). These basically allow you to do whateveryou want with the code: ignore it, include it in your own open sourceproject, include it in your proprietary product, sell it,whatever. python itself is released under a BSD compatible license, inthe sense that, quoting from the PSF license page:
ThereisnoGPL-like"copyleft"restriction.Distributingbinary-onlyversionsofPython,modifiedornot,isallowed.Thereisnorequirementtoreleaseanyofyoursourcecode.YoucanalsowriteextensionmodulesforPythonandprovidethemonlyinbinaryform.
Famous projects released under a BSD-style license in the permissivesense of the last paragraph are the BSD operating system, python andTeX.
There are several reasons why early Matplotlib developers selected aBSD compatible license. Matplotlib is a python extension, and wechoose a license that was based on the python license (BSDcompatible). Also, we wanted to attract as many users and developersas possible, and many software companies will not use GPL code insoftware they plan to distribute, even those that are highly committedto open source development, such asenthought, out of legitimate concern that use of theGPL will "infect" their code base by its viral nature. In effect, theywant to retain the right to release some proprietary code. Companiesand institutions who use Matplotlib often make significantcontributions, because they have the resources to get a job done, evena boring one. Two of the Matplotlib backends (FLTK and WX) werecontributed by private companies. The final reason behind thelicensing choice is compatibility with the other python extensions forscientific computing: ipython, numpy, scipy, the enthought tool suiteand python itself are all distributed under BSD compatible licenses.