@Pythonetc compilation, January 2020



A new selection of Python tips and programming from my @pythonetc feed.

Previous publications


The order of blocks exceptmatters: if an exception can be caught by several blocks, then the upper block will catch it. This code will not 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 IndexErrorwill not work because it IndexErroris a subclass LookupError. A more specific 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 concurrent assignment. This means that all variables change immediately after evaluating all expressions. Moreover, you can use any expression that supports assignment, and not just 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 will not automatically use negative number addition instead of subtracting. Consider an 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'

This code does not work:

v1 = Velocity(20_000_000)
v2 = Velocity(10_000_000)

print(v1 - v2)
# TypeError: unsupported operand type(s) for -: 'Velocity' and 'Velocity


Funny, but this code works:

v1 = Velocity(20_000_000)
v2 = Velocity(10_000_000)

print(v1 +- v2)
# 10022302 m/s


This part is written by a Telegram user. orsinium.

A function cannot be both a generator and a regular function. If it is used in the body of a function yield, then it turns into a 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, a regular function may 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]

And this option can be useful in cases with simple expression-generators:

def zeros(*, count: int, lazy: bool):
    if lazy:
        return (0 for _ in range(count))
    return [0] * count


When creating generator comprehension, you must use parentheses:

>>> 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 comprehension is the only argument to 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]


This is not true for functions that have multiple arguments:

>>> 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

Source: https://habr.com/ru/post/undefined/


All Articles