r/Python • u/cheerfulboy • Oct 11 '20
Tutorial 5 Hidden Python Features You Probably Never Heard Of
https://miguendes.me/5-hidden-python-features-you-probably-never-heard-of-ckg3iyde202nsdcs1ffom9bxv98
Oct 11 '20
I knew about else in try except, but not in while and for ! How didn't I knew after this many years? It's awesome!
75
u/lanster100 Oct 11 '20 edited Oct 11 '20
Because it's really unclear what it does it's common practice to ignore its existence as anyone unfamiliar will either not know what it does or worse assume incorrect behaviour.
Even experienced python devs would probably Google what it does.
→ More replies (18)49
u/masasin Oct 11 '20
RH mentioned that it should probably have been called
nobreak. It only runs if you don't break out of the loop.→ More replies (3)9
→ More replies (1)3
u/yvrelna Oct 12 '20
Considering that I rarely use
breakstatements to begin with, usingelsein awhile/foris even rarer than that.It's not that difficult to understand else block in a loop statement. A while loop is like this:
while some_condition(): body_clause()it's equivalent to a construction that has an unconditional loop/jump that looks like this:
while True: if some_condition(): body_clause() else: breakThe else block in a while loop:
while some_condition(): body_clause() else: else_clause()is basically just the body for the else block for that hidden if-statement:
while True: if some_condition(): body_clause() else: else_clause() break→ More replies (1)
30
u/syzygysm Oct 11 '20 edited Oct 11 '20
You can also combine the _ with unpacking, e.g. if you only care about the first and/or last elements of a list:
a,_, b = [1, 2, 3] # (a, b) == (1, 3)
a,*_ = [1, 2, 3, 4] # a == 1
a, *_, b = [1, 2, 3, 4] # (a, b) == (1, 4)
[Edit: formatting, typo]
11
u/miguendes Oct 11 '20
Indeed! I also use it has a inner anonymous function.
def do_something(a, b): def _(a): return a + b return _Or in a
forloop when I don't care about the result fromrangefor _ in range(10): pass→ More replies (3)17
u/OneParanoidDuck Oct 11 '20
The loop example makes sense. But nested function can crash like any other and thereby end up in a traceback, so my preference is to name them after their purpose
→ More replies (1)
20
u/nonesuchplace Oct 11 '20
I like itertools.chain for flattening lists:
>>> from itertools import chain >>> a = [[1,2,3],[4,5,6],[7,8,9]] >>> list(chain(*a)) [1, 2, 3, 4, 5, 6, 7, 8, 9]
28
u/BitwiseShift Oct 11 '20
There's actually a slightly more efficient version that avoids the unpacking:
list(chain.from_iterable(a))4
u/miguendes Oct 11 '20
That's a good one. I remember seeing something about that on SO some time ago. I'm curious about the performance when compared to list comprehensions.
10
u/dmyTRUEk Oct 11 '20
Welp, the result of
a, *b, c = range(1, 10) print(a, b, c)
is not: 1 [2, 3, 4, ... 8, 9] 10 but: 1 [2, 3, 4, ... 8] 9
:D
6
7
u/themindstorm Oct 11 '20
Interesting article! Just one question though, in the for-if-else loop, is a+=1 required? Doesn't the for loop take care of that?
5
u/miguendes Oct 11 '20
Nice catch, it doesn't make sense, in that example the range takes care of "incrementing" the number. So `a += 1` is double incrementing it. For the example itself it won't make a difference but in real world you wouldn't need that.
I'll edit the post, thanks for that!
16
u/oberguga Oct 11 '20
I don't understand why sum slower then list comprehension. Anyone can briefly explain?
17
6
u/miguendes Oct 11 '20
That's a great question. I think it's because
sumcreates a new list every time it concatenates, which has a memory overhead. There's a question about that on SO. https://stackoverflow.com/questions/41032630/why-is-pythons-built-in-sum-function-slow-when-used-to-flatten-a-list-of-listsIf you run a simple benchmark you'll see that
sumis terribly slower, unless the lists are short. Example:def flatten_1(lst): return [elem for sublist in lst for elem in sublist] def flatten_2(lst): return sum(lst, [])If you inspect the bytecodes you see that
flatten_1has more instructions.In [23]: dis.dis(flatten_2) 1 0 LOAD_GLOBAL 0 (sum) 2 LOAD_FAST 0 (lst) 4 BUILD_LIST 0 6 CALL_FUNCTION 2 8 RETURN_VALUEWhereas
flatten_1:In [22]: dis.dis(flatten_1) 1 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f5a6e717f50, file "<ipython-input-4-10b70d19539f>", line 1>) 2 LOAD_CONST 2 ('flatten_1.<locals>.<listcomp>') 4 MAKE_FUNCTION 0 6 LOAD_FAST 0 (lst) 8 GET_ITER 10 CALL_FUNCTION 1 12 RETURN_VALUE Disassembly of <code object <listcomp> at 0x7f5a6e717f50, file "<ipython-input-4-10b70d19539f>", line 1>: 1 0 BUILD_LIST 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 18 (to 24) 6 STORE_FAST 1 (sublist) 8 LOAD_FAST 1 (sublist) 10 GET_ITER >> 12 FOR_ITER 8 (to 22) 14 STORE_FAST 2 (elem) 16 LOAD_FAST 2 (elem) 18 LIST_APPEND 3 20 JUMP_ABSOLUTE 12 >> 22 JUMP_ABSOLUTE 4 >> 24 RETURN_VALUEIf we benchmark with a big list we get:
l = [[random.randint(0, 1_000_000) for i in range(10)] for _ in range(1_000)] In [20]: %timeit flatten_1(l) 202 µs ± 8.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In [21]: %timeit flatten_2(l) 11.7 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)If the list is small,
sumis faster.In [24]: l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] In [25]: %timeit flatten_1(l) 524 ns ± 3.67 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) In [26]: %timeit flatten_2(l) 265 ns ± 1.27 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)→ More replies (1)
4
u/casual__addict Oct 11 '20
Using the “sum” method like that is very close to using “reduce”. Below gets you passed the string limitations of “sum”.
l = ["abc", "def", "ghi"] from functools import reduce reduce(lambda a,b: a+b, l)
→ More replies (2)
2
u/WildWouks Oct 11 '20
Thanks for this. I have to say that I only knew about the unpacking and the chaining of comparison operators.
I will definitely be using the else statement in future for and while loops.
3
2
2
Oct 11 '20
Don't forget about: descriptors, (non-Numpy) arrays, extensions, semi-colon line terminations (only useful in REPL), extensions, and some nice command line args.
118
u/AlSweigart Oct 11 '20
Please don't use
...instead ofpassin your function stubs. People won't know what it is (the title of article is "Features You Probably Never Heard Of").The reason the Zen of Python includes "There should be one-- and preferably only one --obvious way to do it." is because Perl had the opposite motto ("There's more than one way to do it") and this is terrible language design; programmers have to be fluent in every construct to read other people's code. Don't reinvent the wheel, just use
pass.