Python, for all its ease of learning, has some pockets of real complexity. If you’ve ever read through the programming language’s documentation, you’ve probably gotten a sense of that. If you’re new to the language, here are some features you might not be familiar with, but could nonetheless prove useful (or at least fun to experiment with).
Negative Strides on Slices
Slices are a fairly modern innovation in several computer languages. In Python, we can use them on lists, tuples and strings to get a subset of a particular sequence. You might be familiar with slices in that context… but did you know you can also use them to reverse a sequence? What do you think this code outputs:
alphabet = [chr(c) for c in range(ord('a'), ord('z')+1)] # 'a'..'z'
print(alphabet[::-5])
That first line puts the entire alphabet into the alphabet variable. The -5
, the third parameter of the slice, is stride, an interval. Because it’s negative, it runs backwards.
['z', 'u', 'p', 'k', 'f', 'a']
An Alternative Way to Do Multiple Assignments
You may be familiar with multiple assignments where you assign values to several variables in one statement, like this:
a = b =9
print(f'a={a} b= {b}')
But you can also do multiple assignments this way:
a,b = 5,4
print(f'a={a} b={b}')
This outputs: a=5 b = 4
.
Using the Walrus Operator
This was only recently introduced in Python 3.8. In C, it’s not uncommon to assign a value to a variable in ab (an expression). Now, Python has it with the :=
operator.
(This one I had to test using a repl online, as none of the Python versions I had installed on Windows or Ubuntu were version 3.8.)
a= 9
b =-3.4
print(c := a*a+b)
print(c)
The output was 77.6 twice, with C being assigned the value 77.6 in the first print statement.
‘Else’ Blocks On ‘For’ and ‘While’
This is something that doesn’t happen with other programming languages. I personally find using them confusing and have never used them, but if you want to impress your friends or colleagues….
After a for
or while
loop you can have an else
. The else
branch only runs if there was no break in the loop
body.
Here’s an example. What do you think it will print out?
for i in range(5):
print(f'loop index={i}')
else:
print(f'else i={i}')
This is the output:
loop index=0
loop index=1
loop index=2
loop index=3
loop index=4
else i=4
It’s pretty confusing! If we try it this way, code below the else
never runs, but it’s just as easy to not use else
.
for i in range(5):
print(f'loop index={i}')
if i==4:
break;
else:
print(f'else i={i}')
My suggestion: Don’t use it, just be aware of it so you won’t end up scratching your head if you see it used.
Else
is useful, though, in a try/except
, when comes after the except
and lets you handle code if the specified exceptions don’t occur.
In this somewhat contrived example with two user-defined exceptions, the guess function returns exceptions if the value is too small or too large. Only if the value is correct does the else
get hit:
class Error(Exception):
"""Base class for other exceptions"""
pass
class ValueTooSmallError(Error):
"""Raised when the input value is too small"""
pass
class ValueTooLargeError(Error):
"""Raised when the input value is too large"""
pass
def guess(value):
if value <5:
raise ValueTooSmallError
elif value >15:
raise ValueTooLargeError
return value
try:
answer=guess(5)
except ValueTooSmallError:
print("Too small")
except ValueTooLargeError:
print("Too large")
else:
print("Good guess")
Position ‘Only’ Parameters in Functions
This is another 3.8 feature. When you call a function, you can specify parameters by position or with the name of the parameter. If the parameter list includes a parameter /
and *
, then whether a parameter is positional only, positional or keyword, or keyword only depends upon where the parameter is relative to /
and *
. Note both /
and *
are optional and *
was already used in Python 3.7.
This diagram is taken from PEP 5470:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
The various cases are defined in more detailed in the 3.8 documentation on defining functions.
Conclusion
Staying on top of the changes in a language like Python is never easy, but it can be fun finding out new things! Careful reading of the What’s new in Python 3.xx webpages is a good start; they are already there for both 3.9 and 3.10.
For-else clauses. You wrote “I personally find using them confusing and have never used them”.
It seems you don’t understand their purpose.
The full clause requires if-break inside. It is extremely useful in cases where you look for something in a loop and want to perform an action if not found. Like this:
for line in file:
if pattern in line:
break # found!
else
action(pattern) # not found in the whole file
Write this with no ‘else’ and compare.
How is 9*9-3.4=77.6?
And why did Google recommend this to me?
I disagree strongly with your suggestion that one should not use “else” in a “for loop”.
The “else” statement is useful in the commonly found case where one usually has to resort to a ‘flag variable’ to indicate whether the loop exited because some condition is met or whether the sequence is exhausted. Simple example
for i in sequence:
do_something(i)
if i == some_sentinel:
break
else:
print(“Sentinel not found but sequence is exhausted”)
return
Hey David,
Thanks for cool items mentioned. That is the first time I meet “chr” and “ord” built-ins. Very attractive way to define an alphabet sequence. Thank you 🙂
Btw, regarding the
for
…
else
…
construction – used it couple times. As far as I caught, it is useful when you are searching something by means of an iteration and want to describe some “nothing found” actions. This way it will be more laconic and consistent as “nothing found” code chunk will be a cohesive part of the search loop.
This is a good tour of 4 of the 5 features that you mentioned. The “else” block for “for” or “while”, that used to confuse me too. But better to shine a light than to curse the darkness. I figured out what’s going on there.
Most loops are run with the intent to complete the entire run, or to run indefinitely. That’s by far the most common pattern, and objects like iterators, generators, and list comprehensions work well in this pattern. The “else” serves a purpose as the exception clause in another common loop pattern: when you run a loop expecting to break out of it before it completes. It’s more rare in common usage, but it’s just as valid a pattern as the one that’s typically used.