Tips and tricks from my Telegram-channel @pythonetc, January 2020
It is a new selection of tips and tricks about Python and programming from my Telegram-channel @pythonetc.
← Previous publications.
The order of except
blocks matter: if exceptions can be caught by more than one block, the higher block applies. The following code doesn«t work as intended:
import logging
def get(storage, key, default):
try:
return storage[key]
except LookupError:
return default
except IndexError:
return get(storage, 0, default)
except TypeError:
logging.exception('unsupported key')
return default
print(get([1], 0, 42)) # 1
print(get([1], 10, 42)) # 42
print(get([1], 'x', 42)) # error msg, 42
except IndexError
never works since IndexError
is a subclass of LookupError
. More concrete exception should always be higher:
import logging
def get(storage, key, default):
try:
return storage[key]
except IndexError:
return get(storage, 0, default)
except LookupError:
return default
except TypeError:
logging.exception('unsupported key')
return default
print(get([1], 0, 42)) # 1
print(get([1], 10, 42)) # 1
print(get([1], 'x', 42)) # error msg, 42
Python supports parallel assignment meaning that all variables are modified at once after all expressions are evaluated. Moreover, you can use any expression that supports assignment, not only variables:
def shift_inplace(lst, k):
size = len(lst)
lst[k:], lst[0:k] = lst[0:-k], lst[-k:]
lst = list(range(10))
shift_inplace(lst, -3)
print(lst)
# [3, 4, 5, 6, 7, 8, 9, 0, 1, 2]
shift_inplace(lst, 5)
print(lst)
# [8, 9, 0, 1, 2, 3, 4, 5, 6, 7]
Python substitution does not fallback to addition with negative value. Consider the example:
class Velocity:
SPEED_OF_LIGHT = 299_792_458
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return type(self)(
(self.amount + other.amount) /
(
1 +
self.amount * other.amount /
self.SPEED_OF_LIGHT ** 2
)
)
def __neg__(self):
return type(self)(-self.amount)
def __str__(self):
amount = int(self.amount)
return f'{amount} m/s'
That doesn«t work:
v1 = Velocity(20_000_000)
v2 = Velocity(10_000_000)
print(v1 - v2)
# TypeError: unsupported operand type(s) for -: 'Velocity' and 'Velocity
Funny enough, that does:
v1 = Velocity(20_000_000)
v2 = Velocity(10_000_000)
print(v1 +- v2)
# 10022302 m/s
Today’s post is written by Telegram-user orsinium.
Function can’t be generator and regular function at the same time. If yield is presented anywhere in the function body, the function turns into generator:
def zeros(*, count: int, lazy: bool):
if lazy:
for _ in range(count):
yield 0
else:
return [0] * count
zeros(count=10, lazy=True)
#
zeros(count=10, lazy=False)
#
list(zeros(count=10, lazy=False))
# []
However, regular function can return another iterator:
def _lazy_zeros(*, count: int):
for _ in range(count):
yield 0
def zeros(*, count: int, lazy: bool):
if lazy:
return _lazy_zeros(count=count)
return [0] * count
zeros(count=10, lazy=True)
#
zeros(count=10, lazy=False)
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Also, for simple cases generator expressions could be useful:
def zeros(*, count: int, lazy: bool):
if lazy:
return (0 for _ in range(count))
return [0] * count
Brackets are required to create a generator comprehension:
>>> g = x**x for x in range(10)
File "", line 1
g = x**x for x in range(10)
^
SyntaxError: invalid syntax
>>> g = (x**x for x in range(10))
>>> g
at 0x7f90ed650258>
However they can be omitted if a generator comprehension is the only argument for the function:
>>> list((x**x for x in range(4)))
[1, 1, 4, 27]
>>> list(x**x for x in range(4))
[1, 1, 4, 27]
That doesn«t work for function with more than one argument:
>>> print((x**x for x in range(4)), end='\n')
at 0x7f90ed650468>
>>>
>>>
>>> print(x**x for x in range(4), end='\n')
File "", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument