Tips and tricks from my Telegram-channel @pythonetc, January 2020

7e0up1b_kwsv8ztw27ay70sxnxa.jpeg

It is a new selection of tips and tricks about Python and programming from my Telegram-channel @pythonetc.

← Previous publications.

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


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]


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


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

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


2081cc1fa6dbebbbb6ddd4108512ff5b.png


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

© Habrahabr.ru