Tips and tricks from my Telegram-channel @pythonetc, October 2019


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

    Previous publications


    If you want to iterate over several iterables at once, you can use the zip function (it has nothing to do with ZIP file format):

    from datetime import timedelta
    
    names = [
        'Eleven. Return and Revert',
        'Wilderness',
        'The Menagerie Inside',
        'Evaporate',
    ]
    
    years = [
        2010,
        2013,
        2015,
        2018,
    ]
    
    durations = [
        timedelta(minutes=57, seconds=38),
        timedelta(minutes=48, seconds=5),
        timedelta(minutes=46, seconds=34),
        timedelta(minutes=43, seconds=25),
    ]
    
    print('Midas Fall LPs:')
    for name, year, duration in zip(
        names, years, durations
    ):
        print(f'  * {name} ({year}) — {duration}')

    Output:

    Midas Fall LPs:
      * Eleven. Return and Revert (2010) — 0:57:38
      * Wilderness (2013) — 0:48:05
      * The Menagerie Inside (2015) — 0:46:34
      * Evaporate (2018) — 0:43:25


    A generator can be stopped. You can explicitly call g.close() but usually garbage collector does that for you. Once close is called, the GeneratorExit is raised at the point where the generator function was paused:

    def gen():
        try:
            yield 1
            yield 2
        finally:
            print('END')
    
    
    g = gen()
    print(next(g))  # prints '1'
    g.close()  # prints 'END'

    Mind three things. First, you can’t yield values while handling GeneratorExit:

    def gen():
        try:
            yield 1
        finally:
            yield 3
    
    
    g = gen()
    next(g)
    g.close()  # RuntimeError

    Second, the exception is not raised if a generator is not yet started, but the generator still becomes stopped:

    def gen():
        try:
            yield 1
        finally:
            print('END')
    
    
    g = gen()
    g.close()  # nothing
    print(list(g))  # prints '[]'

    Third, close does nothing if a generator is already finished:

    def gen():
        try:
            yield 1
            yield 2
        finally:
            print('END')
    
    
    g = gen()
    print(list(g))
    print('Closing now')
    g.close()
    
    # END
    # [1, 2]
    # Closing now


    f-strings allow you to specify the width for the printed value as well as other format specifiers:

    >>> x = 42
    >>> f'{x:5}+{x:15f}'
    '   42+      42.000000'

    They can also contain evaluated expressions which can be useful when width is unknown upfront:

    def print_table(matrix):
        cols_width = [
            max(len(str(row[col])) for row in matrix)
            for col in range(len(matrix[0]))
        ]
    
        for row in matrix:
            for i, cell in enumerate(row):
                print(
                    f'{cell:{cols_width[i]}} ',
                    end=''
                )
            print()
    
    albums = [
        ['Eleven. Return and Revert', 2010],
        ['Wilderness', 2013],
        ['The Menagerie Inside', 2015],
        ['Evaporate', 2018],
    ]
    
    print_table(albums)

    Output:

    Eleven. Return and Revert 2010
    Wilderness                2013
    The Menagerie Inside      2015
    Evaporate                 2018


    If your class is derived from another, the metaclass of your class have to be also derived from the metaclass of that class:

    from collections import UserDict
    from abc import ABCMeta
    
    # ABCMeta is a metaclass of UserDict
    class MyDictMeta(ABCMeta):
        def __new__(cls, name, bases, dct):
            return super().__new__(cls, name, bases, dct)
    
    class MyDict(UserDict, metaclass=MyDictMeta):
        pass

    It may be a good idea to get the metaclass of that other class automatically:

    def create_my_dict_class(parents):
        class MyDictMeta(*[type(c) for c in parents]):
            def __new__(cls, name, bases, dct):
                return super().__new__(cls, name, bases, dct)
    
        class MyDict(*parents, metaclass=MyDictMeta):
            pass
    
    
    MyDict = create_my_dict_class((UserDict,))


    __init__ allows you to modify an object right after the creation. If you want to control what is created you should use __new__ instead:

    from typing import Tuple, Dict
    from cached_property import cached_property
    
    
    class Numbers:
        _LOADED: Dict[Tuple[int, ...], 'Numbers'] = {}
    
        def __new__(cls, ints: Tuple[int, ...]):
            if ints not in cls._LOADED:
                obj = super().__new__(cls)
                cls._LOADED[ints] = obj
    
            return cls._LOADED[ints]
    
        def __init__(self, ints: Tuple[int, ...]):
            self._ints = ints
    
        @cached_property
        def biggest(self):
            print('calculating...')
            return max(self._ints)
    
    
    print(Numbers((4, 3, 5)).biggest)
    print(Numbers((4, 3, 5)).biggest)
    print(Numbers((4, 3, 6)).biggest)
    
    Mail.ru Group
    1,223.93
    Building the Internet
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.