holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
410 stars 69 forks source link

Param with bounds should default to lower bound #911

Open ahuang11 opened 4 months ago

ahuang11 commented 4 months ago
import param
class Test(param.Parameterized):

    fps = param.Integer(
        bounds=(1, None),
        doc="The number of frames per second to use for the output.",
    )

Test()

ValueError: Integer parameter 'fps' must be at least 1, not 0.

maximlt commented 3 months ago

I'm not sure. What should this default to?

class Test(param.Parameterized):
    fps = param.Integer(bounds=(None, -1))
jbednar commented 3 months ago

I don't think most people would have a strong expectation about what the default would be in either of those cases, so I'd recommend to users that they specify the default explicitly for clarity.

Still, having defaults be automatic is convenient, especially in a notebook context for quick exploratory mini-apps. My gut preference would be to take the midpoint of the two bounds (as the "most typical" value), or if there's only one specified, take that one. But I believe the current practice is to take the lower bound (alas!), so I guess taking the lower bound (if present) and then falling back to the upper bound (if present) is the best we could do.

All that's to say is that I don't object to this proposal, but I also wouldn't mind if other people want to mark it wontfix.

ahuang11 commented 3 months ago

If it's marked as wontfix I think it should not default to 0 outside the bounds of fps.

maximlt commented 3 months ago

If I could choose I'd prefer Param not to set any default values at all, for all Parameters, so I'm not going to advocate for automatically computing smart default values 🙃

But if you want to go down that route, you'll have to define how this new logic behaves in a class inheritance scenario:

import param

class A(param.Parameterized):
    fps = param.Integer(bounds=(1, None))

class B(A):
    fps = param.Integer(bounds=(10, 20))

a = A()
b = B()
print(a.fps, b.fps)  # ?
ahuang11 commented 3 months ago

My first instinct is 1 and 10, but I don't mind it being wontfix, as long as the default is None (right now it's 0 if I recall).

philippjfr commented 3 months ago

My first instinct is 1 and 10, but I don't mind it being wontfix, as long as the default is None (right now it's 0 if I recall).

In principle I'm in full agreement but in practice this is a extremely difficult change to make because many users may implicitly be depending on the default being there.

jbednar commented 3 months ago

Maybe the most conservative approach is to raise an explicit error when the computed default is invalid for the range, telling the user they need to set an appropriate default.