Легкий асинхронный Python. Прекомпилятор?

представьте, что у вас есть такая тяжелая функция io:

def getMd5Sum(path):
    with open(path) as f:
        return md5(f.read()).hexdigest()

Считаете ли вы, что Python достаточно гибок, чтобы разрешить такой код (обратите внимание на $):

def someGuiCallback(filebutton):
    ...
    path = filebutton.getPath()
    md5sum = $getMd5Sum()
    showNotification("Md5Sum of file: %s" % md5sum)
    ...

Чтобы выполнить что-то вроде этого:

def someGuiCallback_1(filebutton):
    ...
    path = filebutton.getPath()
    Thread(target=someGuiCallback_2, args=(path,)).start()

def someGuiCallback_2(path):
    md5sum = getMd5Sum(path)
    glib.idle_add(someGuiCallback_3, md5sum)

def someGuiCallback_3(md5sum):
    showNotification("Md5Sum of file: %s" % md5sum)
    ...

(glib.idle_add просто помещает функцию в очередь основного потока)

Я думал об использовании декораторов, но они не позволяют мне получить доступ к «содержимому» функции после вызова. (часть showNotification)

Думаю, я мог бы написать «компилятор» для изменения кода перед выполнением, но это не похоже на оптимальное решение.

У вас есть идеи, как сделать что-то вроде вышеперечисленного?


person Thomas Ahle    schedule 08.11.2009    source источник


Ответы (3)


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

from threading import Thread
import hashlib

def async(gen):
    def func(*args, **kwargs):
        it = gen(*args, **kwargs)
        result = it.next()
        Thread(target=lambda: list(it)).start()
        return result
    return func

@async
def test(text):
    # synchronous part (empty in this example)
    yield # Use "yield value" if you need to return meaningful value
    # asynchronous part[s]
    digest = hashlib.md5(text).hexdigest()
    print digest
person Denis Otkidach    schedule 09.11.2009
comment
Отличная идея с генераторами! Я сделал версию, которая может включать и выключать основной поток любое количество раз: code.activestate.com/recipes/576952 По какой-то причине он блокируется в случайных местах, если я использую glib. Наверное, надо будет у них спросить об этом :) - person Thomas Ahle; 10.11.2009

Вы можете использовать хуки импорта для достижения этой цели...

... но я лично считаю это немного неприятным.

Однако, если вы хотите пойти по этому пути, по сути, вы должны сделать следующее:

  • Вы добавляете обработчик импорта для расширения (например, ".thpy").
  • Затем этот хук импорта отвечает за (по сути) передачу некоторого действительного кода в результате импорта.
  • Этот действительный код получает аргументы, которые эффективно относятся к импортируемому файлу.
  • Это означает, что ваш прекомпилятор может выполнять любые преобразования исходного кода на пути к нему.

С другой стороны:

  • Хотя использование хуков импорта таким образом будет работать, оно сильно удивит любого сопровождающего или ваш код. (плохая идея имхо)
  • То, как вы это делаете, зависит от imputil, который был удален в python 3.0, который означает, что ваш код, написанный таким образом, имеет ограниченный срок службы.

Лично я бы туда не пошел, но если вы пойдете, то есть выпуск Python Magazine где такие вещи описаны довольно подробно, и я бы посоветовал получить прошлый выпуск, чтобы прочитать об этом. (Написано Полом Макгуайром, выпуск за апрель 2009 г., вероятно, доступен в формате PDF).

В частности, в качестве примера используется imputil и pyparsing, но принцип тот же.

person Michael Sparks    schedule 08.11.2009

Как насчет такого:

def performAsync(asyncFunc, notifyFunc):
    def threadProc():
        retValue = asyncFunc()
        glib.idle_add(notifyFunc, retValue)
    Thread(target=threadProc).start()

def someGuiCallback(filebutton):
    path = filebutton.getPath()
    performAsync(
        lambda: getMd5Sum(path),
        lambda md5sum: showNotification("Md5Sum of file: %s" % md5sum)
    )

Немного некрасиво с лямбда-выражениями, но это просто и, вероятно, более читабельно, чем использование трюков прекомпилятора.

person interjay    schedule 08.11.2009
comment
Проблема в том случае, когда «...» расширяется, то есть вы хотите сделать более одной строки после получения ответа. Если бы у Python были многострочные встроенные функции, это, вероятно, было бы приемлемо, хотя даже это было бы довольно беспорядочно, если бы у вас было два вызова $ в одном блоке. - person Thomas Ahle; 10.11.2009