以收益率作为例外的双向互动

时间:2014-01-02 23:32:02

标签: python dll python-3.3

我编写的C ++ DLL是Meta Trader 4应用程序(MT4)和Python脚本(将Python嵌入到MT4)之间的门户。 MT4向此DLL发送请求并等待指令(例如,字符串命令数组)。 DLL将MT4请求解析为Python脚本。 Python脚本需要从MT4获取一些信息来解析每个请求。因此MT4和Python有双向通信。

但MT4不支持双向通信,它只能解析自己的DLL请求的结果,并使用新参数对DLL进行新的调用。因此,我需要打破Python控制流,以便从Python中临时返回部分结果到DLL(和MT4)并等待来自MT4的新请求。

如何在Python风格中创建这个(丑陋的)presudo-two-way inteaction?我需要一些continuation函数,但只在函数范围内生成函数,当我需要像异常一样的函数:从调用堆栈的底部到顶部,以及通过调用main.next将控制流返回到前一个屈服点的能力( )在顶级Python脚本中。

MT4伪代码:

new_args = ...
while (true) {
  cmds = DLL_GetCommands(new_args);
  if (! cmd) { // No commands from Python
    break;
  }
  new_args = _parseCommand(cmds);
}

DLL伪代码:

char* __declspec(dllexport) DLL_GetCommands(char* args) {
    // Python already initialized.
    // Some python script already executed
    // and I have local scope of this execution.
    PyObject var = ...search some object variable in Python...
    // var is instance of Advert class
    return PyObject_CallMethodObjArgs(var, "parse_tick", args, NULL);
}

Python代码:

class Handler():
    def some_cpp_request(self, a, b):
        yield 'some_cpp_request'
        # After second call to Advert.parse_tick() control flow should return here.

class Advert():
    def __init__(self):
        self.h = Handler()

    # This method and all what it call should work as single generator
    def parse_tick(self):
        for i in range(2):
            self._some_method(i, i)

    def _some_method(self, a, b):
        self.h.some_cpp_request(a, b)

[更新] 经过abarnert的好建议,我有工作解决方案:

class Handler():
    def __init__(self):
        self.cmds = []
        self.cmds_results = []

    def some_cpp_request(self, a, b):
        self.cmds.append(("SOME_CPP_REQUEST", a, b))
        yield
        # Here self.cmds_results contains MT4 response.

class Advert():
    def __init__(self):
        self.h = Handler()

    def parse_tick(self):
        for i in range(2):
            yield from self._some_method(i, i)

        return 'xxx'

    def _some_method(self, a, b):
        yield from self.h.some_cpp_request(a, b)

parser = Advert()
gen = parser.parse_tick()

# This loop should be written in DLL layer.
while True:
    next(gen)

    parser.h.cmds_results.clear()
    for cmd in parser.h.cmds:
        # Adding some results
        parser.h.cmds_results.append((cmd, 'SOME RESULT'))
    parser.h.cmds.clear()

P.S。更舒适的解决方案是创建并行线程(或进程)而不是调用和捕获产量:这不需要将所有返回替换为产量。两个线程可以通过两个阻塞队列进行通信.Queue's:

  • 子线程:
    1. 将请求发送到请求队列
    2. 阻止等待响应队列中的新元素
  • 主线程:
    1. 无限阻塞等待请求队列中的新元素
    2. 将响应放入请求队列

如果request为None,则为完成运行时打破无限循环:子线程和主线程。

1 个答案:

答案 0 :(得分:1)

如果您想在Python代码中使用yield,请执行以下操作:

def parse_tick(self):
    for i in range(2):
        yield self._some_method(i, i)

调用Python生成器函数 - 无论是从Python还是从C-执行此操作都会为您提供迭代器。每次从迭代器获得next值时,它会在最后yield点之后重新激活生成器函数。

working with iterators from C很简单。这里有一些伪代码(比如你现有的伪代码,跳过错误处理,引用计数等):

PyObject *iterator = PyObject_CallMethodObjArgs(var, "parse_tick", args, NULL);
PyObject *item = NULL;
while (item = PyIter_Next(iterator)) {
    do_stuff_with(item);
}

例外情况怎么样?不是问题。如果从使用Python迭代的生成器函数引发异常,next会将该异常引发给调用者。如果你在C中迭代它,PyIter_Next返回它为完成的迭代器所做的NULL,那么你如何区分呢?选中PyErr_Occurred()


或者,您可以将回调函数传递给Python代码,并且您的Python代码使用每个值调用该回调,而不是yield每个值。这就是嵌入Python的应用程序传统上所做的事情。但是,如果你已经考虑过发电机,你就不需要回到过去了。


首先,听起来您希望重新调整异常,以便raise异常自动充当可以从中恢复的yield。这不起作用。一个不是生成器函数的函数无法恢复,周期。甚至 生成器函数的函数也只能在yield后恢复,而不能在raise(或return之后恢复。没有办法解决这个问题;它会从根本上改变raise(和return)所做的语义。

因此,您将不得不将您想要驱动的低级功能转换为实际的生成器。 (或者,或者将它们分解成更小的函数并按顺序调用它们,或者将它们转换为在__call__上保持显式状态和恢复的对象,或者其他比使用惯用方式更有效的工作。)


接下来,听起来好像你想要隐式嵌套的生成器。 Python生成器不能以这种方式工作。如果调用生成器函数,则返回迭代器。即使你自己也是一个发电机功能,它也不能代表你产生价值。如果你想要嵌套,你必须明确它,如下所示:

def _some_method(self, a, b):
    yield a
    yield b

def parse_tick(self):
    for i in range(2):
        yield from self._some_method(i, i)

请注意,调用者(无论是C还是Python)不必知道parse_tick实际上委托给其他生成器来完成其工作,或_some_method在某处暂停。它只询问parse_tick下一个值,它看到的只是它返回0,然后再次0,然后再1,然后{{1}然后就完成了。

并且1不必记住它有一个暂停的parse_tick,因为它暂停在_some_method的中间;下次恢复时,它会自动恢复yield from呼叫 - 或者,如果呼叫耗尽,则继续下一行代码。

PEP 380试图解释这个设计背后的基本原理,以及如何使用它,如果我没有说清楚的话。 Greg Ewing在使用_some_method时也a great tutorial了。在SO回答中,我无法像他的例子那样解释,即使我的解释是他的一半。


或者,您可以在生成器上使用yield from方法。如果仔细观察,sendyield是表达式,包含值,而不是语句。如果您通过调用yield from来驱动生成器,则表达式的值仅为next,这不是很有用。但是通过在生成器上调用None方法,您不仅可以要求它恢复,还可以给它一个值来恢复。 (如果需要,你也可以调用send将异常引入生成器。)这可以让你根据自上而下的协同程序编写你的控制流 - 我认为这不是你想要的,但请阅读PEP 342并查看。


您还可以构建throw来构建自下而上的协同程序,我认为这可能是您想要的。如果你回到Greg Ewing的页面,最后一个例子展示了如何做到这一点。如果你将最后两个特征与一个简单的可组合“未来”抽象结合在一起......那么,看看3.4的新asyncio模块,了解你可以做什么。