Thanks to visit codestin.com
Credit goes to github.com

Skip to content

axes.locator_params fails with LogLocator (and most Locator subclasses) #3658 #4172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 3, 2015
Merged

Conversation

ryanbelt
Copy link
Contributor

set_param() function is now implemented in both Locator and its subtypes, following the suggestions of @tacaswell.
Test cases were implemented for all Locator classes where a set_param() function is added. Raised warnings are also accounted for in Locators that do not implement set_param() (i.e. NullLocator).

@tacaswell tacaswell added this to the next point release milestone Feb 26, 2015
@tacaswell
Copy link
Member

#3658 for github-foo linking

looks good. Thanks for the exhaustive tests!

My only comment is that it might be better if the set_params functions explicitly listed the kwargs they took instead of relying an kwargs. The danger with the way this is implemented is that if the user spells a keyword wrong it just looks like it is being ignored rather than blowing up (which it probably should).

Could you also add an entry in the https://github.com/matplotlib/matplotlib/tree/master/doc/users/whats_new so that this improvement gets properly advertised?

@ryanbelt
Copy link
Contributor Author

The reason for using kwargs was to mirror the set_param() function within MaxNLocator. Would it be prefered if I changed everything to explicitly list arguments?
I'm also curious as to how to explicity list the kwargs. Are there any examples I could take a look at? So far, I have this in mind:

def explicitArgs(self, a=None, b=None):
    if a is not None:
        self.a = a
    if b is not None:
        self.b = b

@tacaswell
Copy link
Member

That is exactly what I had in mind.

Could you also add doc-strings to all of those set_params methods?

You are making a case to also re-work how the existing set_params works 😄 .

The existing implementation is mirroring in part how the Artist version of setp works, but I think we should start moving towards more explicit parameters. The cost is more verbose code, but payoff is more readable code. As implemented, users have no way to sort out what the allowed kwargs to set_params are other than reading the source.

@@ -1002,6 +1008,12 @@ def __init__(self, base, offset):
self._base = base
self.offset = offset

def set_params(self, **kwargs):
if 'base' in kwargs:
self.base = kwargs['base']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has to be _base with an underscore; or remove the underscore elsewhere in this class.

@efiring
Copy link
Member

efiring commented Feb 27, 2015

Maybe some mechanization would help, instead of lots of boilerplate if a is not None: etc.
@tacaswell, are you opposed to **kw in general, or just the case where unrecognized entries are silently ignored?

@efiring
Copy link
Member

efiring commented Feb 27, 2015

@tacaswell, to add a bit: I am thinking about using data structures (e.g. ordered dict) to handle default parameters and parameter documentation. It's hard to look at a bunch of if.. clauses without thinking about how to turn it into a data structure with a function. Maybe it would be overkill for the tickers, though.

@tacaswell
Copy link
Member

I am mostly worried about the silently ignored, one of my co-workers (@ericdill ) has convinced me that doing that is rather user hostile.

I think doing a if (set(kwargs) - set(known_values)) would be good enough.

I agree the if .. block is ugly and would be nice to make into a loop.

We can't use ordereddicts until we drop 2.6 (or take care of the needed shimming).

@leeonadoh
Copy link
Contributor

Thanks for all the input!
I was experimenting with how to implement the if block using a loop, and this is what I ended up with:

    def set_params(self, base=None, subs=None, numdecs=None, numticks=None):
        """Set parameters within this locator."""
        args = locals()
        for key, value in args.iteritems():
            if value is not None:
                setattr(self, key, value)

The restriction is that the argument names must be exactly the same as the class's parameters. Are there better ways? I'm not familiar with locals and setattr, so they're currently giving me a bad gut feeling 😕

@efiring
Copy link
Member

efiring commented Feb 28, 2015

@tacaswell, this is really calling out for a more comprehensive reworking. It doesn't necessarily have to be done now, but we need to make sure that what is done now doesn't constitute an API change that we have to support, if it is not one we will want to support.

MaxNLocator is already headed in the right direction; it has a class-level dictionary of default parameters. I think that every Locator should have this, or at least some class-level data structure. Second, the MaxNLocator model is using set_params in its init as well as for later modification. I think this is important. What it's set_params is missing is merely a section at the top:

        badkw = set(kwargs.keys()) - set(self.default_params.keys())
        if badkw:
            raise ValueError("Invalid keyword arguments: " + ', '.join(badkw))

