Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Pyplot update in setup.py #928

Merged
merged 9 commits into from

4 participants

@pelson
Collaborator

It seems that it is currently very easy for the pyplot.py file to get out of date.

I have included an update process in setup.py which always re-builds the pyplot.py file.

@efiring
Owner

Providing a function for building pyplot.py is good, but I don't think it should be run automatically by setup. We don't want the act of installation to modify the code being installed, if it can possibly be avoided. In addition, I don't like the mechanism of switching stdout to grab what is printed when boilerplate is imported. Importing should import capabilities, not generate output.

Suggestion: take all this out of setup.py, and turn the present boilerplate.py into a "make_pyplot.py". The present contents of boilerplate would become a function returning a string; another function would take that string and substitute it in the correct location in pyplot.py, as you are doing; and the whole thing would end with the usual "if name == 'main':" block to scriptify it.

@mdboom
Owner

I actually don't have a problem with code being generated by setup.py. It already happens, for example, when running 2to3 over all of the code when installing on Python 3, not to mention all of the C++ code that gets compiled. I personally prefer that over an extra step that we need to remember to run.

However, there are some things about this approach that could be improved.

This is putting generated code in the git repository (pyplot.py is in the repo), so it's possible that just the act of building matplotlib could cause unexpected git merge conflicts on the next git pull. The source and build products need to be clearly separated in the directory tree.

Here's what I would propose:

1) Leave the manually written part of pyplot.py where it is, and then add from .pyplot_generated.py import * at the end.

2) Put the generated stuff in pyplot_generated.py -- but this file should only be in the build tree so as not to pollute the source tree. There is a way to query distutils for the build directory (usually build/lib...), and it should go there.

3) This file should only be run during distutils' build step, otherwise it will get run even if the user does setup.py --help.

4) The file should only be written to disk if the contents are different from the file already on disk (if any). This will prevent unnecessary copies and rebuilds.

5) I agree with @efiring that boilerplate.py should just have a function that returns a string, or alternatively be a function that takes an output stream and writes to that. Importing alone shouldn't write things to stdout.

@efiring
Owner

I'm still a bit uncomfortable with this, but I will get used to it. But please, where you have the "from .pyplot_generated.py..." line, include a comment to the effect that this is generated in the build directory at build time, so some poor soul doesn't waste an hour hunting around for it in the git repo or tarball. And it probably should have a leading underscore to help drive home the point.

@mdboom
Owner

@efiring: good point about the comment and leading underscore.

@pelson
Collaborator

Thanks to you both. I was hoping for a quick win by improving some stuff re boilerplating without having to change all of it, but clearly there are some un-ignorable warts :-) .

We don't want the act of installation to modify the code being installed, if it can possibly be avoided.

I don't really have a good response to this statement. Your absolutely correct. A standard user could do an install and end up with a different pyplot to the one we thought we had released (admittedly this is not very likely). On the other hand, pyplot is currently not being kept perfectly up to date and that is unlikely to change even if there was a simple script for a developer to run. I guess one approach could be to put a git hook inplace which runs the generate_pyplot script on commit.

1) I'm not a fan of having the extra file and doing a import *. With regards to your specific concern, conflicts should be simple to resolve: the first part of pyplot would use the normal git way and conflicts to the second part could simply be ignored by re-building using setup.py.

The source and build products need to be clearly separated in the directory tree.

In general, I agree with this statement. Indeed, I started off by having the leading part of pyplot in the src directory, but I felt that it added yet another thing to remember for very little benefit.

3) & 4) Will do.

5) We all agree that boilerplate.py shouldn't be doing prints on the top level. I will implement this, but it will make things noisier/longer to review.

From this point I'm considering taking the easy answer and creating a script as @efiring suggested. This will at least make building pyplot easier, but it does leave the outstanding issue of keeping the content up-to-date.

@mdboom
Owner

@pelson: github doesn't allow custom git hooks, so that's out.

Generated code should never go in the git repository -- unfortunately it has been that way for a long time in the case of pyplot.py, but since the generation of that file was manually controlled (i.e. not as part of setup.py), the problems with that approach were minimized -- you have to consciously know what you're doing. The problem with the approach in this PR is that you could have a particular version of pyplot.py checked into repo, a user checks it out, installs matplotlib and pyplot.py gets changed -- this could be as simple as updating the timestamp or perhaps dictionary ordering causes the elements to come out in a different order than before. Either way, now the user has a file that git considers "changed", yet she did nothing active where she would expect a change. Doing a git diff shows changes. Doing a git pull after this happens could result in a merge conflict. That could be alarming for someone who is just tracking the latest updates using git. It's true that setup.py would overwrite the correct contents, but git still forces you to mark the merge conflict as resolved using git add. I just see that as being a major pain for users and developers alike.

