-2

The goal is to "do something" if the input value falls between (0.0, 1.0).

Otherwise,

  • return 1.0 if input is >= 1.0 or
  • return 0.0 if input is <= 0.0

The straightforward way would be to:

def func(x):
    if x >= 1.0:
        return 1.0
    elif x <= 0.0:
        return 0.0
    else: 
        return do_something(x)

But it's also better to use some max/min trick like this:

def func(x):
    if 0 < x < 1.0:
        return do_something(x)
    else:
        return max(min(x, 1.0), 0.0)

Which coding style more Pythonic returning clipped values if it falls below 0 and above 1 and if otherwise, the float needs to be manipulated?

do_something() is some float manipulation that should return a value between (0.0, 1.0), i.e. exclusive of 0.0 and 1.0.

For simplicity, treat it as:

def do_something(x):
    return x**2

To further explain the question, the (0.0, 1.0) range excludes the 0.0 and 1.0, i.e.

>>> x = 0.231
>>> 0 < x < 1.0
True

So if 0 < x < 1.0, the desired output has to be put through the do_something() function that returns a value (0.0, 1.0), e.g.

>>> x**2
0.053361000000000006
>>> 0 < x**2 < 1.0
True

I guess the confusion in the comments comes when you're asking why don't I just do_something() first then "clamp"/"clip" the outputs, i.e:

>>> x = 0.231
>>> max(min(x**2, 1.0), 0.0)
0.053361000000000006

But let's say you have a negative no. as an input:

>>> x = -0.231

The desired output is 0.0. But if we do just simply do_something() first then "clamp"/"clip", we get the wrong output:

>>> x = - 0.231
>>> max(min(x**2, 1.0), 0.0)
0.053361000000000006

Perhaps square is a little too simplified to describe do_something().

The actual function looks more like this:

def do_something(x):
    ratio_y, score_y = 1.5, 0.5
    beta = math.log(score_y) / math.log(score_y)**2
    return math.exp(beta * math.log(x)**2)'

Due to the asymptote nature of logarithms, the checks for (0.0, 1.0) needs to be explicit before calling the do_something() function.

alvas
  • 115,346
  • 109
  • 446
  • 738
  • 2
    Shouldn't it be `return do_something(x)`? – Barmar Jul 13 '17 at 09:58
  • Second one, obviously – Deepesh Choudhary Jul 13 '17 at 09:59
  • 1
    Also, why don't you let `do_somthing` decide? the code will be cleaner this way. – DeepSpace Jul 13 '17 at 09:59
  • @DeepSpace Then he'd just have the same question about the code in `do_something`. – Barmar Jul 13 '17 at 10:00
  • 1
    Possible duplicate of [How to limit a number to be within a specified range? (Python)](https://stackoverflow.com/questions/5996881/how-to-limit-a-number-to-be-within-a-specified-range-python) – Håken Lid Jul 13 '17 at 10:00
  • for your information, this operation is often called "clamping" – Pac0 Jul 13 '17 at 10:02
  • @HakenLid this question is a little different because, there's the "else" part that needs to "square" the input if it's within (0.0, 1.0) – alvas Jul 13 '17 at 10:02
  • 1
    @NickChapman This isn't clamping, because the conditional is on the input, not the result. – Barmar Jul 13 '17 at 10:02
  • This doesn't seem to be a common enough idiom that there's likely to be a preferred pythonic way. Just write whatever you feel is clearest. – Barmar Jul 13 '17 at 10:03
  • No, it's not clamping. If the input is not more than 1.0 or less than 0.0, it needs to be squared!! – alvas Jul 13 '17 at 10:05
  • but when you square a result between 1 and 0, the result stays between 1 and 0, so in this case, clamping on the input or the result has same effect. – Pac0 Jul 13 '17 at 10:07
  • I see. I guess it's not a duplicate. OTOH questions about best practice tend to be off topic, since it's more or less a matter of subjective preference. Also, is it something you would like to use several times, you could make it into a higher order function / decorator, and apply it with something like `@clamp_conditional_func(max_bound, min_bound)`, for instance. – Håken Lid Jul 13 '17 at 10:08
  • 1
    Yes, but if input above 1.0, it's easy to detect but if it's under 0, then the square will make it within the (0.0, 1.0) range, which is the undesired output. – alvas Jul 13 '17 at 10:09
  • @NickChapman does the updated question explains the question better? – alvas Jul 13 '17 at 10:24

1 Answers1

2

you can use the decorator style if you like.

The decorator (with custom min and max values):

def limit_decorator(min_val, max_val):
    def inner_decorator(func):
        def wrapping_func(x):
            if x >= max_val:
                return max_val
            elif x <= min_val:
                return min_val
            else:
                return func(x)
        return wrapping_func
    return inner_decorator

you can use it like this:

@limit_decorator(0.0, 1.0)
def do_something(value):
    return value**2

alternatively, use can you a simple decorator (with predefined limits):

def limit_decorator(func):
    def wrapping_func(x):
        if x >= 1.0:
            return 1.0
        elif x <= 0.0:
            return 0.0
        else:
            return func(x)
    return wrapping_func

which you can you like:

@limit_decorator
def do_something(value):
    return value**2
ayun12
  • 479
  • 1
  • 4
  • 9
  • Why obscure simple logic by hiding it in a decorator? – Sneftel Jul 13 '17 at 10:29
  • @Sneftel it's not obscuring the logic if you are going to use this method all the time. If you're just doing it a single time then I agree that using the decorator is more annoying than just putting the logic in the function. But if you were going to repeat this 10 times with different functions then a decorator is a good way to deal with this. – Nick Chapman Jul 13 '17 at 10:32
  • @Sneftel He asked for a code style, not for a solution. I believe it can be useful in some cases. If it's being used once that i agree that there are shorter ways. – ayun12 Jul 13 '17 at 10:39