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
Allow sets of units to be enabled #1268
Conversation
|
||
>>> from astropy import units as u | ||
>>> from astropy.units import imperial | ||
>>> u.add_enabled_units([imperial]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether it would be nicer for users to simply be able to do
u.enable_imperial()
if there are not too many 'collections' of units, this would be manageable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add that as an alias, but the more flexible add_enabled_units
is still required (for custom units, if nothing else).
I like this in principle, though I wonder whether maybe we can make this completely thread-safe by making I wonder whether one could also make it so that units that are not enabled would raise an exception explaining how to enable them, when doing e.g. |
I need to think about the functionality implemented here a bit more from a user point of view. What are we really trying to do here - do we really want things like |
I think the only way to make this threadsafe would be to require a set of units to be passed in to the I'm not sure about the benefit of having I think the problem here, as I understand it, is unit clutter. |
We should probably revisit this. I think it feels a little bit like a "toy" that imperial units are included by default. This is one solution. The other is just to remove them outright. Either way, I'd like to improve this for 0.3. |
Between those solutions, @mdboom, I personally like a solution along these lines... But I am with @astrofrog that I'm not sure exactly how wide-ranging the enabling/disabling should be. That is, if the problem is namespace clutter, than a solution might be to have the less-common (e.g. imperial) units registered but not in the |
👍 to @eteq's suggestion of ... hmmm, I hadn't actually realised that if I wanted to avoid |
@eteq: To clarify: conversion is always possible. You can always convert
I think (1) is a given -- adding things to a module as the result of a function call is too "magical".
@mhvk: We can't disable some CGS or astrophysical units by default because they are needed to support the minimum requirements of the FITS, CGS and VOUnit standards. This is "astro"py after all, and we should by default support the units required by the community's file formats. EDIT: (2) does already work correctly with |
@mdboom - I agree with your summary and definitely don't want to disable I should let @eteq confirm, but my impression was that he wondered whether with his suggestion the machinery to enable/disable units was needed at all (the unit package is not the easiest to comprehend as it is -- at least to me -- and this PR would seem to make it substantially trickier). |
@mhvk: I'm not sure I follow. We need to define many of the units in Fundamentally, you need to have a "pool" of units somewhere in order to support the
I'm not averse to doing a sort of hybrid between (1) and (3), so the user would do:
which would be equivalent to:
|
@mdboom - I think we're talking at cross-purposes -- you're right that if I wish I can already import I guess the above diluted the only concern I could see, that there might be a case for making what will now be the default the only case, i.e., keep Just for my enlightenment, though, if in a given python session, any routine enables |
That's all fine if we never want imperial (or any custom units for that matter) to play with The effect of this is global, so enabling a set of units enables them everywhere. (The lack of global effects is another advantage to proposal (2) above, for what it's worth). However, there are context managers provided here (
|
OK, fair enough. Since I doubt there will be that many cases of different routines wanting different things and it mattering and them not using the unit context, I think this is a minor worry. |
All that said, I agree it's adding complexity. The simpler solution seems to me to be (1):
The downside is the "magicalness" (but maybe it doesn't matter in practice), and the inability to remove the units later during the session (which maybe also doesn't matter). What do others think. I'm willing to move to that simpler API if we all agree that being explicit and removing units later don't matter. |
+1 on being explicit, -1 on magic. |
Before I was thinking about @mdboom's 1) and 2), but had forgotten about 3). Now that I understand, I definitely like this approach, and am also +1 to explicit. I particularly like @mdboom's
as the "standard" way to do it. I would suggest adding a specific mention in the docs of the 1), 2), and 3) that @mdboom gave in response to my last comment, though. That is, very explicitly define what "enabled" means (probably in the "Enabling other units" section this PR adds?) It might also make sense to make a configuration item for this - let the user provide a list of which units systems (i.e. modules inside |
+1 on the methods, though -1 on a configuration item for the unit systems -- too risky that code starts to depend on such items being set "right". (and of course |
As @mhvk, I'm +1 on
but don't like the idea of a configuration item. |
I have added |
Looks good to me from a docs and tests perspective. |
Same goes for me. @mhvk - maybe you could take this for a spin to see if any unexpected issues pop up? |
Can #1073 be closed? |
@astrofrog - would love to, but am leaving tomorrow for travel and hence am very unlikely to get to it until after/at astropy. |
@taldcroft: Yes. This completely supercedes #1073. |
@@ -99,3 +96,48 @@ use the `physical_type` property:: | |||
# However, (u.m / u.m) has a scale of 1.0, so it is the same | |||
>>> (u.m / u.m) == u.dimensionless_unscaled | |||
True | |||
|
|||
Enabling other units |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this should be "Enabling Other Unit Systems"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is, I realize it can be used to add both individual units and unit systems, but I wouldn't necessarily think to look here to learn about activating something like imperial unless it mentioned unit systems...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would "Enabling other units or unit systems" work, or is that too wordy?
add_enabled_units(units) | ||
yield | ||
_current_unit_registry = old_registry | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you should add an
Examples
--------
section here? It can just be a copy of what you have in the docs, but a lot of people don't know what "context manager" means, but will understand if you show them a with statement using it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. There is an example for add_enabled_units
-- I can duplicate something similar here.
Looks good to me aside from the comments above. |
Allow sets of units to be enabled
This is a follow on to #1073.
This adds methods
add_enabled_units
andset_enabled_units
to allow for adding sets or individual units after the fact.I played with various approaches to this for a while. I'm not crazy about the fact that this relies on global state (the present implementation does, too, although less flexibly). The alternative is to require passing in sets of units to functions that do searching (e.g. find_equivalent_units). Ulteimately, I also thought that the convenience of "setting and forgetting" new units is more important, and the lack of thread-safety is not terribly problematic in this case, as the 99% use case is adding units as part of initialization, not adding units "on-the-fly".
There is no function to remove units -- that's tricky to get right giving all of the aliasing and duplication checking that goes on. Instead, I added context managers to allow for temporary enabling of units that restore to the previous state after exiting the
with
block.With this change, Imperial units are no longer available by default, and they aren't injected into the
astropy.units
namespace ever. Most of the changes to the tests are for this.Also, a note about backward compatibility. The old
register
andderegister
methods now just raise an exception. It isn't really feasible to deprecate them and remove later, since that would require two fundamentally different ways of handling the unit registry at the bottom level at the same time. I hope that registering custom units is enough of a corner case (outside of astropy itself) that this won't be a big issue. We did warn that the API is not final on this stuff in 0.2 after all anyway.