简单的Python异步预编译器?

2 投票
3 回答
895 浏览
提问于 2025-04-15 15:46

想象一下,你有一个输入输出很繁重的函数,像这样:

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那部分)

我想我可以写一个“编译器”来在执行前修改代码,但这似乎不是最好的解决办法。

你有什么想法,如何做到上面提到的那样吗?

3 个回答

1

这样做怎么样:

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

虽然用到的lambda表达式看起来有点复杂,但其实很简单,而且可能比用一些预编译的技巧更容易理解。

2

你可以使用导入钩子来实现这个目标...

... 不过我个人觉得这样做有点麻烦。

如果你想尝试这种方法,基本上你需要做的是:

  • 为一个扩展名(比如“.thpy”)添加一个导入钩子
  • 这个导入钩子负责在导入时传递一些有效的代码。
  • 这些有效的代码会接收一些参数,这些参数与您正在导入的文件有关。
  • 这意味着你的预编译器可以在导入过程中对源代码进行你想要的任何转换。

不过,这样做也有一些缺点:

  • 虽然这样使用导入钩子是可行的,但会让任何维护你代码的人感到非常惊讶。(我认为这是个坏主意)
  • 这种方法依赖于imputil,而这个在Python 3.0中被移除了,这意味着你用这种方式写的代码寿命有限。

我个人不建议这样做,但如果你决定尝试,有一本Python Magazine的期刊详细介绍了这种做法,我建议你找一本旧的期刊来看看。(作者是Paul McGuire,2009年4月的期刊,可能有PDF版可供下载)。

具体来说,它使用了imputil和pyparsing作为例子,但原理是一样的。

0

当然,你可以从装饰器中访问函数的代码(已经编译过了),然后进行反汇编和修改。你甚至可以访问定义该函数的模块的源代码,并重新编译它。不过,我觉得这样做并没有必要。下面是一个使用装饰器的生成器的例子,其中 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

撰写回答