![](https://habrastorage.org/webt/7e/0u/p1/7e0up1b_kwsv8ztw27ay70sxnxa.jpeg)
It is a new selection of tips and tricks about Python and programming from my Telegram-channel @pythonetc.
← Previous publications.
![](https://habrastorage.org/getpro/habr/post_images/208/1cc/1fa/2081cc1fa6dbebbbb6ddd4108512ff5b.png)
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
![](https://habrastorage.org/getpro/habr/post_images/208/1cc/1fa/2081cc1fa6dbebbbb6ddd4108512ff5b.png)
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]
![](https://habrastorage.org/getpro/habr/post_images/208/1cc/1fa/2081cc1fa6dbebbbb6ddd4108512ff5b.png)
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
![](https://habrastorage.org/getpro/habr/post_images/208/1cc/1fa/2081cc1fa6dbebbbb6ddd4108512ff5b.png)
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)
# <generator object zeros at 0x7ff0062f2a98>
zeros(count=10, lazy=False)
# <generator object zeros at 0x7ff0073da570>
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)
# <generator object _lazy_zeros at 0x7ff0062f2750>
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
![](https://habrastorage.org/getpro/habr/post_images/208/1cc/1fa/2081cc1fa6dbebbbb6ddd4108512ff5b.png)
Brackets are required to create a generator comprehension:
>>> g = x**x for x in range(10)
File "<stdin>", line 1
g = x**x for x in range(10)
^
SyntaxError: invalid syntax
>>> g = (x**x for x in range(10))
>>> g
<generator object <genexpr> 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')
<generator object <genexpr> at 0x7f90ed650468>
>>>
>>>
>>> print(x**x for x in range(4), end='\n')
File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument