【问题标题】:Custom IronPython import resolution自定义 IronPython 导入解析
【发布时间】:2010-11-05 12:25:24
【问题描述】:

我正在从数据库加载 IronPython 脚本并执行它。这适用于简单的脚本,但导入是一个问题。如何拦截这些导入调用,然后从数据库中加载相应的脚本?

编辑:我的主应用程序是用 C# 编写的,我想在不编辑 Python 脚本的情况下拦截 C# 端的调用。

编辑:从我所做的研究来看,创建自己的 PlatformAdaptationLayer 似乎是您应该实现这一点的方式,但在这种情况下它不起作用。我已经创建了自己的 PAL,并且在我的测试中,我的 FileExsists 方法在脚本中的每次导入时都会被调用。但由于某种原因,它从不调用 OpenInputFileStream 方法的任何重载。挖掘 IronPython 源代码,一旦 FileExists 返回 true,它就会尝试在路径上定位文件本身。所以这看起来像一个死胡同。

【问题讨论】:

    标签: import ironpython


    【解决方案1】:

    经过大量的反复试验,我得出了一个解决方案。我从来没有设法让PlatformAdaptationLayer 方法正常工作。在尝试加载模块时,它从未回调 PAL。

    所以我决定用如下所示的 SetVariable 方法替换内置的导入函数(Engine 和 Scope 是受保护的成员,暴露了父脚本的 ScriptEngineScriptScope):

    delegate object ImportDelegate(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple tuple);
    
    protected void OverrideImport()
    {
        ScriptScope scope = IronPython.Hosting.Python.GetBuiltinModule(Engine);
        scope.SetVariable("__import__", new ImportDelegate(DoDatabaseImport));
    }
    
    protected object DoDatabaseImport(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple tuple)
    {
        if (ScriptExistsInDb(moduleName))
        {
            string rawScript = GetScriptFromDb(moduleName);
            ScriptSource source = Engine.CreateScriptSourceFromString(rawScript);
            ScriptScope scope = Engine.CreateScope();
            Engine.Execute(rawScript, scope);
            Microsoft.Scripting.Runtime.Scope ret = Microsoft.Scripting.Hosting.Providers.HostingHelpers.GetScope(scope);
            Scope.SetVariable(moduleName, ret);
            return ret;
         }
         else
         {   // fall back on the built-in method
             return IronPython.Modules.Builtin.__import__(context, moduleName);
         }
    }
    

    希望这对某人有所帮助!

    【讨论】:

    • 谢谢!我的用例和你的完全一样。
    【解决方案2】:

    我只是想做同样的事情,只是我想将我的脚本存储为嵌入式资源。我正在创建一个混合了 C# 和 IronPython 的库,并希望将其作为单个 dll 分发。我编写了一个可以工作的 PlatformAdaptationLayer,它首先在资源中查找正在加载的脚本,然后回退到在文件系统中查找的基本实现。这三个部分:

    第 1 部分,自定义 PlatformAdaptationLayer

    namespace ZenCoding.Hosting
    {
        internal class ResourceAwarePlatformAdaptationLayer : PlatformAdaptationLayer
        {
            private readonly Dictionary<string, string> _resourceFiles = new Dictionary<string, string>();
            private static readonly char Seperator = Path.DirectorySeparatorChar;
            private const string ResourceScriptsPrefix = "ZenCoding.python.";
    
            public ResourceAwarePlatformAdaptationLayer()
            {
                CreateResourceFileSystemEntries();
            }
    
            #region Private methods
    
            private void CreateResourceFileSystemEntries()
            {
                foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames())
                {
                    if (!name.EndsWith(".py"))
                    {
                        continue;
                    }
                    string filename = name.Substring(ResourceScriptsPrefix.Length);
                    filename = filename.Substring(0, filename.Length - 3); //Remove .py
                    filename = filename.Replace('.', Seperator);
                    _resourceFiles.Add(filename + ".py", name);
                }
            }
    
            private Stream OpenResourceInputStream(string path)
            {
                string resourceName;
                if (_resourceFiles.TryGetValue(RemoveCurrentDir(path), out resourceName))
                {
                    return Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
                }
                return null;
            }
    
            private bool ResourceDirectoryExists(string path)
            {
                return _resourceFiles.Keys.Any(f => f.StartsWith(RemoveCurrentDir(path) + Seperator));
            }
    
            private bool ResourceFileExists(string path)
            {
                return _resourceFiles.ContainsKey(RemoveCurrentDir(path));
            }
    
    
            private static string RemoveCurrentDir(string path)
            {
                return path.Replace(Directory.GetCurrentDirectory() + Seperator, "").Replace("." + Seperator, "");
            }
    
            #endregion
    
            #region Overrides from PlatformAdaptationLayer
    
            public override bool FileExists(string path)
            {
                return ResourceFileExists(path) || base.FileExists(path);
            }
    
            public override string[] GetFileSystemEntries(string path, string searchPattern, bool includeFiles, bool includeDirectories)
            {
                string fullPath = Path.Combine(path, searchPattern);
                if (ResourceFileExists(fullPath) || ResourceDirectoryExists(fullPath))
                {
                    return new[] { fullPath };
                }
                if (!ResourceDirectoryExists(path))
                {
                    return base.GetFileSystemEntries(path, searchPattern, includeFiles, includeDirectories);
                }
                return new string[0];
            }
    
            public override bool DirectoryExists(string path)
            {
                return ResourceDirectoryExists(path) || base.DirectoryExists(path);
            }
    
            public override Stream OpenInputFileStream(string path)
            {
                return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path);
            }
    
            public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share)
            {
                return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path, mode, access, share);
            }
    
            public override Stream OpenInputFileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
            {
                return OpenResourceInputStream(path) ?? base.OpenInputFileStream(path, mode, access, share, bufferSize);
            }
    
            #endregion
        }
    }
    

    您需要将常量 ResourceScriptsPrefix 更改为您存储 python 脚本的基本命名空间。

    第 2 部分,自定义 ScriptHost

    namespace ZenCoding.Hosting
    {
        internal class ResourceAwareScriptHost : ScriptHost
        {
            private readonly PlatformAdaptationLayer _layer = new ResourceAwarePlatformAdaptationLayer();
            public override PlatformAdaptationLayer PlatformAdaptationLayer
            {
                get { return _layer; }
            }
        }
    }
    

    第 3 部分,最后,如何使用您的自定义内容获取 Python 引擎:

    namespace ZenCoding.Hosting
    {
        internal static class ResourceAwareScriptEngineSetup
        {
            public static ScriptEngine CreateResourceAwareEngine()
            {
                var setup = Python.CreateRuntimeSetup(null);
                setup.HostType = typeof(ResourceAwareScriptHost);
                var runtime = new ScriptRuntime(setup);
                return runtime.GetEngineByTypeName(typeof(PythonContext).AssemblyQualifiedName);
            }
        }
    }
    

    更改此设置以从其他位置(如数据库)加载脚本很容易。只需更改 OpenResourceStream、ResourceFileExists 和 ResourceDirectoryExists 方法即可。

    希望这会有所帮助。

    【讨论】:

    • 你能补充一下这是什么版本的 IronPython 吗?我认为这是针对比 IronPython 2.7 更旧的版本?
    • 是的,这是两年前的事了,所以无论哪个版本都是当时的版本。可能是 2.6,但我不确定。
    • 2.7+ 有没有更好的方法?顺便说一句,上述方法仍然有效。
    • @simonjpascoe,我试图在this answer 中解释另外两种方法。
    【解决方案3】:

    您可以使用 PlatformAdaptationLayer 将所有 I/O 重定向到数据库。为此,您需要实现一个提供 PAL 的 ScriptHost。然后,当您创建 ScriptRuntime 时,您将 HostType 设置为您的主机类型,它将用于运行时。然后在 PAL 上覆盖 OpenInputFileStream 并返回一个包含数据库内容的流对象(从数据库读取后,您可以在此处使用 MemoryStream)。

    如果您仍想提供对文件 I/O 的访问,您可以随时退回到 FileStream 的“文件”,以获取您找不到的“文件”。

    【讨论】:

    • 我正在研究一个以此为起点的解决方案(我发现一些有用的东西散落在各处(efreedom.com/Question/1-3264029/…mail-archive.com/users@lists.ironpython.com/msg06080.html)。当前的问题似乎是破译各种平台适应层方法被调用。我似乎根本找不到任何文档。
    • 如果您想查看现有实现,Silverlight 主机可能是一个合理的示例,说明如何执行此操作。
    • 查看我的答案以获得相当简单的自定义 PlatformAdaptationLayer
    • 如果您只想将文件存储在预期文件系统位置以外的位置,但如果您想预编译它们,则此方法似乎很有用。
    【解决方案4】:

    您需要实现导入挂钩。这是一个带有指针的 SO 问题:PEP 302 Example: New Import Hooks

    【讨论】:

    • 不确定我对问题的原始措辞是否足够清楚。调用 Python 脚本的应用程序是用 C# 编写的,我希望尽可能将 Python 脚本视为一个黑盒,因此如果可能的话,我希望能够在 C# 端拦截导入。我已经编辑了原始问题以反映这些附加信息。
    • 见上文。这看起来应该可以工作,但事实并非如此。也许 IronPython Import 实现打破了标准方法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-17
    • 1970-01-01
    • 2020-09-11
    • 2022-01-18
    • 2015-07-28
    • 1970-01-01
    • 2015-08-12
    相关资源
    最近更新 更多