2010-11-05 49 views
7

我從數據庫加載IronPython腳本並執行它。這對於簡單的腳本來說工作正常,但導入是一個問題。我如何攔截這些導入調用,然後從數據庫加載適當的腳本?自定義IronPython導入分辨率

編輯:我的主要應用程序是用C#編寫的,我想在不編輯Python腳本的情況下攔截C#端的調用。

編輯:從我做的研究,它看起來像創建自己的PlatformAdaptationLayer是你的方式假設來實現這一點,但它不起作用在這種情況下。我創建了自己的PAL,在我的測試中,我的FileExsists方法在腳本中的每個導入都被調用。但由於某種原因,它永遠不會調用任何超載的方法OpenInputFileStream。挖掘IronPython源代碼,一旦FileExists返回true,它會嘗試在路徑上找到文件本身。所以這看起來像死衚衕。

回答

11

經過大量的反覆試驗,我得出了一個解決方案。我從未設法使PlatformAdaptationLayer方法正常工作。嘗試加載模塊時,它從不回叫PAL。

所以我決定做的是通過使用SetVariable方法取代內置的導入功能,如下圖所示(發動機和範圍保護成員暴露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); 
    } 
} 

希望這可以幫助別人!

+0

謝謝!我的用例與您的用例完全相同。 – 2011-10-26 06:37:13

0

您需要實現導入掛鉤。以下是帶指針的SO問題:PEP 302 Example: New Import Hooks

+0

不知道我的問題原來的措辭是否足夠清楚。調用Python腳本的應用程序是用C#編寫的,我希望儘可能將Python腳本視爲黑盒子,所以我希望能夠儘可能在C#端攔截導入。我編輯了原始問題以反映這些附加信息。 – Dan 2010-11-05 12:58:58

+0

見上文。這看起來應該起作用,但它不起作用。 IronPython Import實現可能違反標準方法嗎? – Dan 2010-11-08 13:03:02

1

您可以使用PlatformAdaptationLayer將所有I/O重定向到數據庫。要做到這一點,你需要實現一個提供PAL的ScriptHost。然後,當您創建ScriptRuntime時,您將HostType設置爲您的主機類型,並將用於運行時。然後在PAL上重寫OpenInputFileStream,並返回一個包含數據庫內容的流對象(從數據庫讀取數據後,您可以在這裏使用MemoryStream)。

如果你想繼續提供對文件I/O的訪問,你總是可以回退到FileStream中找不到的「文件」。

+0

我正在使用這個作爲起點的解決方案(我發現了一些有用的東西散落在(http://zh.efreedom.com/Question/1-3264029/Can-Set-Dynamic-Imports-Hosting-IronPython和http ://www.mail-archive.com/[email protected]/msg06080.html)。目前的問題似乎是解釋了各種平臺適配層方法如何被調用。我似乎無法找到任何文檔 – Dan 2010-11-08 00:10:10

+0

如果你想看到現有的實現,Silverlight主機可能是一個合理的例子 – 2010-11-08 18:01:52

+0

看到我的答案是一個相當簡單的定製PlatformAdaptationLayer – 2011-01-10 07:32:58

9

我只是想做同樣的事情,除了我想將我的腳本存儲爲嵌入式資源。我創建了一個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方法。

希望這會有所幫助。

+0

co你是否添加了IronPython的這個版本?我認爲這是比IronPython 2.7更舊的版本? – 2013-02-06 10:14:51

+0

是的,這是兩年前的版本,因此無論哪個版本是當前版本。可能是2.6,但我不確定。 – 2013-02-12 13:37:35

+0

2.7+有更好的方法嗎?以上依然工作順便說一句。 – 2015-10-22 04:25:29