Skip to content

Add return annotation support to u.quantity_input#5606

Merged
mhvk merged 4 commits into
astropy:masterfrom
Cadair:return_units
Feb 7, 2017
Merged

Add return annotation support to u.quantity_input#5606
mhvk merged 4 commits into
astropy:masterfrom
Cadair:return_units

Conversation

@Cadair

@Cadair Cadair commented Dec 14, 2016

Copy link
Copy Markdown
Member

This adds a lovely Python 3 only feature to u.quantity_input which enables the use of the return annotation to specify a unit to convert the output to. This makes it much easier to unit convert the return value of a function, rather than explicitly doing it in the return statement. I have found this especially useful for when a value is often printed to screen.

I have assumed here that the error handling in Quantity.to is sufficient for type checking on the return value itself, so this does not need to be done in the decorator.

Also, this could be hacked to be 2.7 compatible by a special keyword argument to quantity_input but frankly that's ugly and I can't be bothered...

@astrofrog astrofrog left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs a changelog entry :)

Comment thread astropy/units/decorators.py Outdated
with add_enabled_equivalencies(self.equivalencies):
return wrapped_function(*func_args, **func_kwargs)
return_ = wrapped_function(*func_args, **func_kwargs)
if wrapped_signature.return_annotation is not funcsigs._empty:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it safe to rely on _empty since it's private?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://docs.python.org/3/library/inspect.html suggests that there is a non-private Signature.empty property. Similarly, for funcsigs: http://funcsigs.readthedocs.io/en/0.4/

Comment thread astropy/units/decorators.py Outdated

import astropy.units as u
@u.quantity_input
def myfunction(myangle: u.arcsec) -> u.deg:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This example would always feel, wouldn't it? It would need return units of u.deg**2.

@mhvk

mhvk commented Dec 14, 2016

Copy link
Copy Markdown
Contributor

Looks good modulo a few comments. Nice to see how relatively easy this is!

Comment thread astropy/units/decorators.py Outdated
with add_enabled_equivalencies(self.equivalencies):
return wrapped_function(*func_args, **func_kwargs)
return_ = wrapped_function(*func_args, **func_kwargs)
if isinstance(wrapped_signature.return_annotation, (Unit, Quantity)):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@astrofrog @mhvk I changed this to verify Unit or Quantity so that quantity_input doesn't just eat all the possible uses for return annotations. Opinions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably a good idea, though is there a use for Quantity here? Why not just Unit?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't know why you would but:

 ❯ (10*u.m).to(100*u.km)
<Quantity 0.0001 100 km>

is valid...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have a slight worry here, because function units such as u.mag(u.Jy) or u.STmag are not Unit instances (this does need to be fixed eventually; #3650). My sense is that if one uses @quantity_input, then one should not really expect to be able to use the output annotation for anything else, so I think I would just stick with what you had. But if you do want to be more general, I would suggest:

try:
    return_unit = Unit(wrapped_signature.return_annotation)
except Exception:  # not a unit requested, so ignore
    pass
else:
    return return_.to(return_unit)

Obviously, the above, like your original implementation, allows one to provide units as strings, which I think is OK.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have reverted it to assuming the return annotation is always a Unit-Like thing. #3650 looks quite interesting...

@Cadair

Cadair commented Dec 14, 2016

Copy link
Copy Markdown
Member Author

Also, I just learned that as of Python 3.2 the function annotations are in a func.__annotations__ attribute, so as of 2.1 this code can be massively simplified (especially if we drop the non-function annotations form).

@Cadair Cadair force-pushed the return_units branch 2 times, most recently from cb33e85 to e634892 Compare December 14, 2016 16:45
@eteq

eteq commented Dec 14, 2016

Copy link
Copy Markdown
Member

@Cadair - this is really neat, and I think it deserves a mention in the narrative docs as well... Perhaps either the quantities page or even the units getting started page?

Also, when this gets released, I think it makes sense to integrate it into the Quantities Tutorial, which has a section on using quantities in functions.

@Cadair

Cadair commented Dec 15, 2016

Copy link
Copy Markdown
Member Author

The other awesome thing about using function annotations is that sphinx knows about them:
screenshot from 2016-12-15 12-53-45

@hamogu

hamogu commented Dec 18, 2016

Copy link
Copy Markdown
Member

I like. Might make me finally upgrade from 2.7 to 3.x!

@Cadair

Cadair commented Jan 19, 2017

Copy link
Copy Markdown
Member Author

@eteq I added an example to the existing doc section on functions, it seemed like the best place.

@astrofrog

Copy link
Copy Markdown
Member

@mhvk @adrn - as units maintainers, can we go ahead and merge this?

@mhvk

mhvk commented Feb 7, 2017

Copy link
Copy Markdown
Contributor

@astrofrog - This looks good, and since you already approved, I'll go ahead and merge. Thanks, @Cadair!

@mhvk mhvk merged commit 5a2cdcb into astropy:master Feb 7, 2017
@hamogu

hamogu commented Feb 7, 2017

Copy link
Copy Markdown
Member

Things like this are definitely an incentive for me to switch my production environment to python3 ASAP.Thanks!

@Cadair Cadair deleted the return_units branch February 7, 2017 17:05
@bsipocz

bsipocz commented Feb 7, 2017

Copy link
Copy Markdown
Member

@hamogu - Another good reason is that py3 is significantly faster...

@hamogu

hamogu commented Feb 7, 2017

Copy link
Copy Markdown
Member

My main problem is that mayavi is not fully ported yet and I use that in some projects, but I'm going off-topic now...

@Cadair

Cadair commented Feb 7, 2017

Copy link
Copy Markdown
Member Author

@hamogu there is now a linux mayavi / vtk build on conda-forge for python 3 and linux

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.

6 participants