Third, there should be a get_params() to display the parameters in effect. This could even be called by the __str__() method. Ideally, a single function for each of get_params and __str__ could be defined at the base class level. It would use the keys from default_params to find the corresponding attributes, with or without underscores. It would be nice to have a single convention--either all the parameters are backed by underscore attributes, or by attributes without underscores, but I think even the present messy mixed situation could be handled by a single function, looking first for the underscore version. At least simple versions of set_params could also be also be handled by a single base class method.
All of this could also be transitioned to a property-based interface, in which a params property would look like a dictionary.
Ideally, a single docstring chunk or generator could be used to provide the kw descriptions for __init__ and set_params (or the property).

The general approach could be used in more than just the Locator classes, I suspect; in that case all of the parameter-handling would be in a mixin class.

@tacaswell
Copy link
Member

I think this is starting to converge back to traitlets/IPython configurable/atom classes. We should sort of if we want to use one of the existing solutions to this before we roll our own.

These get/set params function are also what we are going to need for serialization and are what will make doing the style-sheet like functionality possible.

@efiring
Copy link
Member

efiring commented Mar 1, 2015

Yes, I took a quick look at traitlets, but I'm not convinced that they will be appropriate for what we need here. What is the "atom" class?
Several years ago we experimented with using Enthought Traits, but decided against adopting this approach. It seemed to add too much extra complexity--one almost had to learn, and work in, another language. Traitlets are less daunting, but they still involve quite a bit of machinery. They also impose fairly severe restrictions on the types of attributes they can handle, if I understand correctly--unless one adds support for additional types.

@tacaswell
Copy link
Member

https://github.com/nucleic/atom It is the same idea as traits/triatlets
only more so and backed by c++ classes. If you are wary of traits, atom is
even more intrusive.

I suspect that that complexity provides things that we will want and end up
building out eventually (like call backs when you change a value,
programmatic introspection, value validation/normalization).

On Sun, Mar 1, 2015 at 12:14 PM Eric Firing [email protected]
wrote:

Yes, I took a quick look at traitlets, but I'm not convinced that they
will be appropriate for what we need here. What is the "atom" class?
Several years ago we experimented with using Enthought Traits, but decided
against adopting this approach. It seemed to add too much extra
complexity--one almost had to learn, and work in, another language.
Traitlets are less daunting, but they still involve quite a bit of
machinery. They also impose fairly severe restrictions on the types of
attributes they can handle, if I understand correctly--unless one adds
support for additional types.


Reply to this email directly or view it on GitHub
#4172 (comment)
.

@efiring
Copy link
Member

efiring commented Mar 1, 2015

Enaml, for which Atom is built, looks like it might be nice--but it is py 2.7 only, with py 3 being low priority, and its development is Windows-oriented. Atom itself is very new. I conclude that we will not want a dependency on Atom.
Traitlets is one of the ipython components being split out into its own repo as part of the ipython/jupyterhub explosion following the 3.0 release. I'm certainly open to considering it carefully to see how it might help us.

@efiring efiring mentioned this pull request Mar 3, 2015
@efiring
Copy link
Member

efiring commented Mar 3, 2015

@tacaswell, I'm inclined to go along with this PR in its present straightforward form. We already have set_params on one locator, so regardless of what we do in the future, I don't see how we would be worse off having this method for all locators. @ryanbelt, this would involve a rebase and then adding set_params to the new LogitLocator that I just merged.

@leeonadoh
Copy link
Contributor

Alright. I rebased and added set_params with its respective test for LogitLocator as specified by @efiring .

@tacaswell
Copy link
Member

Sounds fine to me. The conversion to something like traitlets is going to
be disruptive and painful, no matter what.

On Tue, Mar 3, 2015, 14:42 leeonadoh [email protected] wrote:

Alright. I rebased and added set_params with its respective test for
LogitLocator as specified by @efiring https://github.com/efiring .


Reply to this email directly or view it on GitHub
#4172 (comment)
.

efiring added a commit that referenced this pull request Mar 3, 2015
 axes.locator_params fails with LogLocator (and most Locator subclasses) #3658
@efiring efiring merged commit c6f7c65 into matplotlib:master Mar 3, 2015
@efiring
Copy link
Member

efiring commented Mar 3, 2015

@leeonadoh Would you make a PR with entries in doc/api/api_changes and doc/users/whats_new for this, please? Each of those directories has a README with instructions, and you can also look at the existing entries as examples.

tacaswell added a commit that referenced this pull request Mar 8, 2015
DOC : Adding 'api_changes' and 'whats_new' docs for PR #4172
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants