从C#到IronPython生成器的优雅迭代

时间:2013-12-10 21:15:52

标签: c# .net iterator generator ironpython

非常精通C#和Python(但对.NET 4.x 中的新 dynamic 功能一无所知),我最近决定将IronPython脚本支持添加到我的一个C#应用程序中。我有所有的基本通信工作,例如将Action<>传入并从Python调用我的C#代码。

但我无法使用C#代码中的python生成器查找文档或示例。我有一个解决方案,但它远非优雅。

我在我的python代码中定义了一个生成器(“test.py”):

def do_yield():
    yield 1
    yield 2
    yield 3

在C#中,我设置了IronPython环境:

// set up IronPython
var ipy = Python.CreateRuntime();
test = ipy.UseFile("test.py");

我已经定义了一个帮助函数来从返回的PythonGenerator转换为C#迭代器,所以我的foreach看起来像:

foreach (int n in PythonGeneratorToCSharpIterator<int>(test.do_yield()))
{
    Log("py gen returned: " + n);
}

我的助手功能:

IEnumerable<T> PythonGeneratorToCSharpIterator<T>(IronPython.Runtime.PythonGenerator pyGenerator)
{
    while (true) {
        T t = default(T);

        try {
            // get the next value
            object o = pyGenerator.next();

            // will throw an exception if it's not the correct type (or no more values in the generator)
            t = (T)o; 
        }
        catch (Exception ex) {
            break; // break out of the while loop and return from the iterator
        }

        yield return t; // this can't be inside try/catch
    }
}

当没有更多值要返回时,python生成器返回LightException,因此行t = (T)o将抛出异常,因为它试图将其强制转换为int

这样做的一个问题是我没有抓住并正确处理python代码中的任何异常。我只是扔掉它们并退出循环。另一个是当我没有生成器的值时,我抛出一个异常,我更喜欢布尔检查来测试返回的值是无效的。

也许这是我对.NET的新dynamic方面的无知,这使我无法理解如何正确编码。是否有更好/更标准的方法在C#/ IronPython中编写代码?

修改

vcsjones'评论,我现在使用Linq的Cast扩展方法,这个代码对我很有用:

var gen = (IronPython.Runtime.PythonGenerator)test.do_yield();
foreach(int n in gen.Cast<int>()) {
    Log("py gen returned (with Cast): " + n);
}

1 个答案:

答案 0 :(得分:1)

这很容易使用dynamic

var ipy = Python.CreateRuntime();
dynamic test = ipy.UseFile("test.py");
foreach (int n in test.do_yield())
{
    Console.WriteLine("py gen returned: " + n);
}

// outputs
py gen returned: 1
py gen returned: 2
py gen returned: 3

你应该注意的一件大事是,扩展方法(如LINQ)不适用于dynamic,因为扩展方法只是静态方法调用,并且应用了一些语法糖。而object in this case实施IEnumerable但未IEnumerable<int>,限制了您如何处理此问题的选项。

假设您要将项目过滤为偶数项目。这是一种方法:

var ipy = Python.CreateRuntime();
dynamic test = ipy.UseFile("test.py");
foreach (int n in test.do_yield())
{
    if (n % 2 == 0)
        Console.WriteLine("py gen returned: " + n);
}

或者你可以强制转换然后使用LINQ扩展方法(这是有效的,因为IEnumerable强制转换允许在编译时评估其余的代码,就像非动态代码一样):

var ipy = Python.CreateRuntime();
dynamic test = ipy.UseFile("test.py");
foreach (int n in ((IEnumerable)test.do_yield()).Cast<int>().Where(n => n % 2 == 0))
{
    Console.WriteLine("py gen returned: " + n);
}

在某些情况下,您可能希望使用不带语法糖的扩展方法,例如

// these lines are generally equivalent, unless someList is dynamic
var filteredList = Enumerable.Where(someList, x => x % 2 == 0);
var filteredList = someList.Where(x => x % 2 == 0);