-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
ENH: Enforce constraints on polynomial attrs window and domain #16108
Conversation
[domain] = pu.as_series([domain], trim=False) | ||
if len(domain) != 2: | ||
raise ValueError("Domain has wrong number of elements.") | ||
self._domain = domain |
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.
Could consider:
[domain] = pu.as_series([domain], trim=False) | |
if len(domain) != 2: | |
raise ValueError("Domain has wrong number of elements.") | |
self._domain = domain | |
if domain is None: | |
# remove the instance-specific value to restore the default | |
try: | |
del self._domain | |
except KeyError: | |
pass | |
return | |
[domain] = pu.as_series([domain], trim=False) | |
if len(domain) != 2: | |
raise ValueError("Domain has wrong number of elements.") | |
self._domain = domain |
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.
or
[domain] = pu.as_series([domain], trim=False) | |
if len(domain) != 2: | |
raise ValueError("Domain has wrong number of elements.") | |
self._domain = domain | |
if domain is None: | |
self._domain = type(self)._domain | |
return | |
[domain] = pu.as_series([domain], trim=False) | |
if len(domain) != 2: | |
raise ValueError("Domain has wrong number of elements.") | |
self._domain = domain |
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 I understand it, the main thing that this code does is allow None
as a valid value for the domain setter. By example:
Current behavior of NumPy
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
>>> p.domain
Behavior with this PR
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
ValueError: Domain has wrong number of elements.
>>> p.domain
array([-10., 10.])
Behavior with proposed change
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
>>> p.domain
array([-1., 1.])
Is this correct and the change that you intended? IMO, I prefer the version that errors rather than giving None
a special meaning that falls back to the default value for the class. This could be achieved via p.domain = np.polynomial.Polynomial.domain
.
_domain = np.array(chebdomain) | ||
_window = np.array(chebdomain) |
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.
A little alarming that these are mutable.
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.
Do you mean that the original (red) is alarming or the proposed change? I had figured the prepended-_
-means-"private" convention was enough to discourage any user from changing the default class variables.
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.
Thanks for taking a look at this!
I should mention that my main goal in this PR was to introduce some mechanism by which the domain
and window
could be made immutable both at the class and instance level, without breaking backwards compatibility.
I introduced abstract setters (with default implementations) that basically take the constraints applied to the inputs in the constructor and apply them to any time a user would try to change the domain
or window
attributes on-the-fly. Note that instead the setters could be used to raise a future or deprecation warning instead if it were decided that the domain
and window
should be made immutable (forcing the user to use convert()
or create a new instance to change these attrs).
[domain] = pu.as_series([domain], trim=False) | ||
if len(domain) != 2: | ||
raise ValueError("Domain has wrong number of elements.") | ||
self._domain = domain |
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 I understand it, the main thing that this code does is allow None
as a valid value for the domain setter. By example:
Current behavior of NumPy
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
>>> p.domain
Behavior with this PR
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
ValueError: Domain has wrong number of elements.
>>> p.domain
array([-10., 10.])
Behavior with proposed change
>>> p = np.polynomial.Polynomial([1, 2, 3], domain=[-10, 10])
>>> p
Polynomial([1., 2., 3.], domain=[-10., 10.], window=[-1, 1])
>>> p.domain = None
>>> p.domain
array([-1., 1.])
Is this correct and the change that you intended? IMO, I prefer the version that errors rather than giving None
a special meaning that falls back to the default value for the class. This could be achieved via p.domain = np.polynomial.Polynomial.domain
.
_domain = np.array(chebdomain) | ||
_window = np.array(chebdomain) |
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.
Do you mean that the original (red) is alarming or the proposed change? I had figured the prepended-_
-means-"private" convention was enough to discourage any user from changing the default class variables.
131c3d3
to
cd05e00
Compare
I rebased to fix up the merge conflicts. This PR needs a decision on whether or not the change is worthwhile. A quick recap: this PR adds some input-checking to the setting of the >>> import numpy.polynomial as npp
>>> p = npp.Polynomial([1, 2, 3])
>>> p
Polynomial([1., 2., 3.], domain=[-1, 1], window=[-1, 1])
# The domain and window can be set to arbitrary objects, leading to
# strange behavior in e.g. the repr
>>> p.window = [-2, 3] # a perfectly valid window
>>> p
Polynomial([1., 2., 3.], domain=[-1, 1], window=)
# or strange errors during computation
>>> p.window = "2, 3" # Should be disallowed, but isn't
>>> p(10)
Traceback (most recent call last)
...
TypeError: unsupported operand type(s) for -: 'str' and 'str' This PR proposes a solution that still allows |
Got test suite passing again with new organization.
Thus window/domain values are checked for validity both in __init__ and when set dynamically.
cd05e00
to
44c47fa
Compare
I'll go ahead and close this as it's not a critical feature. If ever we want to enforce the window/domain constraints I think this is a decent way to do it. |
Related to #16059
The changes here represent a potential solution to the mutability of two (of the three) defining attributes of polynomials: the
domain
andwindow
attributes.The mutability of these attrs leads to some undesirable behavior. For example, any
array-like
with len == 2 is currently allowed, but the__repr__
expects that they must be arrays. Thus setting thedomain
orwindow
with alist
will cause problems:Note that
__init__
does input validation and convertsarray-like
to the expected format:This PR reorganizes the classes in the polynomial module to add the same input validation used by
__init__
to a general setter for thedomain
andwindow
attributes.