存储为字符串的脚本的 IronPython 依赖项

IronPython dependencies for scripts stored as strings

我有一个 C# 应用程序将 python 脚本文件 (*.py) 存储为字符串。我使用以下方式加载它们:

scriptEngine.CreateScriptSourceFromString(code);

但现在我有多个脚本文件,它们之间存在依赖关系(导入)。为了处理依赖关系,我可以将所有字符串保存回文件夹中的文件并加载我要执行的脚本:

scriptEngine.CreateScriptSourceFromFile(filePath);

但这会使所有脚本文件可见。有没有办法以内存方式实现这一点,这样脚本文件就不会先保存到磁盘,而是直接从字符串中加载?

TL;DR:示例:

myutils.py:

def SomeMethod(p):
    print ('SomeMethod(p=%s)' % p)

script1.py:

import myutils;

if __name__ == '__main__':
    myutils.SomeMethod('script1')

script2.py:

import myutils;

if __name__ == '__main__':
    myutils.SomeMethod('script2')

我的应用程序将脚本存储为字符串。像

Dictionary<string, string> filePathToContent = new Dictionary<string, string>();

filePathToContent["myutils.py"] = "..."; // The script file content.
filePathToContent["script1.py"] = "..."; // The script file content.
filePathToContent["script2.py"] = "..."; // The script file content.

我想调用 script1.py 而不必先将脚本保存到文件夹中。注意:代码只是我所拥有的一个简化示例。

您可以将不同的脚本分别创建为一个函数,并根据给定的参数调用这些函数

ScriptScope scope = scriptEngine.CreateScope();

scope.SetVariable("language", "en");

scriptEngine.Execute(scope);

和python(我知道的愚蠢的例子):

def doEnStuff():
  print "english"

def doEsStuff():
  print "espagna"

if language == "en"
  doEnStuff()

一般来说,在 IronPython 和 Python 中有几种自定义导入处理方法。大多数概念在 PEP 0302(新导入挂钩)中定义。

可以解决需求的两个python机制是meta_pathpath_hooks。两者都可以在 Python 或(如果是 IronPython)C#/.NET 中实现。鉴于问题涉及从 C# 托管 IronPython,实施导入基础结构可以采用任何一种方式。

使用meta_path

IronPython 附带了 ResourceMetaPathImporter,它允许您拥有一个 ZIP 存档,其中包含您的脚本作为嵌入式资源。假设这样的存档被称为 scripts.zip 包含在当前正在执行的程序集中,所需的设置可能如下所示:

var engine = Python.CreateEngine();
var sysScope = engine.GetSysModule();
List metaPath = sysScope.GetVariable("meta_path");
var importer = new ResourceMetaPathImporter(Assembly.GetExecutingAssembly(), "scripts.zip");
metaPath.Add(importer);
sysScope.SetVariable("meta_path", metaPath);

如果程序集和脚本是已知的并且 ZIP 打包不会干扰开发过程,则此方法很有效。

使用path_hooks

路径挂钩包含一个导入器链,查询 sys.path 中的所有项目以确定它们是否可以处理给定路径。类似于 zipimport.cs 的导入器,但负责 DLLs/EXEs 中的嵌入资源而不是 ZIP 存档。 这可以提供一种更通用的方法,只需将 DLL 添加到路径即可处理其他文件。

使用 PlatformAdaptationLayer

第三种方法的工作原理是提供 PlatformAdaptationLayer which is part of Microsoft.Scripting/IronPython. This answer 显示平台适配层解析预定义程序集和包命名空间的嵌入式资源的完整工作示例。

一般说明:相关issue/discussion on github

我制定了以下解决方案,记得将你的脚本文件存放在你的项目资源中。

using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Scripting.Hosting.Providers;

namespace JJMasterData.Core.DataDictionary
{

    /// <summary>
    /// Class responsible for handling Python 3.4 scripts
    /// Gustavo Barros 05/01/2022
    /// </summary>
    public class PythonScriptManager
    {
        private ScriptScope Scope;

        private ScriptEngine _engine;
        private ScriptEngine Engine
        {
            get
            {
                if (_engine == null)
                {

                    ///Creating the Python Engine
                    _engine = Python.CreateEngine();

                    ///Setup JJMasterData scripts
                    Scope = _engine.CreateScope();

                    ///Adding IronPython StdLib
                    ICollection<string> searchPaths = _engine.GetSearchPaths();
                    string user = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                    searchPaths.Add(user + $"\.nuget\packages\ironpython.stdlib\3.4.0-alpha1\content\Lib");
                    _engine.SetSearchPaths(searchPaths);

                    ///Importing custom JJMasterData scripts
                    ImportScripts();
                }

                return _engine;
            }

        }

        protected void ImportScripts()
        {
            ///Repeat the same steps to import a new script
            string dataAccess = Encoding.UTF8.GetString(Properties.Resources.data_access);
            Engine.Execute(dataAccess, Scope);
            Scope.SetVariable("data_access", HostingHelpers.GetScope(Scope));
        }

        /// <summary>
        /// Executes a Python 3.4 script from a string.
        /// Gustavo Barros - 05/01/2022
        /// </summary>
        /// <param name="script">Python script to be executed.</param>
        /// <returns>Script dynamic return./returns>
        public object Run(string script) => Engine.CreateScriptSourceFromString(script).Execute(Scope);

    }
}