Suppose you have a list of strings and want to select all items, which match at least on of the patterns stored in a different list. Simply put, check against multiple string patterns. Instead of using two nested for-loops:
lst = ["hello", "fafaea", "hello world", "xxx world", "zzz"]
patterns = ["hello", "world"]
matches = []
for item in lst:
for pattern in patterns:
if pattern in item:
matches.append(item)
print(matches) # ['hello', 'hello world', 'xxx world']
... you can utilise Python's built-in any()
function, which evaluates to True
if at least one of the supplied items evaluates to True
:
lst = ["hello", "fafaea", "hello world", "xxx world", "zzz"]
patterns = ["hello", "world"]
matches = [
item
for item in lst
if any(pattern in item for pattern in patterns)
]
print(matches) # ['hello', 'hello world', 'xxx world']
Since Python 3.9, decorators are a lot more flexible than before. Any callable can now be a decorator. With that being said, a couple of (seen from the perspective of a Python 3.8 developer) crazy things can be done.
Suppose we have three decorators transforming the returned text of a given function:
# voices.py
import functools
def normal(func):
return func
def shout(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
def whisper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).lower()
return wrapper
From Python 3.9 onwards, we can put all three into a dictionary, let the user decide, which one to use, and apply it to a given function as follows:
# previous code in voices.py
DECORATORS = {
"normal": normal,
"shout": shout,
"whisper": whisper,
}
voice = input(f"Choose your voice ({', '.join(DECORATORS)}):")
@DECORATORS[voice]
def get_story():
return "This is a sample text."
print(get_story())
Executing the script at hand gives us the following output:
$ python voices.py
Choose your voice (normal, shout, whisper): shout
THIS IS A SAMPLE TEXT.
Since Python 3.8, named expressions, also known as the walrus-operator :=
, exist.
This tip includes a simple use case, where a named expression can be useful and why a for-loop does not work.
Suppose you have a simple text file called data.txt with the following content.
This is just a sample text containing a bunch of words, letters,
you name it. It has absolutely no meaning, so do not spend too much
time adding any meaning to it.
Here, line breaks were only inserted for readability purposes. In the use case, the file consists of a single line. The idea is to print at most ten characters per line until the end of the file is reached. Utilising a named expression, this is fairly easy:
# t.py
f = open("data.txt")
while letters := f.read(10):
print(letters)
f.close()
Executing it results in ...
$ python t.py
This is ju
st a sampl
e text con
taining a
bunch of w
ord, lette
rs, you na
me it. It
has absolu
tely no me
aning, so
do not spe
nd too muc
h time add
ing any me
aning to i
t.
One may have the idea to replace it with a for-loop as follows:
# t.py
f = open("data.txt")
for letters in f.read(10):
print(letters)
f.close()
However, we end up with the following result:
$ python t.py
T
h
i
s
i
s
j
u
What happened here?
When using a for-loop, the expression after the keyword in
is evaluated only once: When the loop is initialised.
Consequently, we iterate over a string of ten characters and print each of them on a separate line as shown in the previous output.
When using a named expression, the expression on the right side of the :=
operator is always evaluated, when before a new iteration starts.
Thus, the variable letters
holds at most ten characters.
If it is empty, the expression evaluates to False
and the execution is stopped.
Let's assume we have a function, which has no real return value.
def greet():
print("Hello World")
Should I annotate this function with typing.NoReturn
, because no explicit return-statement was written?
Technically seen, the function has a return-value: None
.
You can see it if you capture the return value of the built-in print()
function:
value = print("Hello\n")
print(value)
# Hello
# None
Consequently, our greet()
function needs to get the None
return type annotated.
def greet() -> None:
print("Hello World")
The type typing.NoReturn
is meant to be used if something does really never return.
This is the case in infinite loops, ...
from typing import NoReturn
def loop() -> NoReturn:
while True:
pass
... when the function terminates the script like sys.exit()
or if an exception is raised:
from typing import NoReturn
def exc() -> NoReturn:
raise ValueError("Let's terminate here...")