12

I have a block of code in a function that does some comparisons, namely:

if customer_info['total_dls'] < min_training_actions \
or customer_info['percentage'] > train_perc_cutoff:
    continue
elif customer_id not in test_set \
or test_set[customer_id]['total_dls'] < min_testing_actions:
    num_uncertain +=1
    continue
elif test_set[customer_id]['percentage'] <= test_perc_cutoff:
    num_correct +=1
else:
    num_incorrect +=1

Now sometimes I need to do those comparisons to be greater than, and sometimes I need them to be less than. All of the rest of the code is exactly the same. Now, I could just make two functions that reuse basically the same code, but before I do, is there some clean way to variabalize the comparison operator, so I could just use the same block of code and pass in the comparison as a variable? Something like: compare(var1, var2, polarity). I know I can make this myself, but I'm wondering what the standard is in cases like this. Is there some pretty pythonic way of doing this I'm unaware of?

[Edit] Adding emphasis to the most important part of the question [/Edit]

Eli
  • 36,793
  • 40
  • 144
  • 207

4 Answers4

21

You can use the operator module; it has functions that act as the comparison operators:

import operator

if foobar:
    comp = operator.lt
else:
    comp = operator.gt

if comp(spam, eggs):

This'll use either test if spam is less then, or greater then eggs, depending on the truth of foobar.

This fits your comparison as a variable requirement exactly.

This is certainly what I'd use to avoid repeating myself; if you have two pieces of code that differ only in the comparison direction, use a function from operators to parameterize the comparison.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Agree with @RemcoGerlich. I'm looking for a clean Pythonic way of doing this. I'm thinking just making another function would be cleaner than the above. – Eli Apr 28 '14 at 19:05
  • @RemcoGerlich: If the *only* difference between two larger codepaths is the operator, then yes. – Martijn Pieters Apr 28 '14 at 19:06
  • But it's not, the variables being compared also differ. – RemcoGerlich Apr 28 '14 at 19:06
  • @RemcoGerlich: And that's the hard part, is it? – Martijn Pieters Apr 28 '14 at 19:08
  • @Eli: I am not entirely certain then what you are looking for; I was merely demonstrating where in the Python stdlib you can find functions that perform the same task as the `<` and `>` operators in function form. Because these are objects, they can be passed around as references too. – Martijn Pieters Apr 28 '14 at 19:15
  • @MartijnPieters if there's a clean way of doing it, I was looking for that. If there's no clean way of doing it, then an answer of "there's no clean way of doing this" would be great. I hope this doesn't sound bitter. I appreciate the help either way! – Eli Apr 28 '14 at 19:31
  • @Eli: your other option is to use a branch in such a function; `def compare(var1, var2, direction): return var1 < var2 if direction == 'lt' else var1 > var2`. But I think it isn't clear from your question what you are looking for here. – Martijn Pieters Apr 28 '14 at 19:33
  • @MartijnPieters I'm not sure how much clearer I can make it. I'm looking for the standard if there is one. If in cases like these people generally use a compare function, then great! That would be the answer. If they generally just reuse the same code, or break the function up into smaller parts, that would be the answer. I'm being very clear here and in the comments about what would be a good answer. I'm sorry it's unclear to you. – Eli Apr 28 '14 at 19:44
  • @Eli: There is no 'best practice' here; you use what allows you to reuse your code and not repeat yourself. If you have a large piece of code where you need to make the comparison variable, I'd certainly use the `operators` module. – Martijn Pieters Apr 28 '14 at 19:45
  • @MartijnPieters Great! That was the answer I was looking for. Thanks! – Eli Apr 28 '14 at 19:46
  • I believe it's the `operator` module (not plural). Otherwise this solution works great! – Serp C Dec 14 '18 at 14:57
  • 1
    @SerpentineCougar yup, my mistake, now corrected. Thanks! – Martijn Pieters Dec 14 '18 at 15:01
2

I had the same question when trying to build a module for unknown ML models. Some require a minimum score to perform, others a maximum score, depending on the metric used. I passed in a 'greater than' boolean variable and wrote a small function:

def is_better(gt,a,b): 
    return a>b if gt else a<b
Dany Majard
  • 103
  • 1
  • 9
1

I would not refactor this by making the operators dynamic in any way. I think the comparisons aren't that similar and forcing them to look more similar obfuscates the code. It's a bit of a red herring.

I would put the complex checks in a function, something like

def customer_trained_enough(customer_info):
    return (
        customer_info['total_dls'] < min_training_actions
        or customer_info['percentage'] > train_perc_cutoff)

def customer_tested_enough(test_info):
    return test_info['total_dls'] >= min_testing_actions

def percentage_too_high(test_info):
    return test_info['percentage'] > test_perc_cutoff

And then the code becomes:

if customer_trained_enough(customer_info):
    continue
if (customer_id not in test_set or
    not customer_tested_enough(test_set[customer_id])):
    num_uncertain += 1
elif not percentage_too_high(test_set[customer_id]):
    num_correct += 1
      else:
    num_incorrect += 1

I guessed some names for the functions, and had to negate the logic in two of them to make the names work.

RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
1

For some simple cases inverting the inputs sign can be the best approach. It does not require any develop process, just calling the function with opposite sign inputs:

a = 3
b = 5
def func(a,b):
    return a>b

func(a, b) -> False
func(-b, -a) -> True

This approach is equivalent to permute >, <, >=, <= and max, min which is also usually useful because if you are inverting the signs "greater than" and "less that" you may also want to invert the maximum and the minimum for consistency.

The big caveat of this approach is that complex functions can lead to incorrect results, if for instance the functions has middle step involving a mathematical operation such as exponentiation.

Other approach that doesn't suffer from the problem described is using a flag in the comparisons you want to invert, and maybe redefine the max/min function according to it too:

def func(a,b, rev=False):
    rev = -1 if rev else 1
    return rev*a>rev*b

func(a, b) -> False
func(a, b, True) -> True
Ziur Olpa
  • 1,839
  • 1
  • 12
  • 27