【问题标题】:Graceful iteration over an IronPython generator from C#从 C# 对 IronPython 生成器进行优雅迭代
【发布时间】:2013-12-28 14:46:29
【问题描述】:

相当精通 C# 和 Python(但对新功能一无所知 dynamic .NET 4.x 中的功能),我最近决定添加 IronPython对我的一个 C# 应用程序的脚本支持。我已经完成了所有基本的通信工作,例如将Action<>s 传入并从 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);
}

【问题讨论】:

  • According to the source 一个 python 生成器已经实现了IEnumerable&lt;object&gt;,所以你所要做的就是使用 Linq 的 Cast&lt;T&gt;

标签: c# .net iterator generator ironpython


【解决方案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&lt;int&gt;,这限制了您如何处理这个问题的选择。

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

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

【讨论】:

  • 我确信我已经尝试过直接使用 foreach,但我猜我的静态类型大脑一定尝试过 foreach(int n in (IEnumerable&lt;int&gt;)test.do_yield()),这会引发异常。这正是我一直在寻找的东西(和一个完全的掌心)。使用 Linq 过滤值也非常有用。感谢您的详细回答。
猜你喜欢
  • 1970-01-01
  • 2015-03-22
  • 2011-05-23
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 2021-01-13
  • 1970-01-01
  • 2017-12-18
相关资源
最近更新 更多