65.9K
CodeProject 正在变化。 阅读更多。
Home

jupyter.net 客户端:一个与 Jupyter 内核交互的 C# 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (9投票s)

2019年11月3日

CPOL

11分钟阅读

viewsIcon

21829

downloadIcon

329

jupyter.net 客户端的描述:一个用于与 Jupyter 内核交互的 C# 库

引言

在本文中,我将介绍一个名为 jupyter.net client 的 C# 库,它允许与 Jupyter 内核进行交互。

Jupyter 是一个为任何编程语言提供交互式计算框架的项目。该框架的主要组件如图所示。客户端与用户交互,请求执行代码并显示从内核收到的输出。内核执行代码并维护计算状态(局部变量、用户函数等)。客户端和服务器之间的通信通过 ZeroMQ 套接字进行,并使用下文所述的协议。

可以连接多个客户端到一个内核,但在本文中,我将只考虑一个内核连接到一个客户端的情况。

Jupyter 项目还定义了 Notebook 规范:一种客户端可以用来将源代码、计算结果和其他数据保存到文件中的文件格式。

现有的 Jupyter 客户端实现有很多。以下是最常用的独立应用程序:

  • Jupyter notebook
  • Jupyter console
  • JupyterLab
  • Nteract
  • CoCalc
  • Spyder

此外,还有一个 jupyter_client 库(https://pypi.ac.cn/project/jupyter-client/),用于以编程方式在 Python 中与内核交互。这里介绍的项目旨在成为该库的 C# 替代品。

jupyter.net client 的一些可能用途包括:

  • 为任何脚本语言创建一个定制的 C# 前端。例如,您可以使用 Python 库 numpypanda 编写一个自定义的数据分析工具。
  • 将其用作脚本引擎,在 C# 应用程序中运行任何可用 Jupyter 内核的语言的脚本。

在本文中,我将尝试概述以下主题:

  • 如何使用 jupyter.net client
  • Jupyter 框架概述
  • jupyter.net client 的代码结构

要深入解释这些论点,一篇文章远不够,所以我将只提供关键信息,并提供一些链接供您深入了解。您也可以查看附带的源代码,它相当简单。

jupyter.net client 可在 GitHub(https://github.com/andreaschiavinato/jupyter.net_client)上找到,或者作为一个名为 JupiterNetClient 的 NuGet 包。

本文之后将有另一篇文章,介绍一个完整的 C# Windows 应用程序,该应用程序允许使用 jupyter.net client 与 Jupyter 内核进行交互。

Jupyter 软件安装

有一些对 Jupyter 有用的软件可以安装,包括:

  • Jupyter notebook:一个用于交互式计算的 Web 应用程序。
  • Jupyter console:一个用于交互式计算的简单命令行应用程序。
  • Python kernel:一个用于 Python 语言的内核,在 Jupyter notebook 和 Jupyter console 中默认使用。
  • 一些实用程序,如 jupyter-kernelspec.exe,它在 python.net client 中用于获取可用内核。

要安装它,请先安装 Python,然后运行 python -m pip install jupyter

要测试您是否正确安装了 Jupyter,您可以运行 python -m jupyter notebook。稍后,Jupyter Notebook Web 应用程序应该会启动。

Hello World 应用程序

下面的代码是一个简单的 C# 应用程序,它在一个 Python 内核上执行代码 print("Hello from Jupyter") 。要编译它,您需要导入 JupiterNetClient NuGet 包;要运行它,您需要安装前一节所述的软件。

using JupiterNetClient;
using JupiterNetClient.Nbformat;
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        //Initializing the Jupyter client
        //The constructor of JupyterBlockingClient will throw an exception
        //if the jupyter framework is not found
        //It is searched on the folders defined on the PATH system variable
        //You can also pass the folder where python.exe is located as
        //an argument of the constructor
        //(since the jupyter framework is located on the python folder)
        var client = new JupyterBlockingClient();

        //Getting available kernels
        var kernels = client.GetKernels();
        if (kernels.Count == 0)
            throw new Exception("No kernels found");

        //Connecting to the first kernel found
        Console.WriteLine($"Connecting to kernel {kernels.First().Value.spec.display_name}");
        client.StartKernel(kernels.First().Key);
        Console.WriteLine("Connected\n");

        //A callback that is executed when there is any information 
        //that needs to be shown to the user
        client.OnOutputMessage += Client_OnOutputMessage;

        //Executing some code
        client.Execute("print(\"Hello from Jupyter\")");

        //Closing the kernel
        client.Shutdown();
           
        Console.WriteLine("Press enter to exit");
        Console.ReadLine();            
    }

    private static void Client_OnOutputMessage(object sender, JupyterMessage message)
    {
        switch (message.content)
        {
            case JupyterMessage.ExecuteResultContent executeResultContent:
                Console.WriteLine($"[{executeResultContent.execution_count}] - 
                                 {executeResultContent.data[MimeTypes.TextPlain]}");
                break;

            case JupyterMessage.StreamContent streamContent:
                Console.WriteLine(streamContent.text);
                break;

            default:
                break;
        }
    } 
}

如果程序成功运行,您将看到以下输出:

Connecting to kernel Python 3
Connected

Hello from Jupyter

Press enter to exit

主类说明

以下是 jupyter.net client 库的主要类:

JupyterClientBase 是主类,它实现了连接到内核、获取可用内核、执行代码等方法。

它有两个版本可供您使用:一个阻塞版本(JupyterBlockingClient)和一个非阻塞版本(JupyterClient)。简而言之,在阻塞版本中,执行代码的函数会等待直到代码执行完成;相反,非阻塞版本会立即返回并在完成后引发事件。这种架构与 Jupyter 客户端的官方实现(https://pypi.ac.cn/project/jupyter-client/)相似。我发现查看其代码有助于更好地理解 Jupyter 客户端应如何工作。

KernelManager 类用于发现和连接到 Jupyter 内核,它在库内部使用。

Notebook 类可用于读取或保存 Jupyter notebook 文件。

JupyterMessage 类用于处理内核和客户端之间交换的消息。

查找内核并连接到它

在接下来的部分中,我将提供有关 Jupyter 框架的一些技术细节。让我们先看看 Jupyter 客户端如何连接到内核。

每个内核都由一个这样的 JSON 文件定义:

{
 "argv": [
  "python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Python 3",
 "language": "python"
}

这种结构称为 Kernelspec,并且在此处定义:here
在我的计算机上,这些文件位于文件夹 C:\Users\Andrea\AppData\Local\Programs\Python\Python37\share\jupyter\kernels\ 中,但检索此信息的更好方法是运行 jupyter-kernelspec.exe list(它位于 Python 目录的 Scripts 子文件夹中,并在 KernelManager.GetKernels() 函数中使用)。

kernelspecargs 成员定义了启动内核的命令行。一旦内核启动,它应该会创建一个名为 connection file 的另一个文件,该文件标识内核实例并包含连接到它所需的所有信息(请参阅 https://jupyter-client.readthedocs.io/en/latest/kernels.html#connection-filesKernelManager.StartKernel() 函数的代码)。
下面是一个 connection file 的示例:

{
  "shell_port": 64656,
  "iopub_port": 64665,
  "stdin_port": 64659,
  "control_port": 64662,
  "hb_port": 64675,
  "ip": "127.0.0.1",
  "key": "5f5313c7-fb04d880c4e5756a645e1a97",
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "kernel_name": ""
}

connection file 包含五个用于与内核通信的 ZMQ 套接字端口号,以及一个用于创建消息签名(添加到所有消息中)的密钥。

ZeroMQ 套接字

ZeroMQ 套接字是用于与内核通信的工具。在 https://zguide.zeromq.cn 上可以找到对 ZMQ 套接字的良好描述。

ZeroMQ(也称为 ØMQ、0MQ 或 zmq)看起来像一个可嵌入的网络库,但它作为一个并发框架。它提供了可以在各种传输(如进程内、进程间、TCP 和多播)中承载原子消息的套接字。您可以使用扇出、发布/订阅、任务分发和请求-响应等模式将套接字 N 对 N 连接起来。它的速度足够快,可以作为集群产品的底层。它的异步 I/O 模型使您能够构建可扩展的多核应用程序,这些应用程序构建为异步消息处理任务。它拥有多种语言 API,并在大多数操作系统上运行。ZeroMQ 来自 iMatix,并且是 LGPLv3 开源的。

正如您所读到的,ZeroMQ 提供了不同的通信模式。以下是 Jupyter 中使用的模式:

  • 请求/响应:响应套接字等待来自任何请求套接字请求。然后响应套接字可以向请求套接字提供答复。
  • 发布/订阅:发布套接字用于发布消息,其他订阅套接字可以订阅它以接收这些消息。
  • Router/Dealer:这是请求/响应模式的一个更复杂的版本,其中 ROUTER 可以被视为 REQUEST 套接字的一个异步版本,而 DEALER 可以被视为 RESPONSE 的一个异步版本。异步的意思是,DEALER 可以同时处理来自不同节点的多个请求。然而,对于本文的目的,将其视为等同于请求/响应就足够了。

下图显示了内核和客户端可以用于通信的 ZeroMQ 套接字。在 https://jupyter-client.readthedocs.io/en/stable/messaging.html 上可以找到更详细的描述。

ZeroMQ 套接字有两个主要的 C# 实现:

在这个项目中,我使用了 ZeroMQ,但对于我们的目的,它们看起来是等效的。

Jupyter 协议

通过这些套接字交换的消息使用 JSON 格式。消息有以下字段:

  • Header:包含消息的唯一标识符、包含用户名的字符串、会话标识符、时间戳、消息类型和协议版本。
  • Parent_header:它是父单元格的 header 的副本(如果存在)。例如,代码执行响应消息的父消息是代码执行请求消息。
  • Metadata:与消息关联的附加元数据。规范中并未明确说明如何使用此信息,在 jupyter.net client 中,元数据未使用。
  • Content:消息的内容,取决于消息类型。
  • Buffers:对于支持协议二进制扩展的实现,是二进制数据缓冲区的列表。在 jupyter.net 中,此信息未使用。

此时可用的消息类型有:

  • execute_request:客户端用于请求内核执行特定操作。
  • execute_reply:内核用于通知客户端请求的操作已完成。
  • status:内核用于向客户端传达其状态。
  • display_data:内核用于通知客户端有需要显示给用户的数据。
  • execute_result:内核用于向客户端传达计算结果。
  • input_request:内核用于通知客户端需要用户输入。
  • execute_input:内核用于向所有连接的客户端广播正在执行的代码。
  • error:内核用于通信计算过程中发生的错误。

有关更多信息,请参阅 https://jupyter-client.readthedocs.io/en/stable/messaging.html#messages-on-the-shell-router-dealer-channel

jupyter.net client 中,创建了 JupyterMessage 类来处理这些消息。如图所示,有一个 abstract 类用于处理内容,该类有不同的子类,具体取决于消息类型。

客户端和服务器之间的交互遵循此模式:

  • 客户端通过 shell 套接字向内核发送一个 execute_request 消息。
  • 内核在 iopub 套接字上发布一个 status 消息,表明它正在忙。
  • 内核在 iopub 套接字上发布任何需要的 display_data/execute_result/error 消息,或者在 stdin 套接字上发布任何 execure_input 消息。
  • 内核在 shell 套接字上发送一个 execute_reply 消息,表明计算已完成。
  • 内核在 iopub 套接字上发布一个 status 消息,表明它已准备好处理下一个请求。

可用命令

Jupyter 协议提供了客户端可以在 execute_request 消息中使用的以下命令:

  • Execute:执行一些代码。
  • Introspection:提供代码信息(例如,变量的类型,但这取决于内核提供什么信息)。
  • Completion:提供一个 string 来完成当前代码。
  • History:提供最近执行语句的列表。
  • Code completeness:指示当前代码是否可以按原样执行,或者客户端是否应要求用户输入更多行。
  • Kernel info:提供内核信息。
  • Kernel shutdown:关闭内核。
  • Kernel interrupt:中断当前计算。

对于这些命令中的每一个,JupyterClient / JupyterBlokingClient 类都提供了一个相应的方法。

执行代码

下面的序列图说明了客户端和内核之间执行代码所交换的消息。

客户端通过 shell 套接字发送一个类型为 execute_requestZeroMQ 消息,其中包含要执行的代码。

内核在 IOPub 套接字上发送一个类型为 status 的消息,指示它正在忙,然后通过发送一个类型为 execute_input 的消息(包含收到的代码的副本和一个标识该语句的渐进编号)来确认消息已收到。

然后,它通过在 IOPub 套接字上发送一个类型为 execute_result 的消息来发送执行结果。有些代码可能不会产生 execute_result,而是内核可能会发送一个 streamdisplay_data 消息。
然后,它在 shell 套接字上发送一个类型为 execute_reply 的消息,表明执行已完成。
最后,它在 IOPub 套接字上发送一个 status 消息,表明它已准备好处理下一个请求。

命令行客户端

下面,我将提供一个可用于与 Jupyter 内核交互的命令行客户端代码。它是 Jupyter 控制台应用程序(https://github.com/jupyter/jupyter_console)的 C# 版本。

using JupiterNetClient;
using JupiterNetClient.Nbformat;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class Program
{
    private const string Prompt = ">>> ";
    private const string PromptWhite = "... ";
    private static JupyterBlockingClient client;
    private static Dictionary<string, KernelSpec> kernels;

    static void Main(string[] args)
    {
        client = new JupyterBlockingClient();

        kernels = client.GetKernels();
        if (kernels.Count == 0)
            throw new Exception("No kernels found");

        //Connecting to the first kernel found
        Console.WriteLine($"Connecting to kernel {kernels.First().Value.spec.display_name}");
        client.StartKernel(kernels.First().Key);

        DisplayKernelInfo(client.KernelInfo);

        client.OnOutputMessage += Client_OnOutputMessage;
        client.OnInputRequest += Client_OnInputRequest;

        //Mainlook asks code to execute and executes it.
        Console.WriteLine("\n\nEnter code to execute or Q <enter> to terminate:");        
        MainLoop(client);

        //terminating the kernel process
        Console.WriteLine("SHUTTING DOWN KERNEL");
        client.Shutdown();
    }

下面是 MainLoop 过程:

    private static void MainLoop(JupyterBlockingClient client)
    {
        //Using the component ReadLine, which has some nice features 
        //like code completion and history support
        ReadLine.HistoryEnabled = true;
        ReadLine.AutoCompletionHandler = new AutoCompletionHandler(client);
        var enteredCode = new StringBuilder();
        var startNewCode = true;
        var lineIdent = string.Empty;
        while (true)
        {
            enteredCode.Append(ReadLine.Read
                        (startNewCode ? Prompt : PromptWhite + lineIdent));
            var code = enteredCode.ToString();
            if (code == "Q")
            {
                //When the user types Q we terminates the application
                return;
            }
            else if (string.IsNullOrWhiteSpace(code))
            {
                //No code entered, do nothing
            }
            else
            {
                //Asking the kernel if the code entered by the user 
                //so far is a complete statement.
                //If not, for example because it is the first line of a function definition,
                //we ask the user to enter one more line
                var isComplete = client.IsComplete(code);
                switch (isComplete.status)
                {
                    case JupyterMessage.IsCompleteStatusEnum.complete:
                        //the code is complete, execute it
                        //the results are given on the OnOutputMessage callback
                        client.Execute(code);
                        startNewCode = true;
                        break;

                    case JupyterMessage.IsCompleteStatusEnum.incomplete:
                        lineIdent = isComplete.indent;
                        enteredCode.Append("\n" + lineIdent);
                        startNewCode = false;
                        break;

                    case JupyterMessage.IsCompleteStatusEnum.invalid:
                    case JupyterMessage.IsCompleteStatusEnum.unknown:
                        Console.WriteLine("Invalid code: " + code);
                        startNewCode = true;
                        break;
                }
            }

            if (startNewCode)
            {
                enteredCode.Clear();
            }
        }
    }

AutoCompletionHandler 类由 Readline 组件使用,以支持自动完成。

    private class AutoCompletionHandler : IAutoCompleteHandler
    {
        private readonly JupyterBlockingClient _client;

        public AutoCompletionHandler(JupyterBlockingClient client)
        {
            _client = client;
        }

        public char[] Separators { get; set; } = new char[] { };

        public string[] GetSuggestions(string text, int index)
        {
            //asking the kernel to provide a list of strings to complete the current line
            var result = _client.Complete(text, text.Length);
            return result.matches
                .Select(s => text.Substring(0, result.cursor_start) + s)
                .ToArray();
        }
    }

这是用于显示输出消息的回调。

    private static void Client_OnOutputMessage(object sender, JupyterMessage message)
    {
        switch (message.content)
        {
            case JupyterMessage.ExecuteInputContent executeInputContent:
                Console.WriteLine($"Executing  
                  [{executeInputContent.execution_count}] - {executeInputContent.code}");
                break;

            case JupyterMessage.ExecuteResultContent executeResultContent:
                Console.WriteLine($"Result  
                    [{executeResultContent.execution_count}] - 
                     {executeResultContent.data[MimeTypes.TextPlain]}");
                break;

            case JupyterMessage.DisplayDataContent displayDataContent:
                Console.WriteLine($"Data  {displayDataContent.data}");
                break;

            case JupyterMessage.StreamContent streamContent:               
                Console.WriteLine($"Stream  {streamContent.name} {streamContent.text}");
                break;

            case JupyterMessage.ErrorContent errorContent:
                Console.WriteLine($"Error  {errorContent.ename} {errorContent.evalue}");
                Console.WriteLine(errorContent.traceback);
                break;

            case JupyterMessage.ExecuteReplyContent executeReplyContent:
                Console.WriteLine($"Executed  
                     [{executeReplyContent.execution_count}] - {executeReplyContent.status}");
                break;

            default:
                break;
        }
    }

这是用于请求用户输入的_回调_。

        private static void Client_OnInputRequest
                  (object sender, (string prompt, bool password) e)
        {
            var input = e.password
                ? ReadLine.ReadPassword(e.prompt)
                : ReadLine.Read(e.prompt);
            client.SendInputReply(input);
        }

最后,这是用于显示内核信息的_方法_:

    private static void DisplayKernelInfo(JupyterMessage.KernelInfoReplyContent kernelInfo)
    {
        Console.WriteLine("");
        Console.WriteLine(" KERNEL INFO");
        Console.WriteLine("============");
        Console.WriteLine($"Banner: {kernelInfo.banner}");
        Console.WriteLine($"Status: {kernelInfo.status}");
        Console.WriteLine($"Protocol version: {kernelInfo.protocol_version}");
        Console.WriteLine($"Implementation: {kernelInfo.implementation}");
        Console.WriteLine($"Implementation version: {kernelInfo.implementation_version}");
        Console.WriteLine($"Language name: {kernelInfo.language_info.name}");
        Console.WriteLine($"Language version: {kernelInfo.language_info.version}");
        Console.WriteLine($"Language mimetype: {kernelInfo.language_info.mimetype}");
        Console.WriteLine($"Language file_extension: {kernelInfo.language_info.file_extension}");
        Console.WriteLine($"Language pygments_lexer: {kernelInfo.language_info.pygments_lexer}");
        Console.WriteLine($"Language nbconvert_exporter: 
                       {kernelInfo.language_info.nbconvert_exporter}");
    }

Notebook 格式

Jupyter notebook 是一个 Json 文件,如下所示:

{
    "metadata": {
        "kernel_info": {
            "name": "Python 3"
        },
        "language_info": {
            "name": "python",
            "version": "3.7.2"
        }
    },
    "nbformat": 4,
    "nbformat_minor": 2,
    "cells": [{
        "execution_count": 1,
        "outputs": [{
            "execution_count": 1,
            "data": {
                "text/plain": ["4"]
            },
            "output_type": "execute_result"
        }],
        "cell_type": "code",
        "source": ["2+2"],
    }]
}

它有一个 metadata 对象,包含有关内核和所用语言的信息。客户端负责确保打开的任何 notebook 文件都与正在使用的内核兼容。

然后有一个单元格元素列表,这些元素可以是以下类型之一:

  • Code:包含要运行的代码,并且可能包含执行的输出。
  • Markdown:包含使用 markdown 语法格式化的文本。
  • Raw:包含用于 nconvert 实用程序的数据的特殊单元格。

以下示例读取一个 notebook 并将其内容打印到屏幕上:

using JupiterNetClient.Nbformat;
using System;

class Program
{
    static void Main(string[] args)
    {
        var nb = Notebook.ReadFromFile(@"test.ipynb"); //TODO: change the name of the file
        Console.WriteLine($"Langauge: {nb.metadata.language_info.name} 
                                      {nb.metadata.language_info.version}");
        Console.WriteLine($"Kernel: {nb.metadata.kernel_info.name}");
        Console.WriteLine($"Notebook format: {nb.nbformat}.{nb.nbformat_minor}");
        Console.WriteLine("\nContent:\n");
        foreach (var cell in nb.cells)
        {
            switch (cell)
            {
                case MarkdownCell markdownCell:                        
                    Console.WriteLine(markdownCell.source);
                    break;

                case CodeCell codeCell:
                    Console.WriteLine(codeCell.source);
                    foreach (var output in codeCell.outputs)
                    {
                        Console.Write("  " + output.output_type + ": ");
                        switch (output)
                        {
                            case StreamOutputCellOutput streamOutputCellOutput:
                                Console.WriteLine($"{streamOutputCellOutput.name} 
                                                    {streamOutputCellOutput.text}");
                                break;
                            case DisplayDataCellOutput displayDataCellOutput:
                                Console.WriteLine
                                   (displayDataCellOutput.data[MimeTypes.TextPlain]);
                                break;
                            case ExecuteResultCellOutput executeResultCellOutput:
                                Console.WriteLine
                                   (executeResultCellOutput.data[MimeTypes.TextPlain]);
                                break;
                            case ErrorCellOutput errorCellOutput:
                                Console.WriteLine($"{errorCellOutput.ename} 
                                                    {errorCellOutput.evalue}");
                                break;
                        }                            
                    }
                    break;

                case RawCell _:
                    Console.WriteLine($"(raw cell)");
                    break;
            }
        }

        Console.ReadLine();
    }
}

以下程序显示如何创建一个简单的 notebook:

using JupiterNetClient;
using JupiterNetClient.Nbformat;
using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var client = new JupyterBlockingClient();

        //Getting available kernels
        var kernels = client.GetKernels();
        if (kernels.Count == 0)
            throw new Exception("No kernels found");

        //Connecting to the first kernel found
        Console.WriteLine($"Connecting to kernel {kernels.First().Value.spec.display_name}");
        client.StartKernel(kernels.First().Key);
        Console.WriteLine("Connected");

        //Creating a notebook and adding a code cell
        var nb = new Notebook(client.KernelSpec, client.KernelInfo.language_info);
        var cell = nb.AddCode("print(\"Hello from Jupyter\")");

        //Setting up the callback so that the outputs are written on the notebook
        client.OnOutputMessage += (sender, message) => 
                     { if (ShouldWrite(message)) cell.AddOutputFromMessage(message); };
            
        //executing the code
        client.Execute(cell.source);

        //saving the notebook
        nb.Save("test.ipynb");
        Console.WriteLine("File test.ipynb written");

        //Closing the kernel
        client.Shutdown();

        Console.WriteLine("Press enter to exit");
        Console.ReadLine();
    }

    private static bool ShouldWrite(JupyterMessage message) =>
        message.header.msg_type == JupyterMessage.Header.MsgType.execute_result
        || message.header.msg_type == JupyterMessage.Header.MsgType.display_data
        || message.header.msg_type == JupyterMessage.Header.MsgType.stream
        || message.header.msg_type == JupyterMessage.Header.MsgType.error;
}

从 C# 应用程序执行 Python 脚本并获取结果

您可以使用 python.net client 从您的 C# 应用程序中运行 Python 脚本。与 IronPython 或 Python for .NET (pythonnet) 等库不同,使用 jupyter.net client(以及 Python 内核),Python 代码在单独的进程中执行,因此更安全,因为它不会导致应用程序崩溃,并且最终可以在远程计算机上执行。您还可以轻松更新 Python 内核,而无需重新编译 C# 应用程序。

下面是一个示例,展示了如何使用 python.net client 运行在 .py 文件中编写的函数并获取结果。

using JupiterNetClient;
using JupiterNetClient.Nbformat;
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        var client = new JupyterBlockingClient();

        //Getting available kernels
        var kernels = client.GetKernels();
        if (kernels.Count == 0)
            throw new Exception("No kernels found");

        //Connecting to the first kernel found
        Console.WriteLine($"Connecting to kernel {kernels.First().Value.spec.display_name}");
        client.StartKernel(kernels.First().Key);
        Console.WriteLine("Connected\n");

        //Loading a script containing the following function:
        //def do_something(a):
        //  return a ** 2
        client.Execute("%run script.py");

        //Creating an event handler that stores the result 
        //of the computation in a TaskCompletionSource object 
        var promise = new TaskCompletionSource<string>();
        EventHandler<JupyterMessage> hanlder = (sender, message) =>
        {
            if (message.header.msg_type == JupyterMessage.Header.MsgType.execute_result)
            {
                var content = (JupyterMessage.ExecuteResultContent)message.content;
                promise.SetResult(content.data[MimeTypes.TextPlain]);
            }
            else if (message.header.msg_type == JupyterMessage.Header.MsgType.error)
            {
                var content = (JupyterMessage.ErrorContent)message.content;
                promise.SetException(new Exception
                     ($"Jupyter kenel error: {content.ename} {content.evalue}"));
            }
        };
        client.OnOutputMessage += hanlder;
        //calling the function do_something
        client.Execute("do_something(2)");
        //removing event handler, since the TaskCompletionSource cannot be reused
        client.OnOutputMessage -= hanlder;

        //getting the result
        try
        {
            Console.WriteLine("Result:");
            if (promise.Task.IsCompleted)
                Console.WriteLine(promise.Task.Result);
            else
                Console.WriteLine("No result received");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }

        finally
        {
            //Closing the kernel
            client.Shutdown();
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }
    }
}

预期的输出是:

Connecting to kernel Python 3
Connected

Result:
4
Press enter to exit

历史

  • 2019年11月3日:初始版本
© . All rights reserved.