-
Notifications
You must be signed in to change notification settings - Fork 12
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
expects decorator #143
base: main
Are you sure you want to change the base?
expects decorator #143
Conversation
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.
If I understand wraps
correctly, it makes sure that the input data is in the specified units, strips them and attaches the units specified as return value to the results. Should we copy that design for expects
?
Edit: It would also make sense to have it stay the way it currently is, though
I actually did not realise that. I just kind of started implementing it the way I felt it should work. That is not that different though - perhaps we could have both behaviours via an extra kwarg |
the disadvantage of a new kwarg is that it could shadow a variable (if we choose the name carefully enough that might not be as much of an issue, though). What do you think about adding a new decorator that does both? It could share all internals and additionally dequantifies before and quantifies after the call to the user function. |
Any xarray function that only accepts kwargs but also has its own kwargs has this problem - we could copy xarray by allowing users to pass a dict of kwarg_units optionally in the same way that
Or we could just do that. It just needs a good name. I still think |
@keewis I don't know if I'm being dense but I actually can't work out how to make two differently-behaving decorators that share most of their internals 😅 I even made a SO question about it, but no-one has answered it yet. I could just copy-paste the internals code for now... |
if you look at the structure of a decorator without arguments, it's usually something like: def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# preprocess args / kwargs
result = func(*args, **kwargs)
# postprocess the result
return result
return wrapper if you put the pre/postprocessing into separate functions, the new decorator can call those and do some additional preprocessing to drop the units (and add them back in the postprocessing step) |
Yes, but I think you still need to duplicate all the triple wrapping code, like this def decorator1(func):
flag = True
@functools.wraps(func)
def wrapper(*args, **kwargs):
preprocess(args, kwargs, flag)
result = func(*args, **kwargs)
postprocess(result, flag)
return result
return wrapper
def decorator2(func):
flag = False
@functools.wraps(func)
def wrapper(*args, **kwargs):
preprocess(args, kwargs, flag)
result = func(*args, **kwargs)
postprocess(result, flag)
return result
return wrapper because if you instead try to move the |
wouldn't it be possible to do this instead: def preprocess1(*args, **kwargs):
...
return args, kwargs
def preprocess2(*args, **kwargs):
...
return args, kwargs
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args, kwargs = preprocess1(*args, **kwargs)
result = func(*args, **kwargs)
result = postprocess(result)
return result
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args, kwargs = preprocess1(*args, **kwargs)
args, kwargs = preprocess2(*args, **kwargs)
result = func(*args, **kwargs)
result = postprocess(result)
return result
return wrapper that's probably not as elegant as using a flag, but maybe it's simpler? |
Yeah, but that doesn't really solve my question - it still duplicates It might be possible to solve this by making a callable class that binds all the arguments as attributes, but it's probably not worth the effort. |
fair point, I didn't consider the outer layer yet. I'll try to come up with something that also deduplicates that (might take some time, though), but in the meantime it might be fine to only share pre/postprocessing. |
Thinking about this again I think you might be right. To be useful, an In other words: when would I ever have a function that (a) specifically wants pint Quantities instead of bare numpy(/xarray) objects but also (b) will only accept those quantities with a specific unit? |
I agree, it looks like just verifying the units' dimensions is not that useful (and this can also be achieved in the function using either |
Co-authored-by: keewis <keewis@users.noreply.github.com>
Co-authored-by: keewis <keewis@users.noreply.github.com>
…nt-xarray into expects_decorator
@keewis I think this might be ready to merge now finally?? |
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.
apologies for letting this sit for almost two weeks again.
I agree, this is fairly close. I still have a few comments regarding the docs, but one of them might also affect the code.
If an argument or return value has a specified unit, but is | ||
not an xarray.DataArray or pint.Quantity. Also thrown if any | ||
of the units are not a valid type, or if the number of | ||
arguments or return values does not match the number of units | ||
specified. |
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.
shouldn't we raise ValueError
if the number of arguments / return values do not match?
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.
Yeah possibly. I can change that.
good news is that the next release of |
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.
as mentioned in https://github.com/xarray-contrib/pint-xarray/pull/143/files#r815333587, Signature.bind
is really useful for this.
I've added a few suggestions that should hopefully make this much easier, but it requires to also specify units for keyword-only parameters, and *args
and **kwargs
are still not supported.
Edit: We'd probably need quite a few tests, too
I've implemented my suggestion to use
|
WIP attempt to add an
@expects
decorator to the API, which emulatespint.UnitRegistry().wraps
.pre-commit run --all-files
whats-new.rst
api.rst