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

Skip to content

Improve alias signatures #11939

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 1 commit into from
Aug 26, 2018
Merged

Improve alias signatures #11939

merged 1 commit into from
Aug 26, 2018

Conversation

timhoffm
Copy link
Member

PR Summary

Until now, all alias methods had the signature *args, **kwargs. This is not very helpful as it's unclear which arguments are expected (well, usually none for getters and one for setters, but the alias method does not tell).

This PR uses inspect to copy the signatures of the original functions to the alias.

Example setter

before:
grafik

now:
grafik

Example getter

before:
grafik

now:
grafik

@timhoffm timhoffm added this to the v3.1 milestone Aug 26, 2018
@@ -1914,20 +1914,28 @@ class so far, an alias named ``get_alias`` will be defined; the same will
if cls is None: # Return the actual class decorator.
return functools.partial(_define_aliases, alias_d)

def make_alias(name): # Enforce a closure over *name*.
def method(self, *args, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

Just add a @functools.wraps(getattr(cls, name)) decorator here? inspect.signature will then get the signature from the wrappee. Note that you can even play with the assigned kwarg to functools.wraps if you don't want to update the name...

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the hint. That would work too.

The getattr-alias is a bit slower than my explictly coded lambda (67ns vs. 26ns). I mean, it's not much in absolute values, but the aliases might be used a lot by users. Do we want the added speedup or should we stay with the slightly simpler but slower getattr implementiation.

Copy link
Contributor

@anntzer anntzer Aug 26, 2018

Choose a reason for hiding this comment

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

No strong opinion there; though eval() whill likely have a small extra startup cost as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

There are 60 properties. The package import time does not measureably change between the two approaches (200.8 +/- 6.0ms vs. 201.0 +/- 6.0ms).

Probably both approaches are fast enough in startup as well as runtime use to not make a difference. I will go back to decorating the original function to keep it simpler. If runtime performance is really super-critical, users should just use the methods functions, not the aliases.

return method
def make_alias(method):
import inspect
args = str(inspect.signature(method))[1:-1] # remove parentheses
Copy link
Contributor

Choose a reason for hiding this comment

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

str(inspect.signature(functools.partial(method, None))) will bind the first argument for you and remove it from the signature more easily than the text wrangling below :) (plus it even works if someone decides to not call the first argument "self", hehe... for example it could just be a wrapper function itself being *args, **kwargs)

@timhoffm
Copy link
Member Author

For future reference, this was the first approach, which was discarded in favor of simply decorating the alias with @functools.wraps(getattr(cls, name)).

    def make_alias(method):
        import inspect
        args = str(inspect.signature(method))[1:-1]  # remove parentheses
        if args[:6] == 'self, ':
            call_args = args[6:]
        elif args == 'self':
            call_args = ''
        else:
            raise RuntimeError('Unexpected signature: %s' % args)
        alias_method = eval('lambda {}: self.{}({})'.format(
            args, method.__name__, call_args))
        alias_method.__doc__ = "Alias for `{}`.".format(method.__name__)
        return alias_method

    for prop, aliases in alias_d.items():
        exists = False
        for prefix in ["get_", "set_"]:
            if prefix + prop in vars(cls):
                exists = True
                for alias in aliases:
                    method = make_alias(getattr(cls, prefix + prop))
                    method.__name__ = prefix + alias
                    setattr(cls, prefix + alias, method)
        if not exists:
            raise ValueError(
                "Neither getter nor setter exists for {!r}".format(prop))

Copy link
Member

@NelleV NelleV left a comment

Choose a reason for hiding this comment

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

This looks good to me.

(although about this whole functionality:
There should be one-- and preferably only one --obvious way to do it. 😭 )

@NelleV NelleV merged commit d5433f6 into matplotlib:master Aug 26, 2018
@NelleV
Copy link
Member

NelleV commented Aug 26, 2018

Thanks @timhoffm

@timhoffm timhoffm deleted the alias-signature branch August 26, 2018 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants