company_banner

Подборка @pythonetc, июнь 2018


    Привет. Я веду авторский канал @pythonetc с советами про Python в частности и про программирование в целом. С этого месяца мы запускаем серию подборок с лучшими постами за месяц в переводе на русский.


    Передача данных по цепочке вызовов


    Когда вы хотите передать какую-то информацию по цепочке вызовов, то обычно используете самый простой способ: передаете данные в виде аргументов функций.


    Но иногда очень неудобно изменять все функции в цепочке, только чтобы передать новую порцию данных. В таких случаях лучше создать своеобразный контекст, который будут использовать функции. Как это сделать?


    Простейшее решение — глобальная переменная. В Python в качестве хранителей контекста можно использовать модули и классы, поскольку они, строго говоря, тоже глобальные переменные. Возможно, вы периодически так и делаете, скажем, когда создаете логгеры.


    Если у вас многопоточное приложение, то обычные глобальные переменные не помогут, поскольку не потокобезопасны (thread-safe). Одновременно у вас могут исполняться несколько цепочек вызовов, и каждой понадобится свой контекст. Модуль threading предоставляет потокобезопасный объект threading.local(). Сохраняйте в нем любые данные, просто обращаясь к атрибутам: threading.local().symbol = '@'.


    Однако оба подхода не concurrency-safe, то есть они не будут работать в цепочках вызовов корутин, в которых корутины могут не вызывать другие корутины, а делать на них await. Если корутина в состоянии ожидания, event loop может запустить другую корутину из другой цепочки. Этот вариант не будет работать:


    import asyncio
    import sys
    
    global_symbol = '.'
    
    async def indication(timeout):
        while True:
            print(global_symbol, end='')
            sys.stdout.flush()
            await asyncio.sleep(timeout)
    
    async def sleep(t, indication_t, symbol='.'):
        loop = asyncio.get_event_loop()
    
        global global_symbol
        global_symbol = symbol
        loop.create_task(indication(indication_t))
        await asyncio.sleep(t)
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(
        sleep(1, 0.1, '0'),
        sleep(1, 0.1, 'a'),
        sleep(1, 0.1, 'b'),
        sleep(1, 0.1, 'c'),
    ))

    Решить проблему можно, заставив event loop сохранять и восстанавливать контекст при каждом возвращении к корутине. Так поступает модуль aiotask_context, который с помощью loop.set_task_factory меняет способ создания объектов задач. Вот такой вариант сработает:


    import asyncio                                
    import sys                                    
    import aiotask_context as context             
    
    async def indication(timeout):                
        while True:                               
            print(context.get('symbol'), end='') 
            sys.stdout.flush()                    
            await asyncio.sleep(timeout)          
    
    async def sleep(t, indication_t, symbol='.'):
        loop = asyncio.get_event_loop()           
    
        context.set(key='symbol', value=symbol)  
        loop.create_task(indication(indication_t))
        await asyncio.sleep(t)                    
    
    loop = asyncio.get_event_loop()               
    loop.set_task_factory(context.task_factory)  
    loop.run_until_complete(asyncio.gather(       
        sleep(1, 0.1, '0'),                       
        sleep(1, 0.1, 'a'),                       
        sleep(1, 0.1, 'b'),                       
        sleep(1, 0.1, 'c'),                       
    ))

    Создаем SVG


    SVG — векторный графический формат, хранящий информацию об изображении в виде всех форм и чисел, необходимых для отрисовки в XML. К примеру, оранжевый круг можно представить так:


    <svg xmlns="http://www.w3.org/2000/svg">
        <circle cx="125" cy="125" r="75" fill="orange"/>
    </svg>

    Поскольку SVG является подмножеством XML, можно довольно легко создавать SVG-файлы на любом языке. В том числе и на Python, например, с помощью lxml. Но есть и модуль svgwrite, созданный как раз для создания SVG.


    Вот пример, как можно отобразить последовательность Рекамана в виде диаграммы, которую вы видели в начале статьи.


    Обращение к внешним областям видимости


    Когда вы используете переменную в Python, он сначала ищет ее в текущей области видимости. Если не находит, то ищет уже в области на уровень выше. И так до тех пор, пока не дойдет глобального пространства имен.


    x = 1
    def scope():
        x = 2
        def inner_scope():
            print(x)  # prints 2
        inner_scope()
    scope()

    Но присвоение переменной работает иначе. Новая переменная всегда создается в текущей области видимости, если только не указано global или nonlocal:


    x = 1
    def scope():
        x = 2
        def inner_scope():
            x = 3
            print(x)  # prints 3
        inner_scope()
        print(x)  # prints 2
    scope()
    print(x)  # prints 1

    global позволяет использовать переменные глобального пространства имен, а в случае с nonlocal Python ищет переменную в ближайшем объемлющем контексте. Сравните:


    x = 1
    def scope():
        x = 2
        def inner_scope():
            global x
            x = 3
            print(x)  # prints 3
        inner_scope()
        print(x)  # prints 2
    scope()
    print(x)  # prints 3
    
    x = 1
    def scope():
        x = 2
        def inner_scope():
            nonlocal x
            x = 3
            print(x)  # prints 3
        inner_scope()
        print(x)  # prints 3
    scope()
    print(x)  # prints 1

    Выполнение скриптов


    python поддерживает несколько способов запуска скрипта. Обычная команда python foo.py просто исполняет foo.py.


    Также можно использовать конструкцию python -m foo. Если foo не является пакетом, то система найдет foo.py в sys.path и выполнит. Если же является, Python выполнит foo/__init__.py, а потом foo/__main__.py. Обратите внимание, что переменная __name__ в ходе выполнения __init__.py принимает значение foo, а в ходе выполнения __main__.py__main__.


    Также можно использовать форму python dir/ или даже python dir.zip. Тогда python будет искать dir/__main__.py, и если найдет — выполнит.


    $ ls foo
    __init__.py  __main__.py
    $ cat foo/__init__.py
    print(__name__)
    $ cat foo/__main__.py
    print(__name__)
    
    $ python -m foo
    foo
    __main__
    $ python foo/
    __main__
    $ python foo/__init__.py
    __main__

    Количество секунд с начала эпохи


    До появления Python 3.3 трудно было преобразовать объект datetime в количество секунд с начала эпохи Unix.


    Логичнее всего использовать метод strftime, который умеет форматировать datetime. Взяв %s в качестве формата, можно получить timestamp.


    naive_time = datetime(2018, 3, 31, 12, 0, 0)
    utc_time = pytz.utc.localize(naive_time)
    ny_time = utc_time.astimezone(
        pytz.timezone('US/Eastern'))

    ny_time— это абсолютно то же время, что и utc_time, но записанное в том виде, как это принято в Нью-Йорке:


    # utc_time
    datetime.datetime(2018, 3, 31, 12, 0,
        tzinfo=<UTC>)
    # utc_time
    datetime.datetime(2018, 3, 31, 8, 0,
        tzinfo=<DstTzInfo 'US/Eastern' ...>)

    Если время одинаковое, то и таймстемпы должны быть эквивалентны:


    In : int(utc_time.strftime('%s')),
         int(ny_time.strftime('%s'))
    Out: (1522486800, 1522468800)

    Эээ, что? Почему они разные? Дело в том, что использовать strftime для решения этой задачи нельзя. В Python strftime вообще не поддерживает %s в качестве аргумента, а работает это лишь потому, что внутри вызывается функция strftime() платформы С-библиотеки. Но, как видите, часовой пояс объекта datetime полностью проигнорирован.


    Правильный результат можно получить с помощью простого вычитания:


    In : epoch_start = pytz.utc.localize(
          datetime(1970, 1, 1))
    
    In : (utc_time - epoch_start).total_seconds()
    Out: 1522497600.0
    
    In : (utc_time - epoch_start).total_seconds()
    Out: 1522497600.0

    А если вы используете Python 3.3+, то можете решить проблему с помощью метода timestamp класса datetime: utc_time.timestamp().

    • +33
    • 8,7k
    • 8

    Mail.Ru Group

    627,49

    Строим Интернет

    Поделиться публикацией

    Похожие публикации

    Комментарии 8
      0
      Были уже такие. Один закончился в апреле, другой — еще в сентябре 2014-го. И подборки статей у обоих были повнушительнее.
        0
        Они теперь на стендэлоне pythondigest.ru
          0
          Да, нас попросили уйти, потому что один и тот же контент на хабре и в других местах нельзя размещать.
          0
          Так тут хотя бы не сборник ссылок на новые релизы библиотек и статьи с не всегда очевидными заголовками
            +1
            Потому оно и называлось «Pygest #ХХ. Релизы, статьи, интересные проекты, пакеты и библиотеки из мира Python». Мне кажется, что дайджест на то и назван дайджестом, что содержит краткое изложение разных статей, по возможности со ссылками на них. А не компоновку из пяти статей на разные темы в одном теле, которые, являясь параграфами статьи Хабра, воспринимаются как одно целое.
          –6
          >Питонячий дайджест, выпуск 1

          У нас уже есть pythondigest.ru спасибо mail.ru я уверен, что вы знали об этом. И очень некрасиво с вашей стороны такое поведение. -1 в карму. Ваш дайджест читать из принципа не буду.
            +1

            Да, кажется, название и правда слегка не то означает; поправил.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое