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

в 7:31, , рубрики: python, Блог компании Mail.Ru Group

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

Привет. Я веду канал @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().

Автор: Пуштаев Вадим

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js