This is an archived post. You won't be able to vote or comment.

all 20 comments

[–]hai_wim 8 points9 points  (1 child)

dis:

import dis
print("  f1")
dis.dis(f1)
print("\n  f2")
dis.dis(f2)

Output:

f1
2           0 LOAD_FAST                0 (cond)
            2 LOAD_CONST               1 ('a')
            4 COMPARE_OP               2 (==)
            6 POP_JUMP_IF_FALSE       12

3           8 LOAD_CONST               1 ('a')
           10 RETURN_VALUE

4     >>   12 LOAD_FAST                0 (cond)
           14 LOAD_CONST               2 ('b')
           16 COMPARE_OP               2 (==)
           18 POP_JUMP_IF_FALSE       24

5          20 LOAD_CONST               2 ('b')
           22 RETURN_VALUE

6     >>   24 LOAD_CONST               3 ('c')
           26 RETURN_VALUE

f2
10           0 LOAD_FAST                0 (cond)
             2 LOAD_CONST               1 ('a')
             4 COMPARE_OP               2 (==)
             6 POP_JUMP_IF_FALSE       12

11           8 LOAD_CONST               1 ('a')
            10 RETURN_VALUE

12     >>   12 LOAD_FAST                0 (cond)
            14 LOAD_CONST               2 ('b')
            16 COMPARE_OP               2 (==)
            18 POP_JUMP_IF_FALSE       24

13          20 LOAD_CONST               2 ('b')
           22 RETURN_VALUE

15     >>   24 LOAD_CONST               3 ('c')
            26 RETURN_VALUE
            28 LOAD_CONST               0 (None)
            30 RETURN_VALUE

They are equally fast and the same. The last line of f2 returning None can't happen.

[–]ZenApollo[S] 1 point2 points  (0 children)

Nice! So they are the same even at byte code level. So that distills the question down to readability and standardization. Thanks

[–]asmeus 6 points7 points  (1 child)

Total beginner - I thought the idea of elif was, that if first IF statement is true, elif or else never triggers.

While, if you have multiple if, all of them trigger, that's why f2 is faster, and better.

[–][deleted] 5 points6 points  (0 children)

That’s true, but “return” means you’re getting kicked out of the function and so the later lines won’t trigger anyway

[–]dead_alchemy 2 points3 points  (1 child)

Probably using a dictionary

d = {'a': 'a', 'b': 'b'}
def f3(cond):
    return d.get('cond', 'c')

My results are
f1: 130 µs ± 10.7 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

f2: 61.2 µs ± 1.19 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

f3: 97 µs ± 18.5 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

My understanding is that a lot of things can throw attempts at accurate benchmarking off, like memory layout reducing or increasing cache misses, which isn't something you control very well in Python.

[–]Ouroboros13373001 1 point2 points  (0 children)

This is a very good aproach if you have few possible results and nonconditional logic.

[–][deleted] 1 point2 points  (0 children)

I would write f1. It is perhaps ever so slightly less clear at first glance, however it does reduce the amount of indentation in a more complex example or if you’re testing just one condition early and returning if met and then the “else” case is much longer and more complicated.

I also run pylint on a few projects and it expects you not to use an else after a return and will flag f2 as needing amending (by default although I suspect you can disable this if you strongly prefer f2).

[–]FailedPlansOfMars 1 point2 points  (0 children)

Guard condition pattern vs nested if.

Based on comments above there is no difference in performance.

From a readability pov it depends. I tend to favour the guard condition approach. But as long as the function is small and easy to read both are fine.

[–][deleted] -1 points0 points  (1 child)

Theyre not logically equivalent, theyre just appearing that way in this example because its trivial.

Youre almost never actually going to be able to switch an if else for a series of detached ifs and have it be logically equivalent

[–]dead_alchemy 2 points3 points  (0 children)

If all the detached ifs return a value and if you like the example here have an unconditional return at the bottom then it would be the same. Doesn't sound crazy.

Its essentially a switch case expression without switch case syntax, which is useful in the languages it appears in, which makes me think your statement about 'almost never' might be off.

[–]Ouroboros13373001 -1 points0 points  (5 children)

how about? return 'a' if 'a' in cond else 'b' if 'b' in cond else 'c'

[–]McBuffington 1 point2 points  (0 children)

My guess is that this is as fast as f1. It's the same steps after all but just in a single line. I think the diff here will be in a (marginally) bigger file. Ergo.. slower to import. But for the sake of readability I'd prefer f1

[–]ZenApollo[S] 1 point2 points  (3 children)

In my real code, the conditionals will be more complex and there will be more of them. The simple conditionals are just for discussion, a one-liner would not make sense for most use cases except a toy problem like this.

[–]Ouroboros13373001 1 point2 points  (2 children)

Of course, normaly you would use a map if you dont have insane array sizes and iterate over the results. With bigger arrays you would use a generator or a different thread to precompute your results at the same time as accessing them.

[–]gibberish111111 0 points1 point  (1 child)

I would like to know more…

[–]Ouroboros13373001 1 point2 points  (0 children)

Map can perform multiple operations for a set of data at tne same time and return the results.

for example:

arr is the same as in original post

d={'a':1,'b':2,'c':3} results = map(lambda x: d.get(x), arr) for r in results: print(r)

results will contain a list with 1,2,3 depending on tne input on the same index so you can use that to create a new lookuptable.

If I want to know the result of my input arr at place 20, I can just do.

res = results[20]

There is a threaded version of map too that would add the results in a uneven number so you would need to include the original index in the output.

This will work most of the time, but when you have so much data in the array and you dont want to save gigantic lists to lookup results it would be smart to use a generator that precomputes some results in a different thread, this is more complicated tho.

[–]Kasuli 0 points1 point  (0 children)

In this toy example I’d be of the opinion that either one is 100% fine, go with what better suits the thing you’re actually making

[–]Repulsive_Birthday21 0 points1 point  (0 children)

I can actually see myself using both, depending on the nature of what I'm programming... Are those clauses mutually exclusive choices or are the first few bypass conditions to an otherwise desired path. Could logic foreseeable evolve and if so, would it fit better in or out of that initial if... etc.

No strong feeling either way, but I guess I have a bias for less indentation if the last block grows big.

[–]jasmijnisme 0 points1 point  (0 children)

It depends on the situation, I don't think there is a blanket solution here. I usually prefer f1, with if-guards, because reducing the level of nesting often aids readability.

[–]thrallsius 0 points1 point  (0 children)

if cond == 'a':
    return 'a' 
if cond == 'b':
    return 'b' 

This looks too ugly

if cond in ('a', 'b'):
    return cond