In case there was some confusion -- the leading part of pyplot.py would stay where it is in lib/matplotlib, not be moved to the src directory -- that is for C/C++ code. It would only be the generated parts that would be in a different place -- in build/lib.../matplotlib, which will ultimately be installed alongside all the source code from lib/matplotlib.

I agree that creating a separate script is a reasonable first step, and worthwhile work even if it gets automated in setup.py down the road.

@efiring
Owner

There are different kinds of generated code, and some belong in the repo, some don't. For example, cython-generated C-code typically is in the repo, for good reason. Its generation should be under developer control, not installer or user control.
pyplot boilerplate could go either way. I think the reasonable alternatives are (1) a script that a developer uses to rebuild it when warranted, with the code always being in the repo, or (2) automatic building at build time of code that is not in the repo.

@pelson
Collaborator

I have updated the boilerplate script. Unfortunately, the diffs look bigger than they are due to simple indentation. Fortunately, pyplot has changed very little, so that should give confidence that boilerplate is still doing what it was originally.

@efiring
Owner

This does look like a step in the right direction. I like it. @mdboom, are there any potential 2to3 or other version issues involved in the use of file() and binary versus text mode, for example? Does this need to be tested with git on Windows? (I don't know who would do that.)

@mdboom
Owner

It would be nice (though not entirely necessary) for boilerplate to run under Python 3. At the moment, it doesn't, since it doesn't get run through 2to3.

>python3 boilerplate.py 
  File "boilerplate.py", line 171
    raise ValueError, ('default value %s unknown to boilerplate.formatvalue'
                    ^
SyntaxError: invalid syntax

As @efiring points out, the calls to file should be replaced by open. It doesn't necessarily have to open the files in binary mode -- testing will tell.

This approach (which isn't running in setup.py anymore) shouldn't have any issues wrt to git on Windows, unless I'm missing something.

@pelson
Collaborator

That makes things a little neater. There are still some improvements that could be made, but I am resisting the temptation.

The motivation for this whole PR is so that I can submit a small change to the content of pyplot. However I am reluctant to include it here as it would make the review harder.

@pelson pelson commented on the diff
boilerplate.py
((22 lines not shown))
import inspect
import random
-import re
-import sys
import types
# import the local copy of matplotlib, not the installed one
#sys.path.insert(0, './lib')
from matplotlib.axes import Axes
@pelson Collaborator
pelson added a note

Notice that this line probably puts the scuppers on including the boilerplate script in setup.py (at least before building the c code) in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mdboom
Owner

I've filed a pull request against your branch that adds Python 3 compatibility for boilerplate.py.

One other thing while we're in here -- maybe we should add a paragraph to the developer docs about this? I can't seem to find it if it already exists.

Other than that, I think this is good to go and is a worthwhile improvement.

Phil Elson and others added some commits
Phil Elson Merge pull request #2 from mdboom/pyplot_build-py3
Add Python 3 compatiblity to boilerplate.py
501c18b
@pelson pelson Coding guide pyplot update. 7b1e887
@pelson pelson Merge branch 'pyplot_build' of github.com:pelson/matplotlib into pypl…
…ot_build

* 'pyplot_build' of github.com:pelson/matplotlib:
  Make boilerplate.py Python 3 compatible
8bc84e6
@pelson
Collaborator

Added some docs to the developer guide. I have built them and it all renders nicely.

doc/devel/coding_guide.rst
@@ -349,6 +349,17 @@ object::
print 'datafile', datafile
+Writing a new pyplot function
+=============================
+
+The :mod:`matplotlib.pyplot` module can be thought of as combination of some
+manually written and some autogenerated functions. Modifications can be made
+to pyplot where the function definition does not explicitly state otherwise
+and autogenerated functions can be modified by changing, and subsequently
+runnning, the :file:`boilerplate.py` script which will re-generate the complete
@mdboom Owner
mdboom added a note

What changes to boilerplate.py are required?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
doc/devel/coding_guide.rst
@@ -349,6 +349,17 @@ object::
print 'datafile', datafile
+Writing a new pyplot function
+=============================
+
+The :mod:`matplotlib.pyplot` module can be thought of as combination of some
+manually written and some autogenerated functions. Modifications can be made
+to pyplot where the function definition does not explicitly state otherwise
@mdboom Owner
mdboom added a note

otherwise than what?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pelson
Collaborator

I was trying to keep it succinct and to avoid any redundancy by documenting the boilerplate mechanism in two places (here and in boilerplate.py itself). It was my intention to ensure that a developer was aware of boilerplate.py without fully documenting its functionality here. If you've got a feeling as to what you would like to see here, please let me know.

@jdh2358
Owner

The perfect is the enemy of the good, so I have no problem accepting an improved boilerplate.py solution. But the real solution is to probably just get rid of it. As discussed in the thread linked below, when I wrote boilerplate.py there were already code magic solutions in python2.4 that could have avoided the need for boilerplate.py

http://groups.google.com/group/comp.lang.python/browse_frm/thread/dcd63ec13096a0f6/1b14640f3a4ad3dc?pli=1

But I was supporting python 2.2 at the time so I just did the brute force thing. There is now so much magic cleverness in python function generation that I expect we could do away with it entirely,

@pelson
Collaborator

But the real solution is to probably just get rid of it. As discussed in the thread linked below, when I wrote boilerplate.py there were already code magic solutions in python2.4 that could have avoided the need for boilerplate.py.

Technically, as you say, there is a far more elegant solution using new which would avoid the need for the boilerplate script. However, one of the biggest benefits of the boilerplate script is that it generates python code which a novice user could understand with very little effort; I am not confident that providing a "code magic" solution would be able to maintain such simple and readable code (in the user facing pyplot.py).

@mdboom: Do you have any suggestions for the documentation in the developer guide?

@mdboom
Owner

How about something like:

Large portions of the pyplot interface is automatically generated by the `boilerplate.py` script (in the root of the source tree).  To add or remove a plotting method from pyplot, edit the appropriate list in `boilerplate.py` and then run the script.  The new versions of `boilerplate.py` and `lib/matplotlib/pyplot.py` should be checked into git.

I think John makes a good point about removing code generation in the future if possible. I don't think it would be terribly complex, and __new__ won't be needed. As far as I can tell, boilerplate.py on generates functions. This is easy enough to do with nested functions, which while somewhat unusual are not terribly difficult relative to metaclasses.

@pelson
Collaborator

@mdboom I've used your content and tweaked a little.

@mdboom
Owner

Looks good, thanks!

@pelson
Collaborator

I think this resolves all of the issues. Unless anyone objects, I will merge in a couple of hours.

@pelson pelson merged commit 45493c4 into matplotlib:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 6, 2012
  1. @pelson

    Auto build pyplot from setup.

    pelson authored
  2. @pelson

    Tidied message in pyplot

    pelson authored
Commits on Jun 7, 2012
  1. @pelson
Commits on Jun 8, 2012
  1. @pelson
  2. @mdboom
  3. Merge pull request #2 from mdboom/pyplot_build-py3

    Phil Elson authored
    Add Python 3 compatiblity to boilerplate.py
  4. @pelson

    Coding guide pyplot update.

    pelson authored
  5. @pelson

    Merge branch 'pyplot_build' of github.com:pelson/matplotlib into pypl…

    pelson authored
    …ot_build
    
    * 'pyplot_build' of github.com:pelson/matplotlib:
      Make boilerplate.py Python 3 compatible
Commits on Jun 11, 2012
  1. @pelson
This page is out of date. Refresh to see the latest.
View
443 boilerplate.py
@@ -1,25 +1,45 @@
-# Wrap the plot commands defined in axes. The code generated by this
-# file is pasted into pyplot.py. We did try to do this the smart way,
+"""
+Script to autogenerate pyplot wrappers.
+
+When this script is run, the current contents of pyplot are
+split into generatable and non-generatable content (via the magic header
+:attr:`PYPLOT_MAGIC_HEADER`) and the generatable content is overwritten.
+Hence, the non-generatable content should be edited in the pyplot.py file
+itself, whereas the generatable content must be edited via templates in
+this file.
+
+"""
+# We did try to do the wrapping the smart way,
# with callable functions and new.function, but could never get the
# docstrings right for python2.2. See
# http://groups.google.com/group/comp.lang.python/browse_frm/thread/dcd63ec13096a0f6/1b14640f3a4ad3dc?#1b14640f3a4ad3dc
# For some later history, see
# http://thread.gmane.org/gmane.comp.python.matplotlib.devel/7068
+import os
import inspect
import random
-import re
-import sys
import types
# import the local copy of matplotlib, not the installed one
#sys.path.insert(0, './lib')
from matplotlib.axes import Axes
@pelson Collaborator
pelson added a note

Notice that this line probably puts the scuppers on including the boilerplate script in setup.py (at least before building the c code) in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
-from matplotlib.cbook import dedent
-_fmtplot = """\
+
+# this is the magic line that must exist in pyplot, after which the boilerplate content will be
+# appended
+PYPLOT_MAGIC_HEADER = '################# REMAINING CONTENT GENERATED BY boilerplate.py ##############\n'
+
+PYPLOT_PATH = os.path.join(os.path.dirname(__file__), 'lib',
+ 'matplotlib', 'pyplot.py')
+
+
+AUTOGEN_MSG = """
# This function was autogenerated by boilerplate.py. Do not edit as
-# changes will be lost
+# changes will be lost"""
+
+
+PLOT_TEMPLATE = AUTOGEN_MSG + """
@autogen_docstring(Axes.%(func)s)
def %(func)s(%(argspec)s):
%(ax)s = gca()
@@ -37,9 +57,9 @@ def %(func)s(%(argspec)s):
return %(ret)s
"""
-_fmtmisc = """\
-# This function was autogenerated by boilerplate.py. Do not edit as
-# changes will be lost
+
+# Used for misc functions such as cla/legend etc.
+MISC_FN_TEMPLATE = AUTOGEN_MSG + """
@docstring.copy_dedent(Axes.%(func)s)
def %(func)s(%(argspec)s):
%(ret)s = gca().%(func)s(%(call)s)
@@ -47,206 +67,227 @@ def %(func)s(%(argspec)s):
return %(ret)s
"""
-# these methods are all simple wrappers of Axes methods by the same
-# name.
-_plotcommands = (
- 'acorr',
- 'arrow',
- 'axhline',
- 'axhspan',
- 'axvline',
- 'axvspan',
- 'bar',
- 'barh',
- 'broken_barh',
- 'boxplot',
- 'cohere',
- 'clabel',
- 'contour',
- 'contourf',
- 'csd',
- 'errorbar',
- 'fill',
- 'fill_between',
- 'fill_betweenx',
- 'hexbin',
- 'hist',
- 'hist2d',
- 'hlines',
- 'imshow',
- 'loglog',
- 'pcolor',
- 'pcolormesh',
- 'pie',
- 'plot',
- 'plot_date',
- 'psd',
- 'quiver',
- 'quiverkey',
- 'scatter',
- 'semilogx',
- 'semilogy',
- 'specgram',
- #'spy',
- 'stem',
- 'step',
- 'streamplot',
- 'tricontour',
- 'tricontourf',
- 'tripcolor',
- 'triplot',
- 'vlines',
- 'xcorr',
- 'barbs',
- )
-_misccommands = (
- 'cla',
- 'grid',
- 'legend',
- 'table',
- 'text',
- 'annotate',
- 'ticklabel_format',
- 'locator_params',
- 'tick_params',
- 'margins',
- 'autoscale',
- )
-
-cmappable = {
- 'contour' : 'if %(ret)s._A is not None: sci(%(ret)s)',
- 'contourf': 'if %(ret)s._A is not None: sci(%(ret)s)',
- 'hexbin' : 'sci(%(ret)s)',
- 'scatter' : 'sci(%(ret)s)',
- 'pcolor' : 'sci(%(ret)s)',
- 'pcolormesh': 'sci(%(ret)s)',
- 'hist2d' : 'sci(%(ret)s[-1])',
- 'imshow' : 'sci(%(ret)s)',
- #'spy' : 'sci(%(ret)s)', ### may return image or Line2D
- 'quiver' : 'sci(%(ret)s)',
- 'specgram' : 'sci(%(ret)s[-1])',
- 'streamplot' : 'sci(%(ret)s)',
- 'tricontour' : 'if %(ret)s._A is not None: sci(%(ret)s)',
- 'tricontourf': 'if %(ret)s._A is not None: sci(%(ret)s)',
- 'tripcolor' : 'sci(%(ret)s)',
-
-}
-
-def format_value(value):
- """
- Format function default values as needed for inspect.formatargspec.
- The interesting part is a hard-coded list of functions used
- as defaults in pyplot methods.
- """
- if isinstance(value, types.FunctionType):
- if value.func_name in ('detrend_none', 'window_hanning'):
- return '=mlab.' + value.func_name
- if value.func_name == 'mean':
- return '=np.' + value.func_name
- raise ValueError, ('default value %s unknown to boilerplate.formatvalue'
- % value)
- return '='+repr(value)
-
-def remove_final_whitespace(string):
- """
- Return a copy of *string* with final whitespace removed from each line.
- """
- return '\n'.join(x.rstrip() for x in string.split('\n'))
-
-for fmt,cmdlist in (_fmtplot,_plotcommands),(_fmtmisc,_misccommands):
- for func in cmdlist:
- # For some commands, an additional line is needed to set the
- # color map
- if func in cmappable:
- mappable = cmappable[func] % locals()
- else:
- mappable = ''
-
- # Get argspec of wrapped function
- args, varargs, varkw, defaults = inspect.getargspec(getattr(Axes, func))
- args.pop(0) # remove 'self' argument
- if defaults is None:
- defaults = ()
-
- # How to call the wrapped function
- call = map(str, args)
- if varargs is not None:
- call.append('*'+varargs)
- if varkw is not None:
- call.append('**'+varkw)
- call = ', '.join(call)
-
- # Add a hold keyword argument if needed (fmt is _fmtplot) and
- # possible (if *args is used, we can't just add a hold
- # argument in front of it since it would gobble one of the
- # arguments the user means to pass via *args)
- if varargs:
- sethold = "hold = %(varkw)s.pop('hold', None)" % locals()
- elif fmt is _fmtplot:
- args.append('hold')
- defaults = defaults + (None,)
- sethold = ''
-
- # Now we can build the argspec for defining the wrapper
- argspec = inspect.formatargspec(args, varargs, varkw, defaults,
- formatvalue=format_value)
- argspec = argspec[1:-1] # remove parens
-
- # A gensym-like facility in case some function takes an
- # argument named washold, ax, or ret
- washold,ret,ax = 'washold', 'ret', 'ax'
- bad = set(args) | set((varargs, varkw))
- while washold in bad or ret in bad or ax in bad:
- washold = 'washold' + str(random.randrange(10**12))
- ret = 'ret' + str(random.randrange(10**12))
- ax = 'ax' + str(random.randrange(10**12))
-
- # Since we can't avoid using some function names,
- # bail out if they are used as argument names
- for reserved in ('gca', 'gci', 'draw_if_interactive'):
- if reserved in bad:
- raise ValueError, \
- 'Axes method %s has kwarg named %s' % (func, reserved)
-
- print remove_final_whitespace(fmt%locals())
-
-# define the colormap functions
-_fmtcmap = """\
-# This function was autogenerated by boilerplate.py. Do not edit as
-# changes will be lost
-def %(name)s():
+# Used for colormap functions
+CMAP_TEMPLATE = AUTOGEN_MSG + """
+def {name}():
'''
- set the default colormap to %(name)s and apply to current image if any.
+ set the default colormap to {name} and apply to current image if any.
See help(colormaps) for more information
'''
- rc('image', cmap='%(name)s')
+ rc('image', cmap='{name}')
im = gci()
if im is not None:
- im.set_cmap(cm.%(name)s)
+ im.set_cmap(cm.{name})
draw_if_interactive()
"""
-cmaps = (
- 'autumn',
- 'bone',
- 'cool',
- 'copper',
- 'flag',
- 'gray' ,
- 'hot',
- 'hsv',
- 'jet' ,
- 'pink',
- 'prism',
- 'spring',
- 'summer',
- 'winter',
- 'spectral'
-)
-# add all the colormaps (autumn, hsv, ....)
-for name in cmaps:
- print _fmtcmap%locals()
+def boilerplate_gen():
+ """Generator of lines for the automated part of pyplot."""
+
+ # these methods are all simple wrappers of Axes methods by the same
+ # name.
+ _plotcommands = (
+ 'acorr',
+ 'arrow',
+ 'axhline',
+ 'axhspan',
+ 'axvline',
+ 'axvspan',
+ 'bar',
+ 'barh',
+ 'broken_barh',
+ 'boxplot',
+ 'cohere',
+ 'clabel',
+ 'contour',
+ 'contourf',
+ 'csd',
+ 'errorbar',
+ 'fill',
+ 'fill_between',
+ 'fill_betweenx',
+ 'hexbin',
+ 'hist',
+ 'hist2d',
+ 'hlines',
+ 'imshow',
+ 'loglog',
+ 'pcolor',
+ 'pcolormesh',
+ 'pie',
+ 'plot',
+ 'plot_date',
+ 'psd',
+ 'quiver',
+ 'quiverkey',
+ 'scatter',
+ 'semilogx',
+ 'semilogy',
+ 'specgram',
+ #'spy',
+ 'stem',
+ 'step',
+ 'streamplot',
+ 'tricontour',
+ 'tricontourf',
+ 'tripcolor',
+ 'triplot',
+ 'vlines',
+ 'xcorr',
+ 'barbs',
+ )
+
+ _misccommands = (
+ 'cla',
+ 'grid',
+ 'legend',
+ 'table',
+ 'text',
+ 'annotate',
+ 'ticklabel_format',
+ 'locator_params',
+ 'tick_params',
+ 'margins',
+ 'autoscale',
+ )
+
+ cmappable = {
+ 'contour' : 'if %(ret)s._A is not None: sci(%(ret)s)',
+ 'contourf': 'if %(ret)s._A is not None: sci(%(ret)s)',
+ 'hexbin' : 'sci(%(ret)s)',
+ 'scatter' : 'sci(%(ret)s)',
+ 'pcolor' : 'sci(%(ret)s)',
+ 'pcolormesh': 'sci(%(ret)s)',
+ 'hist2d' : 'sci(%(ret)s[-1])',
+ 'imshow' : 'sci(%(ret)s)',
+ #'spy' : 'sci(%(ret)s)', ### may return image or Line2D
+ 'quiver' : 'sci(%(ret)s)',
+ 'specgram' : 'sci(%(ret)s[-1])',
+ 'streamplot' : 'sci(%(ret)s)',
+ 'tricontour' : 'if %(ret)s._A is not None: sci(%(ret)s)',
+ 'tricontourf': 'if %(ret)s._A is not None: sci(%(ret)s)',
+ 'tripcolor' : 'sci(%(ret)s)',
+
+ }
+
+ def format_value(value):
+ """
+ Format function default values as needed for inspect.formatargspec.
+ The interesting part is a hard-coded list of functions used
+ as defaults in pyplot methods.
+ """
+ if isinstance(value, types.FunctionType):
+ if value.__name__ in ('detrend_none', 'window_hanning'):
+ return '=mlab.' + value.__name__
+ if value.__name__ == 'mean':
+ return '=np.' + value.__name__
+ raise ValueError(('default value %s unknown to boilerplate.' + \
+ 'formatvalue') % value)
+ return '='+repr(value)
+
+ for fmt, cmdlist in [(PLOT_TEMPLATE, _plotcommands),
+ (MISC_FN_TEMPLATE, _misccommands)]:
+ for func in cmdlist:
+ # For some commands, an additional line is needed to set the
+ # color map
+ if func in cmappable:
+ mappable = cmappable[func] % locals()
+ else:
+ mappable = ''
+
+ # Get argspec of wrapped function
+ args, varargs, varkw, defaults = inspect.getargspec(getattr(Axes, func))
+ args.pop(0) # remove 'self' argument
+ if defaults is None:
+ defaults = ()
+
+ # How to call the wrapped function
+ call = list(map(str, args))
+ if varargs is not None:
+ call.append('*'+varargs)
+ if varkw is not None:
+ call.append('**'+varkw)
+ call = ', '.join(call)
+
+ # Add a hold keyword argument if needed (fmt is PLOT_TEMPLATE) and
+ # possible (if *args is used, we can't just add a hold
+ # argument in front of it since it would gobble one of the
+ # arguments the user means to pass via *args)
+ if varargs:
+ sethold = "hold = %(varkw)s.pop('hold', None)" % locals()
+ elif fmt is PLOT_TEMPLATE:
+ args.append('hold')
+ defaults = defaults + (None,)
+ sethold = ''
+
+ # Now we can build the argspec for defining the wrapper
+ argspec = inspect.formatargspec(args, varargs, varkw, defaults,
+ formatvalue=format_value)
+ argspec = argspec[1:-1] # remove parens
+
+ # A gensym-like facility in case some function takes an
+ # argument named washold, ax, or ret
+ washold, ret, ax = 'washold', 'ret', 'ax'
+ bad = set(args) | set((varargs, varkw))
+ while washold in bad or ret in bad or ax in bad:
+ washold = 'washold' + str(random.randrange(10**12))
+ ret = 'ret' + str(random.randrange(10**12))
+ ax = 'ax' + str(random.randrange(10**12))
+
+ # Since we can't avoid using some function names,
+ # bail out if they are used as argument names
+ for reserved in ('gca', 'gci', 'draw_if_interactive'):
+ if reserved in bad:
+ msg = 'Axes method %s has kwarg named %s' % (func, reserved)
+ raise ValueError(msg)
+
+ yield fmt % locals()
+
+ cmaps = (
+ 'autumn',
+ 'bone',
+ 'cool',
+ 'copper',
+ 'flag',
+ 'gray' ,
+ 'hot',
+ 'hsv',
+ 'jet' ,
+ 'pink',
+ 'prism',
+ 'spring',
+ 'summer',
+ 'winter',
+ 'spectral'
+ )
+ # add all the colormaps (autumn, hsv, ....)
+ for name in cmaps:
+ yield CMAP_TEMPLATE.format(name=name)
+
+
+def build_pyplot():
+ pyplot_path = os.path.join(os.path.dirname(__file__), 'lib',
+ 'matplotlib', 'pyplot.py')
+
+ pyplot_orig = open(pyplot_path, 'r').readlines()
+
+
+ try:
+ pyplot_orig = pyplot_orig[:pyplot_orig.index(PYPLOT_MAGIC_HEADER)+1]
+ except IndexError:
+ raise ValueError('The pyplot.py file *must* have the exact line: %s' % PYPLOT_MAGIC_HEADER)
+
+ pyplot = open(pyplot_path, 'w')
+ pyplot.writelines(pyplot_orig)
+ pyplot.write('\n')
+
+ pyplot.writelines(boilerplate_gen())
+
+
+if __name__ == '__main__':
+ # Write the matplotlib.pyplot file
+ build_pyplot()
View
11 doc/devel/coding_guide.rst
@@ -323,7 +323,7 @@ Writing examples
================
We have hundreds of examples in subdirectories of
-file:`matplotlib/examples`, and these are automatically
+:file:`matplotlib/examples`, and these are automatically
generated when the website is built to show up both in the `examples
<http://matplotlib.sourceforge.net/examples/index.html>`_ and `gallery
<http://matplotlib.sourceforge.net/gallery.html>`_ sections of the
@@ -349,6 +349,15 @@ object::
print 'datafile', datafile
+Writing a new pyplot function
+=============================
+A large portion of the pyplot interface is automatically generated by the
+`boilerplate.py` script (in the root of the source tree). To add or remove
+a plotting method from pyplot, edit the appropriate list in `boilerplate.py`
+and then run the script which will update the content in
+`lib/matplotlib/pyplot.py`. Both the changes in `boilerplate.py` and
+`lib/matplotlib/pyplot.py` should be checked into the repository.
+
Testing
=======
View
133 lib/matplotlib/pyplot.py
@@ -1,3 +1,5 @@
+# Note: The first part of this file can be modified in place, but the latter part
+# is autogenerated by the boilerplate.py script.
"""
Provides a MATLAB-like plotting framework.
@@ -2220,6 +2222,10 @@ def spy(Z, precision=0, marker=None, markersize=None, aspect='equal', hold=None,
sci(ret)
return ret
+
+################# REMAINING CONTENT GENERATED BY boilerplate.py ##############
+
+
# This function was autogenerated by boilerplate.py. Do not edit as
# changes will be lost
@autogen_docstring(Axes.acorr)
@@ -2227,7 +2233,7 @@ def acorr(x, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2235,7 +2241,7 @@ def acorr(x, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2245,7 +2251,7 @@ def arrow(x, y, dx, dy, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2253,7 +2259,7 @@ def arrow(x, y, dx, dy, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2263,7 +2269,7 @@ def axhline(y=0, xmin=0, xmax=1, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2271,7 +2277,7 @@ def axhline(y=0, xmin=0, xmax=1, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2281,7 +2287,7 @@ def axhspan(ymin, ymax, xmin=0, xmax=1, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2289,7 +2295,7 @@ def axhspan(ymin, ymax, xmin=0, xmax=1, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2299,7 +2305,7 @@ def axvline(x=0, ymin=0, ymax=1, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2307,7 +2313,7 @@ def axvline(x=0, ymin=0, ymax=1, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2317,7 +2323,7 @@ def axvspan(xmin, xmax, ymin=0, ymax=1, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2325,7 +2331,7 @@ def axvspan(xmin, xmax, ymin=0, ymax=1, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2335,7 +2341,7 @@ def bar(left, height, width=0.8, bottom=None, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2343,7 +2349,7 @@ def bar(left, height, width=0.8, bottom=None, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2353,7 +2359,7 @@ def barh(bottom, width, height=0.8, left=None, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2361,7 +2367,7 @@ def barh(bottom, width, height=0.8, left=None, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2371,7 +2377,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2379,7 +2385,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2389,7 +2395,7 @@ def boxplot(x, notch=0, sym='b+', vert=1, whis=1.5, positions=None, widths=None,
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2397,7 +2403,7 @@ def boxplot(x, notch=0, sym='b+', vert=1, whis=1.5, positions=None, widths=None,
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2407,7 +2413,7 @@ def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.wi
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2415,7 +2421,7 @@ def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.wi
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2433,7 +2439,7 @@ def clabel(CS, *args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2479,7 +2485,7 @@ def csd(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.windo
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2487,7 +2493,7 @@ def csd(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.windo
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2497,7 +2503,7 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None,
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2505,7 +2511,7 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None,
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2523,7 +2529,7 @@ def fill(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2533,7 +2539,7 @@ def fill_between(x, y1, y2=0, where=None, interpolate=False, hold=None, **kwargs
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2541,7 +2547,7 @@ def fill_between(x, y1, y2=0, where=None, interpolate=False, hold=None, **kwargs
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2551,7 +2557,7 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2559,7 +2565,7 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2569,7 +2575,7 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', yscale='linea
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2587,7 +2593,7 @@ def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, b
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2595,7 +2601,7 @@ def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, b
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2605,7 +2611,7 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, cma
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2623,7 +2629,7 @@ def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, *
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2631,7 +2637,7 @@ def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, *
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2641,7 +2647,7 @@ def imshow(X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None,
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2667,7 +2673,7 @@ def loglog(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2713,7 +2719,7 @@ def pie(x, explode=None, labels=None, colors=None, autopct=None, pctdistance=0.6
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2721,7 +2727,7 @@ def pie(x, explode=None, labels=None, colors=None, autopct=None, pctdistance=0.6
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2739,7 +2745,7 @@ def plot(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2749,7 +2755,7 @@ def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, **kwa
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2757,7 +2763,7 @@ def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, **kwa
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2767,7 +2773,7 @@ def psd(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_h
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2775,7 +2781,7 @@ def psd(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_h
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2811,7 +2817,7 @@ def quiverkey(*args, **kw):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2821,7 +2827,7 @@ def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2847,7 +2853,7 @@ def semilogx(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2865,7 +2871,7 @@ def semilogy(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2875,7 +2881,7 @@ def specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.win
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2893,7 +2899,7 @@ def stem(x, y, linefmt='b-', markerfmt='bo', basefmt='r-', bottom=None, label=No
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -2901,7 +2907,7 @@ def stem(x, y, linefmt='b-', markerfmt='bo', basefmt='r-', bottom=None, label=No
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -2919,17 +2925,17 @@ def step(x, y, *args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
# changes will be lost
@autogen_docstring(Axes.streamplot)
-def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.10000000000000001, hold=None):
+def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, hold=None):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -3009,7 +3015,7 @@ def triplot(*args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -3019,7 +3025,7 @@ def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, *
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -3027,7 +3033,7 @@ def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, *
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -3037,7 +3043,7 @@ def xcorr(x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, maxlags=
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+
if hold is not None:
ax.hold(hold)
try:
@@ -3045,7 +3051,7 @@ def xcorr(x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, maxlags=
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -3063,7 +3069,7 @@ def barbs(*args, **kw):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -3378,4 +3384,3 @@ def spectral():
im.set_cmap(cm.spectral)
draw_if_interactive()
-
View
3  setup.py
@@ -1,5 +1,5 @@
"""
-You will need to have freetype, libpng and zlib installed to comile
+You will need to have freetype, libpng and zlib installed to compile
matplotlib, inlcuding the *-devel versions of these libraries if you
are using a package manager like RPM or debian.
@@ -248,6 +248,7 @@ def add_dateutil():
template = template.replace(" use = 'Agg'", " use = '%s'"%rc['backend'])
open('lib/matplotlib/mpl-data/matplotlib.conf', 'w').write(template)
+
try: additional_params # has setupegg.py provided
except NameError: additional_params = {}
Something went wrong with that request. Please try again.