Skip to content
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

Clarify the implementation of _process_plot_var_args. #12969

Merged
merged 1 commit into from
Jan 2, 2019

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Dec 10, 2018

Having an argument named kwargs that's not a dict but a tuple is just
a good way to trip the reader. Fortunately _getdefaults and
_setdefaults in only even called with a single kwargs, so we can replace
it with a single non-varargs argument and simplify the code at the same
time.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer added this to the v3.1 milestone Dec 10, 2018
returned dictionary.

If some keys in the property cycle (excluding those in *ignore*) are
absent or set to None in *kw*, return a copy of the next entry in the
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
absent or set to None in *kw*, return a copy of the next entry in the
absent or set to None in the dict *kw*, return a copy of the next entry in the

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

update those other dictionaries with information in defaults if
none of the other dictionaries contains that information.

Add to *kw* the entries in *default* that are absent or set to None in
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Add to *kw* the entries in *default* that are absent or set to None in
Add to the dict *kw* the entries in the dict *default* that are absent or set to None in

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

an empty dictionary. Ignored keys are excluded from the
returned dictionary.

If some keys in the property cycle (excluding those in *ignore*) are
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
If some keys in the property cycle (excluding those in *ignore*) are
If some keys in the property cycle (excluding those in the set *ignore*) are

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed


def _makeline(self, x, y, kw, kwargs):
kw = {**kw, **kwargs} # Don't modify the original kw.
default_dict = self._getdefaults(None, kw)
default_dict = self._getdefaults(set(), kw)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
default_dict = self._getdefaults(set(), kw)
default_dict = self._getdefaults(ignore=set(), kw=kw)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The function is defined just above, and it's not as if you can really guess what it does from the name, so in practice you're going to scroll up and see what it does anyways.

Copy link
Member

Choose a reason for hiding this comment

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

ok.

if all(kw.get(k, None) is None for kw in kwargs):
for kw in kwargs:
kw[k] = defaults[k]
if kw.get(k, None) is None:
Copy link
Member

@timhoffm timhoffm Dec 10, 2018

Choose a reason for hiding this comment

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

thats

kw.update({k: v
           for k, v in defaults.items()
           if kw.get(k) is None})

Not sure if that is more readable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can bet that if anything is writable as a comprehension I will likely have tried doing so. In this case I thought the appearance of kw twice, outside and inside, hinders legibility.

Copy link
Member

@timhoffm timhoffm Dec 10, 2018

Choose a reason for hiding this comment

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

The double appearance in the expression is inherent to the problem. Essentially what we have todto (and what the expression also says):

Update kw
with keys and values from defaults
where the key is not in kw or maps to None

The more I think about it, the more I like the comprehension 😄.

Note also that kw.get(k, None) is the same as kw.get(k).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually I thought the clearest was way to just drop out the Nones first, ie

    def _getdefaults(self, ignore, kw):
        """
        If some keys in the property cycle (excluding those in the set
        *ignore*) are absent or set to None in the dict *kw*, return a copy
        of the next entry in the property cycle, excluding keys in *ignore*.
        Otherwise, don't advance the property cycle, and return an empty dict.
        """
        prop_keys = self._prop_keys - ignore
        keys_no_none = {k for k, v in kw.items() if v is not None}
        if prop_keys - keys_no_none:  # Some keys in prop_keys are not in kw.
            # Copy the default dict to avoid modifying the reference held by
            # the cycler.
            default_dict = next(self.prop_cycler).copy()
            for p in ignore:
                default_dict.pop(p, None)
        else:
            default_dict = {}
        return default_dict

and

    def _setdefaults(self, defaults, kw):
        """
        Add to the dict *kw* the entries in the dict *default* that are absent
        or set to None in *kw*.
        """
        kw_no_none = {k: v for k, v in kw.items() if v is not None}
        kw.clear()
        kw.update(defaults)
        kw.update(kw_no_none)

How does that look to you?

Copy link
Member

@timhoffm timhoffm Dec 10, 2018

Choose a reason for hiding this comment

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

Actually, self._setdefaults(default_dict, kw) is basically kw = {**default_dict, **kw}. So if we can replace the kw instance by a new one (which I suppose we can), we can get rid of the function alltogether. Or you could use a collections.ChainMap.

Copy link
Member

@timhoffm timhoffm Dec 10, 2018

Choose a reason for hiding this comment

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

Better but still a bit verbose. It's less efficient than the comprehension, but with few keywords that probably doesn't matter.

What about replacing the function with:

def _no_none(kw):
    return {k: v for k, v in kw.items() if v is not None}

kw = {**defaults, **_no_none(kw)}

or ChainMap(defaults, _no_none(kw))?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, introducing the _no_none helper makes the whole thing much simpler. Pushed a new version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, that doesn't work: we don't want to drop all the None entries in kw, only those that are present in defaults. Those that are not in defaults should be kept.
(This is needed e.g. for the closed kwarg to fill.)

Returning to the previous version...

Having an argument named `kwargs` that's not a dict but a tuple is just
a good way to trip the reader.  Fortunately _getdefaults and
_setdefaults in only even called with a single kwargs, so we can replace
it with a single non-varargs argument and simplify the code at the same
time.
@anntzer anntzer force-pushed the getsetdefaults branch 2 times, most recently from 3a2abb5 to d0d36cb Compare December 10, 2018 23:32
@@ -391,8 +371,6 @@ def _grab_next_args(self, *args, **kwargs):


class _AxesBase(martist.Artist):
"""
Copy link
Member

Choose a reason for hiding this comment

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

Was this maybe here to prevent docstring inheritance? OTOH, doesn't make much sense since the class is private and thus neither shows up in the HTML docs nor should a user encounter it in an interactive session. So probably fine.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Actually, that doesn't work: we don't want to drop all the None entries in kw, only those that are present in defaults. Those that are not in defaults should be kept.
(This is needed e.g. for the closed kwarg to fill.)

Returning to the previous version...

Good catch. Unfortunately, you're right.

We have sought for better solutions enough. Thus approving as is (and pushing my first-reviewer count 😄 ).

@jklymak jklymak merged commit e8be5f7 into matplotlib:master Jan 2, 2019
@anntzer anntzer deleted the getsetdefaults branch January 2, 2019 22:06
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.

3 participants