From 8e0b1a2a376e5080adaa69e7fd5f6723b572b1f1 Mon Sep 17 00:00:00 2001 From: Victor Uriarte Date: Fri, 3 Mar 2017 09:57:12 -0700 Subject: [PATCH 01/20] Deprecate public RunString Had to remove defaults to disambiguate call on `internal RunString`. Can re-add after removing `public RunString` Closes #401 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b446cc4b6..cc6fbb54c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Deprecated `RunString` (#401) +### Deprecated + +- Deprecated `RunString` (#401) + ### Fixed - Fixed crash during Initialization (#262)(#343) From 72003aa45b3378e57b18a15b78f016641bdbee44 Mon Sep 17 00:00:00 2001 From: yag Date: Wed, 15 Feb 2017 23:17:55 +0800 Subject: [PATCH 02/20] add a scope class to manage the context of interaction with Python and simplify the variable exchanging --- src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/pyscope.cs | 113 +++++ src/runtime/pythonengine.cs | 437 ++++++++++++++++++++ 3 files changed, 551 insertions(+) create mode 100644 src/embed_tests/pyscope.cs diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 3ec0d4b57..1334d517c 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -98,6 +98,7 @@ + diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs new file mode 100644 index 000000000..b22c1f193 --- /dev/null +++ b/src/embed_tests/pyscope.cs @@ -0,0 +1,113 @@ +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + [TestFixture] + public class PyScopeTest + { + PyScope ps; + + [SetUp] + public void SetUp() + { + ps = Py.Session("test"); + } + + [TearDown] + public void TearDown() + { + ps.Dispose(); + } + + /// + /// Eval a Python expression and obtain its return value. + /// + [Test] + public void TestEval() + { + ps.SetLocal("a", 1); + int result = ps.Eval("a+2"); + Assert.AreEqual(result, 3); + } + + /// + /// Exec Python statements and obtain the local variables created. + /// + [Test] + public void TestExec() + { + ps.SetGlobal("bb", 100); //declare a global variable + ps.SetLocal("cc", 10); //declare a local variable + ps.Exec("aa=bb+cc+3"); + int result = ps.Get("aa"); + Assert.AreEqual(result, 113); + } + + /// + /// Exec Python statements in a subscope of the session then discard it. + /// + [Test] + public void TestSubScope() + { + ps.SetGlobal("bb", 100); //declare a global variable + ps.SetLocal("cc", 10); //declare a local variable + + PyScope scope = ps.SubScope(); + scope.Exec("aa=bb+cc+3"); + int result = scope.Get("aa"); + Assert.AreEqual(result, 113); // + scope.Dispose(); + + Assert.IsFalse(ps.Exists("aa")); + } + + /// + /// Import a python module into the session. + /// Equivalent to the Python "import" statement. + /// + [Test] + public void TestImport() + { + dynamic sys = ps.Import("sys"); + Assert.IsTrue(ps.Exists("sys")); + + ps.Exec("sys.attr1 = 2"); + int value1 = ps.Eval("sys.attr1"); + int value2 = (int)sys.attr1.AsManagedObject(typeof(int)); + Assert.AreEqual(value1, 2); + Assert.AreEqual(value2, 2); + } + + /// + /// Import a python module into the session with a new name. + /// Equivalent to the Python "import .. as .." statement. + /// + [Test] + public void TestImportAs() + { + ps.ImportAs("sys", "sys1"); + Assert.IsTrue(ps.Exists("sys1")); + } + + /// + /// Suspend the Session, and reuse it later. + /// + [Test] + public void TestSuspend() + { + ps.SetGlobal("bb", 100); + ps.SetLocal("cc", 10); + ps.Suspend(); + + using (Py.GIL()) + { + PythonEngine.RunSimpleString("import sys;"); + } + + ps.Exec("aa=bb+cc+3"); + int result = ps.Get("aa"); + Assert.AreEqual(result, 113); + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 1fd3b239a..d11a8355e 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -527,6 +527,415 @@ public enum RunFlagType Eval = 258 } + public class PySessionDisposedException: Exception + { + + } + + public class PyScope : IDisposable + { + public class GILState : IDisposable + { + private bool isGetGIL; + private IntPtr state; + + internal GILState() + { + } + + public void AcquireLock() + { + if (isGetGIL) + { + return; + } + state = PythonEngine.AcquireLock(); + isGetGIL = true; + } + + public void ReleaseLock() + { + if (isGetGIL) + { + PythonEngine.ReleaseLock(state); + isGetGIL = false; + } + } + + public void Dispose() + { + this.ReleaseLock(); + GC.SuppressFinalize(this); + } + + ~GILState() + { + Dispose(); + } + } + + private string name; + + private PyScope parent; + + private GILState state; + + private bool isDisposed; + + private PyScope(GILState state) + { + this.isDisposed = false; + this.state = state; + globals = Runtime.PyDict_New(); + if (globals == IntPtr.Zero) + { + throw new PythonException(); + } + locals = Runtime.PyDict_New(); + if (locals == IntPtr.Zero) + { + throw new PythonException(); + } + } + + internal PyScope(string name, GILState state) + :this(state) + { + this.state = state; + this.name = name; + Runtime.PyDict_SetItemString( + globals, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); + } + + internal PyScope(PyScope parent, GILState state) + : this(state) + { + this.state = state; + this.parent = parent; + globals = Runtime.PyDict_New(); + if (globals == IntPtr.Zero) + { + throw new PythonException(); + } + locals = Runtime.PyDict_New(); + if (locals == IntPtr.Zero) + { + throw new PythonException(); + } + int result = Runtime.PyDict_Update(globals, parent.globals); + if (result < 0) + { + throw new PythonException(); + } + result = Runtime.PyDict_Update(globals, parent.locals); + if (result < 0) + { + throw new PythonException(); + } + } + + /// + /// the dict for global variables + /// + public IntPtr globals + { + get; + private set; + } + + /// + /// the dict for local variables + /// + public IntPtr locals + { + get; + private set; + } + + public PyScope SubScope() + { + return new PyScope(this, this.state); + } + + public void Suspend() + { + this.state.ReleaseLock(); + } + + /// + /// Import Method + /// + /// + /// The import .. as .. statement in Python. + /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// + public PyObject Import(string name) + { + return ImportAs(name, name); + } + + /// + /// Import Method + /// + /// + /// The import .. as .. statement in Python. + /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// + public PyObject ImportAs(string name, string asname) + { + this.AcquireLock(); + PyObject module = PythonEngine.ImportModule(name); + if (asname == null) + { + asname = name; + } + SetLocal(asname, module); + return module; + } + + /// + /// Evaluate a Python expression + /// + /// + /// Evaluate a Python expression and return the result as a PyObject + /// or null if an exception is raised. + /// + public PyObject Eval(string code) + { + this.AcquireLock(); + var flag = (IntPtr)Runtime.Py_eval_input; + IntPtr ptr = Runtime.PyRun_String( + code, flag, globals, locals + ); + Py.Throw(); + return new PyObject(ptr); + } + + /// + /// Evaluate a Python expression + /// + /// + /// Evaluate a Python expression and convert the result to Managed Object. + /// + public T Eval(string code) + { + PyObject pyObj = Eval(code); + T obj = (T)pyObj.AsManagedObject(typeof(T)); + return obj; + } + + /// + /// Exec Method + /// + /// + /// Evaluate a Python script and save its local variables in the current local variable dict. + /// + public void Exec(string code) + { + this.AcquireLock(); + Exec(code, this.globals, this.locals); + } + + private void Exec(string code, IntPtr _globals, IntPtr _locals) + { + var flag = (IntPtr)Runtime.Py_file_input; + IntPtr ptr = Runtime.PyRun_String( + code, flag, _globals, _locals + ); + Py.Throw(); + if (ptr != Runtime.PyNone) + { + throw new PythonException(); + } + Runtime.XDecref(ptr); + } + + /// + /// SetGlobal Method + /// + /// + /// Add a new variable to global variable dict if it not exists + /// or set the value of the global variable if it exists. + /// + public void SetGlobal(string name, object value) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + IntPtr _value = GetInstHandle(value); + int r = Runtime.PyObject_SetItem(globals, pyKey.obj, _value); + if (r < 0) + { + throw new PythonException(); + } + Runtime.XDecref(_value); + } + } + + /// + /// DelGlobal Method + /// + /// + /// Remove a variable from the global variable dict. + /// + public void DelGlobal(string name) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_DelItem(globals, pyKey.obj); + if (r < 0) + { + throw new PythonException(); + } + } + } + + /// + /// SetLocal Method + /// + /// + /// Add a new variable to local variable dict if it not exists + /// or set the value of the local variable if it exists. + /// + public void SetLocal(string name, object value) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + IntPtr _value = GetInstHandle(value); + int r = Runtime.PyObject_SetItem(locals, pyKey.obj, _value); + if (r < 0) + { + throw new PythonException(); + } + Runtime.XDecref(_value); + } + } + + /// + /// DelLocal Method + /// + /// + /// Remove a variable from the local variable dict. + /// + public void DelLocal(string name) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_DelItem(locals, pyKey.obj); + if (r < 0) + { + throw new PythonException(); + } + } + } + + /// + /// Exists Method + /// + /// + /// Returns true if the variable appears in the local variable dict or the global variable dict. + /// + public bool Exists(string name) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) + { + return true; + } + return Runtime.PyMapping_HasKey(globals, pyKey.obj) != 0; + } + } + + /// + /// Get Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable is not exists, return null. + /// + public PyObject Get(string name) + { + this.AcquireLock(); + using (var pyKey = new PyString(name)) + { + IntPtr op; + if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) + { + op = Runtime.PyObject_GetItem(locals, pyKey.obj); + } + else if (Runtime.PyMapping_HasKey(globals, pyKey.obj) != 0) + { + op = Runtime.PyObject_GetItem(globals, pyKey.obj); + } + else + { + return null; //name not exists + } + if (op == IntPtr.Zero) + { + throw new PythonException(); + } + return new PyObject(op); + } + } + + public T Get(string name) + { + PyObject obj = this.Get(name); + return (T)obj.AsManagedObject(typeof(T)); + } + + private static IntPtr GetInstHandle(object value) + { + if (value == null) + { + Runtime.XIncref(Runtime.PyNone); + return Runtime.PyNone; + } + else + { + var ptr = Converter.ToPython(value, value.GetType()); + return ptr; + } + } + + private void AcquireLock() + { + if(isDisposed) + { + throw new PySessionDisposedException(); + } + this.state.AcquireLock(); + } + + public virtual void Dispose() + { + if(isDisposed) + { + return; + } + Runtime.XDecref(globals); + Runtime.XDecref(locals); + if (this.parent == null) + { + Py.RemoveSession(name); + } + isDisposed = true; + } + + ~PyScope() + { + Dispose(); + } + } + public static class Py { public static GILState GIL() @@ -539,6 +948,34 @@ public static GILState GIL() return new GILState(); } + private static PyScope.GILState gil = new PyScope.GILState(); + + /// + /// Sessions should be cleared after shut down. + /// Currently, the seperation of static methods into Py and PythonEngine makes the code ugly. + /// + private static Dictionary Sessions = new Dictionary(); + + public static PyScope Session(string name) + { + if (!PythonEngine.IsInitialized) + { + PythonEngine.Initialize(); + } + if(Sessions.ContainsKey(name)) + { + return Sessions[name]; + } + var session = new PyScope(name, gil); + Sessions[name] = session; + return session; + } + + internal static void RemoveSession(string name) + { + Sessions.Remove(name); + } + public class GILState : IDisposable { private IntPtr state; From efd3798d5900ffe2e8681c937e99be320f7e8090 Mon Sep 17 00:00:00 2001 From: yag Date: Thu, 23 Feb 2017 23:16:24 +0800 Subject: [PATCH 03/20] Rename several methods and add three methods Referring to IronPython, change the name of the methods Get, Exists, SetLocal, DelLocal to GetVariable, ContainsVariable, SetVariable, RemoveVariable. Hidden the methods SetGlobalVariable, RemoveGlobalVariable. Add a new method 'Compile' to compile string into ast, the ast can be seen as the ScriptSource of IronPython. Add two new methods 'Execute' and 'Execute' to execute an ast and obtain the result, corresponding to the 'Execute' method of IronPython. --- src/embed_tests/pyscope.cs | 56 +++++++++++++++++++++++------- src/runtime/pythonengine.cs | 68 ++++++++++++++++++++++++++++++------- src/runtime/runtime.cs | 7 +++- 3 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs index b22c1f193..483d74fc5 100644 --- a/src/embed_tests/pyscope.cs +++ b/src/embed_tests/pyscope.cs @@ -26,7 +26,7 @@ public void TearDown() [Test] public void TestEval() { - ps.SetLocal("a", 1); + ps.SetVariable("a", 1); int result = ps.Eval("a+2"); Assert.AreEqual(result, 3); } @@ -37,10 +37,40 @@ public void TestEval() [Test] public void TestExec() { - ps.SetGlobal("bb", 100); //declare a global variable - ps.SetLocal("cc", 10); //declare a local variable + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable ps.Exec("aa=bb+cc+3"); - int result = ps.Get("aa"); + int result = ps.GetVariable("aa"); + Assert.AreEqual(result, 113); + } + + /// + /// Compile an expression into an ast object; + /// Execute the ast and obtain its return value. + /// + [Test] + public void TestCompileExpression() + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + var script = ps.Compile("bb+cc+3", "", CompileMode.Eval); + var result = ps.Execute(script); + Assert.AreEqual(result, 113); + } + + /// + /// Compile Python statements into an ast object; + /// Execute the ast; + /// Obtain the local variables created. + /// + [Test] + public void TestCompileStatements() + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + var script = ps.Compile("aa=bb+cc+3", "", CompileMode.File); + ps.Execute(script); + int result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); } @@ -50,16 +80,16 @@ public void TestExec() [Test] public void TestSubScope() { - ps.SetGlobal("bb", 100); //declare a global variable - ps.SetLocal("cc", 10); //declare a local variable + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable PyScope scope = ps.SubScope(); scope.Exec("aa=bb+cc+3"); - int result = scope.Get("aa"); + int result = scope.GetVariable("aa"); Assert.AreEqual(result, 113); // scope.Dispose(); - Assert.IsFalse(ps.Exists("aa")); + Assert.IsFalse(ps.ContainsVariable("aa")); } /// @@ -70,7 +100,7 @@ public void TestSubScope() public void TestImport() { dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.Exists("sys")); + Assert.IsTrue(ps.ContainsVariable("sys")); ps.Exec("sys.attr1 = 2"); int value1 = ps.Eval("sys.attr1"); @@ -87,7 +117,7 @@ public void TestImport() public void TestImportAs() { ps.ImportAs("sys", "sys1"); - Assert.IsTrue(ps.Exists("sys1")); + Assert.IsTrue(ps.ContainsVariable("sys1")); } /// @@ -96,8 +126,8 @@ public void TestImportAs() [Test] public void TestSuspend() { - ps.SetGlobal("bb", 100); - ps.SetLocal("cc", 10); + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); ps.Suspend(); using (Py.GIL()) @@ -106,7 +136,7 @@ public void TestSuspend() } ps.Exec("aa=bb+cc+3"); - int result = ps.Get("aa"); + int result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index d11a8355e..df2e0225a 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -532,6 +532,13 @@ public class PySessionDisposedException: Exception } + public enum CompileMode + { + Single = 256, + File = 257, + Eval = 258 + } + public class PyScope : IDisposable { public class GILState : IDisposable @@ -691,10 +698,47 @@ public PyObject ImportAs(string name, string asname) { asname = name; } - SetLocal(asname, module); + SetVariable(asname, module); return module; } + public PyObject Execute(PyObject script) + { + IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, globals, locals); + Py.Throw(); + if(ptr == Runtime.PyNone) + { + Runtime.XDecref(ptr); + return null; + } + return new PyObject(ptr); + } + + public T Execute(PyObject script) + { + var pyObj = this.Execute(script); + if(pyObj == null) + { + return default(T); + } + T obj = (T)pyObj.AsManagedObject(typeof(T)); + return obj; + } + + /// + /// Compile Method + /// + /// + /// Compile Python expression/statements into ast. + /// + public PyObject Compile(string code, string filename = "", CompileMode mode = CompileMode.File) + { + IntPtr flag = (IntPtr)mode; + IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); + Py.Throw(); + return new PyObject(ptr); + } + /// /// Evaluate a Python expression /// @@ -751,15 +795,15 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) } Runtime.XDecref(ptr); } - + /// - /// SetGlobal Method + /// SetGlobalVariable Method /// /// /// Add a new variable to global variable dict if it not exists /// or set the value of the global variable if it exists. /// - public void SetGlobal(string name, object value) + internal void SetGlobalVariable(string name, object value) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -775,12 +819,12 @@ public void SetGlobal(string name, object value) } /// - /// DelGlobal Method + /// RemoveGlobalVariable Method /// /// /// Remove a variable from the global variable dict. /// - public void DelGlobal(string name) + internal void RemoveGlobalVariable(string name) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -800,7 +844,7 @@ public void DelGlobal(string name) /// Add a new variable to local variable dict if it not exists /// or set the value of the local variable if it exists. /// - public void SetLocal(string name, object value) + public void SetVariable(string name, object value) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -821,7 +865,7 @@ public void SetLocal(string name, object value) /// /// Remove a variable from the local variable dict. /// - public void DelLocal(string name) + public void RemoveVariable(string name) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -840,7 +884,7 @@ public void DelLocal(string name) /// /// Returns true if the variable appears in the local variable dict or the global variable dict. /// - public bool Exists(string name) + public bool ContainsVariable(string name) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -860,7 +904,7 @@ public bool Exists(string name) /// Returns the value of the variable, local variable first. /// If the variable is not exists, return null. /// - public PyObject Get(string name) + public PyObject GetVariable(string name) { this.AcquireLock(); using (var pyKey = new PyString(name)) @@ -886,9 +930,9 @@ public PyObject Get(string name) } } - public T Get(string name) + public T GetVariable(string name) { - PyObject obj = this.Get(name); + PyObject obj = this.GetVariable(name); return (T)obj.AsManagedObject(typeof(T)); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 9ee693cf3..2c35ce44d 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -707,7 +707,12 @@ public static extern int Py_Main( [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); - [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, + ExactSpelling = true, CharSet = CharSet.Ansi)] + internal unsafe static extern IntPtr + PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); + + [DllImport(PythonDll)] internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok); [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] From 9f50e25dda9b3a30f1cea594bdfbe133d756c60b Mon Sep 17 00:00:00 2001 From: yag Date: Fri, 24 Feb 2017 17:12:06 +0800 Subject: [PATCH 04/20] rebased --- src/embed_tests/pyscope.cs | 4 ++-- src/runtime/pythonengine.cs | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs index 483d74fc5..85baa6496 100644 --- a/src/embed_tests/pyscope.cs +++ b/src/embed_tests/pyscope.cs @@ -53,7 +53,7 @@ public void TestCompileExpression() { ps.SetVariable("bb", 100); //declare a global variable ps.SetVariable("cc", 10); //declare a local variable - var script = ps.Compile("bb+cc+3", "", CompileMode.Eval); + var script = ps.Compile("bb+cc+3", "", RunFlagType.Eval); var result = ps.Execute(script); Assert.AreEqual(result, 113); } @@ -68,7 +68,7 @@ public void TestCompileStatements() { ps.SetVariable("bb", 100); //declare a global variable ps.SetVariable("cc", 10); //declare a local variable - var script = ps.Compile("aa=bb+cc+3", "", CompileMode.File); + var script = ps.Compile("aa=bb+cc+3", "", RunFlagType.File); ps.Execute(script); int result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index df2e0225a..52c767d88 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -531,14 +531,7 @@ public class PySessionDisposedException: Exception { } - - public enum CompileMode - { - Single = 256, - File = 257, - Eval = 258 - } - + public class PyScope : IDisposable { public class GILState : IDisposable @@ -731,7 +724,7 @@ public T Execute(PyObject script) /// /// Compile Python expression/statements into ast. /// - public PyObject Compile(string code, string filename = "", CompileMode mode = CompileMode.File) + public PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) { IntPtr flag = (IntPtr)mode; IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); From add5ba8fe7ad4ef7c73ec0a3ea980a033d05e905 Mon Sep 17 00:00:00 2001 From: Victor Uriarte Date: Tue, 28 Feb 2017 09:23:17 -0700 Subject: [PATCH 05/20] Rebased update --- src/runtime/pythonengine.cs | 12 ++++++------ src/runtime/runtime.cs | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 52c767d88..3800fdd90 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -531,7 +531,7 @@ public class PySessionDisposedException: Exception { } - + public class PyScope : IDisposable { public class GILState : IDisposable @@ -698,7 +698,7 @@ public PyObject ImportAs(string name, string asname) public PyObject Execute(PyObject script) { IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, globals, locals); - Py.Throw(); + Runtime.CheckExceptionOccurred(); if(ptr == Runtime.PyNone) { Runtime.XDecref(ptr); @@ -728,7 +728,7 @@ public PyObject Compile(string code, string filename = "", RunFlagType mode = Ru { IntPtr flag = (IntPtr)mode; IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); - Py.Throw(); + Runtime.CheckExceptionOccurred(); return new PyObject(ptr); } @@ -746,7 +746,7 @@ public PyObject Eval(string code) IntPtr ptr = Runtime.PyRun_String( code, flag, globals, locals ); - Py.Throw(); + Runtime.CheckExceptionOccurred(); return new PyObject(ptr); } @@ -774,14 +774,14 @@ public void Exec(string code) this.AcquireLock(); Exec(code, this.globals, this.locals); } - + private void Exec(string code, IntPtr _globals, IntPtr _locals) { var flag = (IntPtr)Runtime.Py_file_input; IntPtr ptr = Runtime.PyRun_String( code, flag, _globals, _locals ); - Py.Throw(); + Runtime.CheckExceptionOccurred(); if (ptr != Runtime.PyNone) { throw new PythonException(); diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 2c35ce44d..59a02e6b6 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -707,10 +707,8 @@ public static extern int Py_Main( [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); - [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl, - ExactSpelling = true, CharSet = CharSet.Ansi)] - internal unsafe static extern IntPtr - PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); + [DllImport(PythonDll)] + internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); [DllImport(PythonDll)] internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok); From c15555cafd070c1dcf16f8a5eeeaa54905234d30 Mon Sep 17 00:00:00 2001 From: Victor Uriarte Date: Tue, 28 Feb 2017 09:31:38 -0700 Subject: [PATCH 06/20] format cleanup --- src/embed_tests/pyscope.cs | 34 ++++++++-------- src/runtime/pythonengine.cs | 77 ++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs index 85baa6496..2eb427847 100644 --- a/src/embed_tests/pyscope.cs +++ b/src/embed_tests/pyscope.cs @@ -1,13 +1,13 @@ +using System; using NUnit.Framework; using Python.Runtime; namespace Python.EmbeddingTest { - [TestFixture] public class PyScopeTest { - PyScope ps; - + private PyScope ps; + [SetUp] public void SetUp() { @@ -15,7 +15,7 @@ public void SetUp() } [TearDown] - public void TearDown() + public void Dispose() { ps.Dispose(); } @@ -27,7 +27,7 @@ public void TearDown() public void TestEval() { ps.SetVariable("a", 1); - int result = ps.Eval("a+2"); + var result = ps.Eval("a + 2"); Assert.AreEqual(result, 3); } @@ -39,8 +39,8 @@ public void TestExec() { ps.SetVariable("bb", 100); //declare a global variable ps.SetVariable("cc", 10); //declare a local variable - ps.Exec("aa=bb+cc+3"); - int result = ps.GetVariable("aa"); + ps.Exec("aa = bb + cc + 3"); + var result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); } @@ -53,7 +53,7 @@ public void TestCompileExpression() { ps.SetVariable("bb", 100); //declare a global variable ps.SetVariable("cc", 10); //declare a local variable - var script = ps.Compile("bb+cc+3", "", RunFlagType.Eval); + PyObject script = ps.Compile("bb + cc + 3", "", RunFlagType.Eval); var result = ps.Execute(script); Assert.AreEqual(result, 113); } @@ -68,14 +68,14 @@ public void TestCompileStatements() { ps.SetVariable("bb", 100); //declare a global variable ps.SetVariable("cc", 10); //declare a local variable - var script = ps.Compile("aa=bb+cc+3", "", RunFlagType.File); + PyObject script = ps.Compile("aa = bb + cc + 3", "", RunFlagType.File); ps.Execute(script); - int result = ps.GetVariable("aa"); + var result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); } /// - /// Exec Python statements in a subscope of the session then discard it. + /// Exec Python statements in a SubScope of the session then discard it. /// [Test] public void TestSubScope() @@ -84,8 +84,8 @@ public void TestSubScope() ps.SetVariable("cc", 10); //declare a local variable PyScope scope = ps.SubScope(); - scope.Exec("aa=bb+cc+3"); - int result = scope.GetVariable("aa"); + scope.Exec("aa = bb + cc + 3"); + var result = scope.GetVariable("aa"); Assert.AreEqual(result, 113); // scope.Dispose(); @@ -103,8 +103,8 @@ public void TestImport() Assert.IsTrue(ps.ContainsVariable("sys")); ps.Exec("sys.attr1 = 2"); - int value1 = ps.Eval("sys.attr1"); - int value2 = (int)sys.attr1.AsManagedObject(typeof(int)); + var value1 = ps.Eval("sys.attr1"); + var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); Assert.AreEqual(value1, 2); Assert.AreEqual(value2, 2); } @@ -135,8 +135,8 @@ public void TestSuspend() PythonEngine.RunSimpleString("import sys;"); } - ps.Exec("aa=bb+cc+3"); - int result = ps.GetVariable("aa"); + ps.Exec("aa = bb + cc + 3"); + var result = ps.GetVariable("aa"); Assert.AreEqual(result, 113); } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 3800fdd90..c7bbacc1a 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -527,9 +527,8 @@ public enum RunFlagType Eval = 258 } - public class PySessionDisposedException: Exception + public class PySessionDisposedException : Exception { - } public class PyScope : IDisposable @@ -564,7 +563,7 @@ public void ReleaseLock() public void Dispose() { - this.ReleaseLock(); + ReleaseLock(); GC.SuppressFinalize(this); } @@ -584,7 +583,7 @@ public void Dispose() private PyScope(GILState state) { - this.isDisposed = false; + isDisposed = false; this.state = state; globals = Runtime.PyDict_New(); if (globals == IntPtr.Zero) @@ -599,14 +598,14 @@ private PyScope(GILState state) } internal PyScope(string name, GILState state) - :this(state) + : this(state) { this.state = state; this.name = name; Runtime.PyDict_SetItemString( - globals, "__builtins__", - Runtime.PyEval_GetBuiltins() - ); + globals, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); } internal PyScope(PyScope parent, GILState state) @@ -639,29 +638,21 @@ internal PyScope(PyScope parent, GILState state) /// /// the dict for global variables /// - public IntPtr globals - { - get; - private set; - } + public IntPtr globals { get; private set; } /// /// the dict for local variables /// - public IntPtr locals - { - get; - private set; - } + public IntPtr locals { get; private set; } public PyScope SubScope() { - return new PyScope(this, this.state); + return new PyScope(this, state); } public void Suspend() { - this.state.ReleaseLock(); + state.ReleaseLock(); } /// @@ -685,7 +676,7 @@ public PyObject Import(string name) /// public PyObject ImportAs(string name, string asname) { - this.AcquireLock(); + AcquireLock(); PyObject module = PythonEngine.ImportModule(name); if (asname == null) { @@ -699,7 +690,7 @@ public PyObject Execute(PyObject script) { IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, globals, locals); Runtime.CheckExceptionOccurred(); - if(ptr == Runtime.PyNone) + if (ptr == Runtime.PyNone) { Runtime.XDecref(ptr); return null; @@ -709,12 +700,12 @@ public PyObject Execute(PyObject script) public T Execute(PyObject script) { - var pyObj = this.Execute(script); - if(pyObj == null) + PyObject pyObj = Execute(script); + if (pyObj == null) { return default(T); } - T obj = (T)pyObj.AsManagedObject(typeof(T)); + var obj = (T)pyObj.AsManagedObject(typeof(T)); return obj; } @@ -726,7 +717,7 @@ public T Execute(PyObject script) /// public PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) { - IntPtr flag = (IntPtr)mode; + var flag = (IntPtr)mode; IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); Runtime.CheckExceptionOccurred(); return new PyObject(ptr); @@ -741,7 +732,7 @@ public PyObject Compile(string code, string filename = "", RunFlagType mode = Ru /// public PyObject Eval(string code) { - this.AcquireLock(); + AcquireLock(); var flag = (IntPtr)Runtime.Py_eval_input; IntPtr ptr = Runtime.PyRun_String( code, flag, globals, locals @@ -759,7 +750,7 @@ public PyObject Eval(string code) public T Eval(string code) { PyObject pyObj = Eval(code); - T obj = (T)pyObj.AsManagedObject(typeof(T)); + var obj = (T)pyObj.AsManagedObject(typeof(T)); return obj; } @@ -771,8 +762,8 @@ public T Eval(string code) /// public void Exec(string code) { - this.AcquireLock(); - Exec(code, this.globals, this.locals); + AcquireLock(); + Exec(code, globals, locals); } private void Exec(string code, IntPtr _globals, IntPtr _locals) @@ -798,7 +789,7 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) /// internal void SetGlobalVariable(string name, object value) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { IntPtr _value = GetInstHandle(value); @@ -819,7 +810,7 @@ internal void SetGlobalVariable(string name, object value) /// internal void RemoveGlobalVariable(string name) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { int r = Runtime.PyObject_DelItem(globals, pyKey.obj); @@ -839,7 +830,7 @@ internal void RemoveGlobalVariable(string name) /// public void SetVariable(string name, object value) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { IntPtr _value = GetInstHandle(value); @@ -860,7 +851,7 @@ public void SetVariable(string name, object value) /// public void RemoveVariable(string name) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { int r = Runtime.PyObject_DelItem(locals, pyKey.obj); @@ -879,7 +870,7 @@ public void RemoveVariable(string name) /// public bool ContainsVariable(string name) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) @@ -899,7 +890,7 @@ public bool ContainsVariable(string name) /// public PyObject GetVariable(string name) { - this.AcquireLock(); + AcquireLock(); using (var pyKey = new PyString(name)) { IntPtr op; @@ -925,7 +916,7 @@ public PyObject GetVariable(string name) public T GetVariable(string name) { - PyObject obj = this.GetVariable(name); + PyObject obj = GetVariable(name); return (T)obj.AsManagedObject(typeof(T)); } @@ -938,29 +929,29 @@ private static IntPtr GetInstHandle(object value) } else { - var ptr = Converter.ToPython(value, value.GetType()); + IntPtr ptr = Converter.ToPython(value, value.GetType()); return ptr; } } private void AcquireLock() { - if(isDisposed) + if (isDisposed) { throw new PySessionDisposedException(); } - this.state.AcquireLock(); + state.AcquireLock(); } public virtual void Dispose() { - if(isDisposed) + if (isDisposed) { return; } Runtime.XDecref(globals); Runtime.XDecref(locals); - if (this.parent == null) + if (parent == null) { Py.RemoveSession(name); } @@ -999,7 +990,7 @@ public static PyScope Session(string name) { PythonEngine.Initialize(); } - if(Sessions.ContainsKey(name)) + if (Sessions.ContainsKey(name)) { return Sessions[name]; } From b0d57e997790a889595571a2aaae36e8459d5311 Mon Sep 17 00:00:00 2001 From: yag Date: Wed, 1 Mar 2017 22:55:43 +0800 Subject: [PATCH 07/20] create unnamed pyscope, make PyScope.GILState save remove method GetInstHandle add function to create unnamed pyscope add a field isDisposed for PyScope.GILState to make it more save --- src/runtime/pythonengine.cs | 80 ++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index c7bbacc1a..f061e3b4f 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -536,10 +536,15 @@ public class PyScope : IDisposable public class GILState : IDisposable { private bool isGetGIL; + + private bool isDisposed; + private IntPtr state; internal GILState() { + isGetGIL = false; + isDisposed = false; } public void AcquireLock() @@ -563,6 +568,12 @@ public void ReleaseLock() public void Dispose() { + if (isDisposed) + { + return; + } + isDisposed = true; + AcquireLock(); ReleaseLock(); GC.SuppressFinalize(this); } @@ -573,9 +584,17 @@ public void Dispose() } } - private string name; + public string Name + { + get; + private set; + } - private PyScope parent; + public PyScope Parent + { + get; + private set; + } private GILState state; @@ -601,7 +620,7 @@ internal PyScope(string name, GILState state) : this(state) { this.state = state; - this.name = name; + this.Name = name; Runtime.PyDict_SetItemString( globals, "__builtins__", Runtime.PyEval_GetBuiltins() @@ -612,7 +631,7 @@ internal PyScope(PyScope parent, GILState state) : this(state) { this.state = state; - this.parent = parent; + this.Parent = parent; globals = Runtime.PyDict_New(); if (globals == IntPtr.Zero) { @@ -792,7 +811,7 @@ internal void SetGlobalVariable(string name, object value) AcquireLock(); using (var pyKey = new PyString(name)) { - IntPtr _value = GetInstHandle(value); + IntPtr _value = Converter.ToPython(value, value?.GetType()); int r = Runtime.PyObject_SetItem(globals, pyKey.obj, _value); if (r < 0) { @@ -833,7 +852,7 @@ public void SetVariable(string name, object value) AcquireLock(); using (var pyKey = new PyString(name)) { - IntPtr _value = GetInstHandle(value); + IntPtr _value = Converter.ToPython(value, value?.GetType()); int r = Runtime.PyObject_SetItem(locals, pyKey.obj, _value); if (r < 0) { @@ -919,21 +938,7 @@ public T GetVariable(string name) PyObject obj = GetVariable(name); return (T)obj.AsManagedObject(typeof(T)); } - - private static IntPtr GetInstHandle(object value) - { - if (value == null) - { - Runtime.XIncref(Runtime.PyNone); - return Runtime.PyNone; - } - else - { - IntPtr ptr = Converter.ToPython(value, value.GetType()); - return ptr; - } - } - + private void AcquireLock() { if (isDisposed) @@ -949,12 +954,10 @@ public virtual void Dispose() { return; } + AcquireLock(); Runtime.XDecref(globals); Runtime.XDecref(locals); - if (parent == null) - { - Py.RemoveSession(name); - } + Py.RemoveSession(this); isDisposed = true; } @@ -978,30 +981,43 @@ public static GILState GIL() private static PyScope.GILState gil = new PyScope.GILState(); + private static List Sessions = new List(); + /// /// Sessions should be cleared after shut down. /// Currently, the seperation of static methods into Py and PythonEngine makes the code ugly. /// - private static Dictionary Sessions = new Dictionary(); + private static Dictionary NamedSessions = new Dictionary(); - public static PyScope Session(string name) + public static PyScope Session(string name = null) { if (!PythonEngine.IsInitialized) { PythonEngine.Initialize(); } - if (Sessions.ContainsKey(name)) + if (name != null && NamedSessions.ContainsKey(name)) { - return Sessions[name]; + return NamedSessions[name]; } var session = new PyScope(name, gil); - Sessions[name] = session; + if(name != null) + { + NamedSessions[name] = session; + } + Sessions.Add(session); return session; } - internal static void RemoveSession(string name) + internal static void RemoveSession(PyScope scope) { - Sessions.Remove(name); + if(scope.Parent == null)//top session + { + Sessions.Remove(scope); + if(scope.Name != null) + { + NamedSessions.Remove(scope.Name); + } + } } public class GILState : IDisposable From ceaaef0333398dbe171876275d3e40613378e238 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Thu, 2 Mar 2017 09:36:27 +0800 Subject: [PATCH 08/20] fixup! create unnamed pyscope, make PyScope.GILState save --- src/runtime/pythonengine.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index f061e3b4f..3ce23373f 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -573,7 +573,6 @@ public void Dispose() return; } isDisposed = true; - AcquireLock(); ReleaseLock(); GC.SuppressFinalize(this); } @@ -957,7 +956,10 @@ public virtual void Dispose() AcquireLock(); Runtime.XDecref(globals); Runtime.XDecref(locals); - Py.RemoveSession(this); + if(this.Parent == null) + { + Py.RemoveSession(this); + } isDisposed = true; } @@ -1010,7 +1012,7 @@ public static PyScope Session(string name = null) internal static void RemoveSession(PyScope scope) { - if(scope.Parent == null)//top session + if(scope.Parent == null)//top scope { Sessions.Remove(scope); if(scope.Name != null) From dec39c74f5edc0e69ea1f4bf32eebc5304c31691 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Tue, 7 Mar 2017 11:58:24 +0800 Subject: [PATCH 09/20] remove GIL and rebased --- src/embed_tests/pyscope.cs | 154 +++++++++++------ src/runtime/pythonengine.cs | 325 ++++++++++++++++++------------------ 2 files changed, 266 insertions(+), 213 deletions(-) diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs index 2eb427847..ae6011a3e 100644 --- a/src/embed_tests/pyscope.cs +++ b/src/embed_tests/pyscope.cs @@ -11,13 +11,20 @@ public class PyScopeTest [SetUp] public void SetUp() { - ps = Py.Session("test"); + using (Py.GIL()) + { + ps = Py.Scope("test"); + } } [TearDown] public void Dispose() { - ps.Dispose(); + using (Py.GIL()) + { + ps.Dispose(); + ps = null; + } } /// @@ -26,9 +33,12 @@ public void Dispose() [Test] public void TestEval() { - ps.SetVariable("a", 1); - var result = ps.Eval("a + 2"); - Assert.AreEqual(result, 3); + using (Py.GIL()) + { + ps.SetVariable("a", 1); + var result = ps.Eval("a + 2"); + Assert.AreEqual(result, 3); + } } /// @@ -37,11 +47,14 @@ public void TestEval() [Test] public void TestExec() { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - ps.Exec("aa = bb + cc + 3"); - var result = ps.GetVariable("aa"); - Assert.AreEqual(result, 113); + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + ps.Exec("aa = bb + cc + 3"); + var result = ps.GetVariable("aa"); + Assert.AreEqual(result, 113); + } } /// @@ -51,11 +64,14 @@ public void TestExec() [Test] public void TestCompileExpression() { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - PyObject script = ps.Compile("bb + cc + 3", "", RunFlagType.Eval); - var result = ps.Execute(script); - Assert.AreEqual(result, 113); + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + PyObject script = ps.Compile("bb + cc + 3", "", RunFlagType.Eval); + var result = ps.Execute(script); + Assert.AreEqual(result, 113); + } } /// @@ -66,12 +82,15 @@ public void TestCompileExpression() [Test] public void TestCompileStatements() { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - PyObject script = ps.Compile("aa = bb + cc + 3", "", RunFlagType.File); - ps.Execute(script); - var result = ps.GetVariable("aa"); - Assert.AreEqual(result, 113); + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + PyObject script = ps.Compile("aa = bb + cc + 3", "", RunFlagType.File); + ps.Execute(script); + var result = ps.GetVariable("aa"); + Assert.AreEqual(result, 113); + } } /// @@ -80,16 +99,19 @@ public void TestCompileStatements() [Test] public void TestSubScope() { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable - PyScope scope = ps.SubScope(); - scope.Exec("aa = bb + cc + 3"); - var result = scope.GetVariable("aa"); - Assert.AreEqual(result, 113); // - scope.Dispose(); + PyScope scope = ps.SubScope(); + scope.Exec("aa = bb + cc + 3"); + var result = scope.GetVariable("aa"); + Assert.AreEqual(result, 113); // + scope.Dispose(); - Assert.IsFalse(ps.ContainsVariable("aa")); + Assert.IsFalse(ps.ContainsVariable("aa")); + } } /// @@ -99,14 +121,17 @@ public void TestSubScope() [Test] public void TestImport() { - dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.ContainsVariable("sys")); - - ps.Exec("sys.attr1 = 2"); - var value1 = ps.Eval("sys.attr1"); - var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); - Assert.AreEqual(value1, 2); - Assert.AreEqual(value2, 2); + using (Py.GIL()) + { + dynamic sys = ps.Import("sys"); + Assert.IsTrue(ps.ContainsVariable("sys")); + + ps.Exec("sys.attr1 = 2"); + var value1 = ps.Eval("sys.attr1"); + var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); + Assert.AreEqual(value1, 2); + Assert.AreEqual(value2, 2); + } } /// @@ -116,28 +141,59 @@ public void TestImport() [Test] public void TestImportAs() { - ps.ImportAs("sys", "sys1"); - Assert.IsTrue(ps.ContainsVariable("sys1")); + using (Py.GIL()) + { + ps.ImportAs("sys", "sys1"); + Assert.IsTrue(ps.ContainsVariable("sys1")); + } } /// /// Suspend the Session, and reuse it later. /// [Test] - public void TestSuspend() + public void TestThread() { - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); - ps.Suspend(); - + //I open an proposal here https://github.com/pythonnet/pythonnet/pull/419 + //after it merged, the BeginAllowThreads statement blow and the last EndAllowThreads statement + //should be removed. + var ts = PythonEngine.BeginAllowThreads(); using (Py.GIL()) { - PythonEngine.RunSimpleString("import sys;"); + ps.SetVariable("res", 0); + ps.SetVariable("bb", 100); + ps.SetVariable("th_cnt", 0); } - - ps.Exec("aa = bb + cc + 3"); - var result = ps.GetVariable("aa"); - Assert.AreEqual(result, 113); + int th_cnt = 3; + for (int i =0; i< th_cnt; i++) + { + System.Threading.Thread th = new System.Threading.Thread(()=> + { + using (Py.GIL()) + { + ps.Exec( + "res += bb + 1\n" + + "th_cnt += 1\n"); + } + }); + th.Start(); + } + //do not use Thread.Join to make this test more complicate + int cnt = 0; + while(cnt != th_cnt) + { + using (Py.GIL()) + { + cnt = ps.GetVariable("th_cnt"); + } + System.Threading.Thread.Sleep(10); + } + using (Py.GIL()) + { + var result = ps.GetVariable("res"); + Assert.AreEqual(101* th_cnt, result); + } + PythonEngine.EndAllowThreads(ts); } } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 3ce23373f..9b6f0afc1 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -294,13 +294,13 @@ public static void Shutdown() { if (initialized) { + Py.ClearScopes(); Marshal.FreeHGlobal(_pythonHome); _pythonHome = IntPtr.Zero; Marshal.FreeHGlobal(_programName); _programName = IntPtr.Zero; Marshal.FreeHGlobal(_pythonPath); _pythonPath = IntPtr.Zero; - Runtime.Shutdown(); initialized = false; } @@ -527,110 +527,32 @@ public enum RunFlagType Eval = 258 } - public class PySessionDisposedException : Exception + public class PyScopeException : Exception { - } - - public class PyScope : IDisposable - { - public class GILState : IDisposable + public PyScopeException(string message) + :base(message) { - private bool isGetGIL; - - private bool isDisposed; - - private IntPtr state; - - internal GILState() - { - isGetGIL = false; - isDisposed = false; - } - - public void AcquireLock() - { - if (isGetGIL) - { - return; - } - state = PythonEngine.AcquireLock(); - isGetGIL = true; - } - - public void ReleaseLock() - { - if (isGetGIL) - { - PythonEngine.ReleaseLock(state); - isGetGIL = false; - } - } - public void Dispose() - { - if (isDisposed) - { - return; - } - isDisposed = true; - ReleaseLock(); - GC.SuppressFinalize(this); - } - - ~GILState() - { - Dispose(); - } } + } - public string Name - { - get; - private set; - } + public interface IPyObject : IDisposable + { + } - public PyScope Parent + public class PyScope : IPyObject + { + public string Name { get; private set; } - - private GILState state; - + private bool isDisposed; - - private PyScope(GILState state) - { - isDisposed = false; - this.state = state; - globals = Runtime.PyDict_New(); - if (globals == IntPtr.Zero) - { - throw new PythonException(); - } - locals = Runtime.PyDict_New(); - if (locals == IntPtr.Zero) - { - throw new PythonException(); - } - } - - internal PyScope(string name, GILState state) - : this(state) + + internal PyScope(string name, PyScope origin) { - this.state = state; this.Name = name; - Runtime.PyDict_SetItemString( - globals, "__builtins__", - Runtime.PyEval_GetBuiltins() - ); - } - - internal PyScope(PyScope parent, GILState state) - : this(state) - { - this.state = state; - this.Parent = parent; globals = Runtime.PyDict_New(); if (globals == IntPtr.Zero) { @@ -641,38 +563,43 @@ internal PyScope(PyScope parent, GILState state) { throw new PythonException(); } - int result = Runtime.PyDict_Update(globals, parent.globals); - if (result < 0) + if(origin == null) { - throw new PythonException(); + Runtime.PyDict_SetItemString( + globals, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); } - result = Runtime.PyDict_Update(globals, parent.locals); - if (result < 0) + else { - throw new PythonException(); - } + int result = Runtime.PyDict_Update(globals, origin.globals); + if (result < 0) + { + throw new PythonException(); + } + result = Runtime.PyDict_Update(globals, origin.locals); + if (result < 0) + { + throw new PythonException(); + } + } } /// /// the dict for global variables /// - public IntPtr globals { get; private set; } + internal IntPtr globals { get; private set; } /// /// the dict for local variables /// - public IntPtr locals { get; private set; } + internal IntPtr locals { get; private set; } public PyScope SubScope() { - return new PyScope(this, state); + return new PyScope(null, this); } - - public void Suspend() - { - state.ReleaseLock(); - } - + /// /// Import Method /// @@ -694,7 +621,7 @@ public PyObject Import(string name) /// public PyObject ImportAs(string name, string asname) { - AcquireLock(); + Check(); PyObject module = PythonEngine.ImportModule(name); if (asname == null) { @@ -706,6 +633,7 @@ public PyObject ImportAs(string name, string asname) public PyObject Execute(PyObject script) { + Check(); IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, globals, locals); Runtime.CheckExceptionOccurred(); if (ptr == Runtime.PyNone) @@ -718,6 +646,7 @@ public PyObject Execute(PyObject script) public T Execute(PyObject script) { + Check(); PyObject pyObj = Execute(script); if (pyObj == null) { @@ -735,6 +664,7 @@ public T Execute(PyObject script) /// public PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) { + Check(); var flag = (IntPtr)mode; IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); Runtime.CheckExceptionOccurred(); @@ -750,7 +680,7 @@ public PyObject Compile(string code, string filename = "", RunFlagType mode = Ru /// public PyObject Eval(string code) { - AcquireLock(); + Check(); var flag = (IntPtr)Runtime.Py_eval_input; IntPtr ptr = Runtime.PyRun_String( code, flag, globals, locals @@ -767,6 +697,7 @@ public PyObject Eval(string code) /// public T Eval(string code) { + Check(); PyObject pyObj = Eval(code); var obj = (T)pyObj.AsManagedObject(typeof(T)); return obj; @@ -780,7 +711,7 @@ public T Eval(string code) /// public void Exec(string code) { - AcquireLock(); + Check(); Exec(code, globals, locals); } @@ -807,7 +738,7 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) /// internal void SetGlobalVariable(string name, object value) { - AcquireLock(); + Check(); using (var pyKey = new PyString(name)) { IntPtr _value = Converter.ToPython(value, value?.GetType()); @@ -828,7 +759,7 @@ internal void SetGlobalVariable(string name, object value) /// internal void RemoveGlobalVariable(string name) { - AcquireLock(); + Check(); using (var pyKey = new PyString(name)) { int r = Runtime.PyObject_DelItem(globals, pyKey.obj); @@ -848,7 +779,7 @@ internal void RemoveGlobalVariable(string name) /// public void SetVariable(string name, object value) { - AcquireLock(); + Check(); using (var pyKey = new PyString(name)) { IntPtr _value = Converter.ToPython(value, value?.GetType()); @@ -869,7 +800,7 @@ public void SetVariable(string name, object value) /// public void RemoveVariable(string name) { - AcquireLock(); + Check(); using (var pyKey = new PyString(name)) { int r = Runtime.PyObject_DelItem(locals, pyKey.obj); @@ -888,7 +819,7 @@ public void RemoveVariable(string name) /// public bool ContainsVariable(string name) { - AcquireLock(); + Check(); using (var pyKey = new PyString(name)) { if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) @@ -899,16 +830,8 @@ public bool ContainsVariable(string name) } } - /// - /// Get Method - /// - /// - /// Returns the value of the variable, local variable first. - /// If the variable is not exists, return null. - /// - public PyObject GetVariable(string name) + private PyObject _GetVariable(string name) { - AcquireLock(); using (var pyKey = new PyString(name)) { IntPtr op; @@ -922,7 +845,7 @@ public PyObject GetVariable(string name) } else { - return null; //name not exists + return null; } if (op == IntPtr.Zero) { @@ -932,35 +855,105 @@ public PyObject GetVariable(string name) } } + /// + /// GetVariable Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable is not exists, return null. + /// + public PyObject GetVariable(string name) + { + Check(); + var variable = _GetVariable(name); + if(variable == null) + { + throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); + } + if (variable.Handle == Runtime.PyNone) + { + variable.Dispose(); + return null; + } + return variable; + } + + /// + /// GetVariable Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable is not exists, return null. + /// + public bool TryGetVariable(string name, out PyObject value) + { + Check(); + var variable = _GetVariable(name); + if (variable == null) + { + value = null; + return false; + } + if (variable.Handle == Runtime.PyNone) + { + value = null; + return true; + } + value = variable; + return true; + } + public T GetVariable(string name) { + Check(); PyObject obj = GetVariable(name); + if (obj == null) + { + return default(T); + } return (T)obj.AsManagedObject(typeof(T)); } - - private void AcquireLock() + + public bool TryGetVariable(string name, out T value) + { + Check(); + PyObject obj; + var result = TryGetVariable(name, out obj); + if(!result) + { + value = default(T); + return false; + } + if(obj == null) + { + value = default(T); + return true; + } + value = (T)obj.AsManagedObject(typeof(T)); + return true; + } + + private void Check() { if (isDisposed) { - throw new PySessionDisposedException(); + throw new PyScopeException("'ScopeStorage' object has been disposed"); } - state.AcquireLock(); } - public virtual void Dispose() + public void Dispose() { if (isDisposed) { return; - } - AcquireLock(); + } + isDisposed = true; Runtime.XDecref(globals); Runtime.XDecref(locals); - if(this.Parent == null) + if(this.Name != null) { - Py.RemoveSession(this); - } - isDisposed = true; + Py.RemoveScope(this); + } } ~PyScope() @@ -980,45 +973,49 @@ public static GILState GIL() return new GILState(); } + + private static Dictionary NamedScopes = new Dictionary(); - private static PyScope.GILState gil = new PyScope.GILState(); - - private static List Sessions = new List(); + public static PyScope Scope() + { + return Scope(null, null); + } - /// - /// Sessions should be cleared after shut down. - /// Currently, the seperation of static methods into Py and PythonEngine makes the code ugly. - /// - private static Dictionary NamedSessions = new Dictionary(); + public static PyScope Scope(string name) + { + return Scope(name, null); + } - public static PyScope Session(string name = null) + public static PyScope Scope(string name, PyScope origin) { - if (!PythonEngine.IsInitialized) + if (name != null && NamedScopes.ContainsKey(name)) { - PythonEngine.Initialize(); + return NamedScopes[name]; } - if (name != null && NamedSessions.ContainsKey(name)) - { - return NamedSessions[name]; - } - var session = new PyScope(name, gil); + var scope = new PyScope(name, origin); if(name != null) { - NamedSessions[name] = session; - } - Sessions.Add(session); - return session; + NamedScopes[name] = scope; + } + return scope; } - internal static void RemoveSession(PyScope scope) + internal static void RemoveScope(PyScope scope) { - if(scope.Parent == null)//top scope + PyScope _scope; + NamedScopes.TryGetValue(scope.Name, out _scope); + if (_scope == scope) { - Sessions.Remove(scope); - if(scope.Name != null) - { - NamedSessions.Remove(scope.Name); - } + NamedScopes.Remove(scope.Name); + } + } + + internal static void ClearScopes() + { + var scopes = NamedScopes.Values.ToList(); + foreach (var scope in scopes) + { + scope.Dispose(); } } From e117d604b556f318fecb54c59bb5a935cc605ec6 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Tue, 7 Mar 2017 15:25:43 +0800 Subject: [PATCH 10/20] Add several methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add ImportScope: a scope can import variable from any scope, equivalent to python 'import * from mod' add dynamic member support add an OnDispose event remove the field ‘globals’ referring to python module put the scope class in a new file Unit test: TestThread uses scope function replacing Exec/Eval to speed up the execution. --- src/embed_tests/Python.EmbeddingTest.csproj | 6 +- src/embed_tests/TestPyScope.cs | 290 ++++++++++++ src/embed_tests/pyscope.cs | 199 -------- src/runtime/Python.Runtime.csproj | 8 +- src/runtime/pyscope.cs | 396 ++++++++++++++++ src/runtime/pythonengine.cs | 482 ++------------------ 6 files changed, 725 insertions(+), 656 deletions(-) create mode 100644 src/embed_tests/TestPyScope.cs delete mode 100644 src/embed_tests/pyscope.cs create mode 100644 src/runtime/pyscope.cs diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 1334d517c..7f3e5baf0 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,4 +1,4 @@ - + Debug @@ -98,7 +98,7 @@ - + @@ -118,4 +118,4 @@ - + \ No newline at end of file diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs new file mode 100644 index 000000000..20f50e57d --- /dev/null +++ b/src/embed_tests/TestPyScope.cs @@ -0,0 +1,290 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class PyScopeTest + { + private PyScope ps; + + [SetUp] + public void SetUp() + { + using (Py.GIL()) + { + ps = Py.CreateScope("test"); + } + } + + [TearDown] + public void Dispose() + { + using (Py.GIL()) + { + ps.Dispose(); + ps = null; + } + } + + /// + /// Eval a Python expression and obtain its return value. + /// + [Test] + public void TestEval() + { + using (Py.GIL()) + { + ps.SetVariable("a", 1); + var result = ps.Eval("a + 2"); + Assert.AreEqual(3, result); + } + } + + /// + /// Exec Python statements and obtain the variables created. + /// + [Test] + public void TestExec() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + ps.Exec("aa = bb + cc + 3"); + var result = ps.GetVariable("aa"); + Assert.AreEqual(113, result); + } + } + + /// + /// Compile an expression into an ast object; + /// Execute the ast and obtain its return value. + /// + [Test] + public void TestCompileExpression() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); + var result = ps.Execute(script); + Assert.AreEqual(113, result); + } + } + + /// + /// Compile Python statements into an ast object; + /// Execute the ast; + /// Obtain the local variables created. + /// + [Test] + public void TestCompileStatements() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); //declare a global variable + ps.SetVariable("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); + ps.Execute(script); + var result = ps.GetVariable("aa"); + Assert.AreEqual(113, result); + } + } + + /// + /// Create a function in the scope, then the function can read variables in the scope. + /// It cannot write the variables unless it uses the 'global' keyword. + /// + [Test] + public void TestScopeFunction() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); + ps.Exec( + "def func1():\n" + + " bb = cc + 10\n"); + dynamic func1 = ps.GetVariable("func1"); + func1(); //call the function, it can be called any times + var result = ps.GetVariable("bb"); + Assert.AreEqual(100, result); + + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); + ps.Exec( + "def func2():\n" + + " global bb\n" + + " bb = cc + 10\n"); + dynamic func2 = ps.GetVariable("func2"); + func2(); + result = ps.GetVariable("bb"); + Assert.AreEqual(20, result); + } + } + + /// + /// Import a python module into the session. + /// Equivalent to the Python "import" statement. + /// + [Test] + public void TestImportModule() + { + using (Py.GIL()) + { + dynamic sys = ps.ImportModule("sys"); + Assert.IsTrue(ps.ContainsVariable("sys")); + + ps.Exec("sys.attr1 = 2"); + var value1 = ps.Eval("sys.attr1"); + var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); + Assert.AreEqual(2, value1); + Assert.AreEqual(2, value2); + + //import as + ps.ImportModule("sys", "sys1"); + Assert.IsTrue(ps.ContainsVariable("sys1")); + } + } + + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportScope() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); + + PyScope scope = ps.CreateScope(); + + scope.Exec("aa = bb + cc + 3"); + var result = scope.GetVariable("aa"); + Assert.AreEqual(113, result); + + scope.Dispose(); + + Assert.IsFalse(ps.ContainsVariable("aa")); + } + } + + /// + /// Create a scope and import variables from a scope, + /// call the function imported. + /// + [Test] + public void TestImportScopeFunction() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); + ps.Exec( + "def func1():\n" + + " return cc + bb\n"); + + PyScope scope = ps.CreateScope(); + + //'func1' is imported from the origion scope + scope.Exec( + "def func2():\n" + + " return func1() - cc - bb\n"); + dynamic func2 = scope.GetVariable("func2"); + + var result1 = func2().AsManagedObject(typeof(int)); + Assert.AreEqual(0, result1); + + scope.SetVariable("cc", 20);//it has no effect on the globals of 'func1' + var result2 = func2().AsManagedObject(typeof(int)); + Assert.AreEqual(-10, result2); + scope.SetVariable("cc", 10); //rollback + + ps.SetVariable("cc", 20); + var result3 = func2().AsManagedObject(typeof(int)); + Assert.AreEqual(10, result3); + ps.SetVariable("cc", 10); //rollback + + scope.Dispose(); + } + } + + /// + /// Import a python module into the session with a new name. + /// Equivalent to the Python "import .. as .." statement. + /// + [Test] + public void TestImportScopeByName() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); + + var scope = Py.CreateScope(); + scope.ImportScope("test"); + + Assert.IsTrue(scope.ContainsVariable("bb")); + } + } + + /// + /// Share a pyscope by multiple threads. + /// + [Test] + public void TestThread() + { + //After the proposal here https://github.com/pythonnet/pythonnet/pull/419 complished, + //the BeginAllowThreads statement blow and the last EndAllowThreads statement + //should be removed. + dynamic _ps = ps; + var ts = PythonEngine.BeginAllowThreads(); + using (Py.GIL()) + { + _ps.res = 0; + _ps.bb = 100; + _ps.th_cnt = 0; + //add function to the scope + //can be call many times, more efficient than ast + ps.Exec( + "def update():\n" + + " global res, th_cnt\n" + + " res += bb + 1\n" + + " th_cnt += 1\n" + ); + } + int th_cnt = 3; + for (int i =0; i< th_cnt; i++) + { + System.Threading.Thread th = new System.Threading.Thread(()=> + { + using (Py.GIL()) + { + //ps.GetVariable("update")(); //call the scope function dynamicly + _ps.update(); + } + }); + th.Start(); + } + //equivalent to Thread.Join, make the main thread join the GIL competition + int cnt = 0; + while(cnt != th_cnt) + { + using (Py.GIL()) + { + cnt = ps.GetVariable("th_cnt"); + } + System.Threading.Thread.Sleep(10); + } + using (Py.GIL()) + { + var result = ps.GetVariable("res"); + Assert.AreEqual(101* th_cnt, result); + } + PythonEngine.EndAllowThreads(ts); + } + } +} diff --git a/src/embed_tests/pyscope.cs b/src/embed_tests/pyscope.cs deleted file mode 100644 index ae6011a3e..000000000 --- a/src/embed_tests/pyscope.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using NUnit.Framework; -using Python.Runtime; - -namespace Python.EmbeddingTest -{ - public class PyScopeTest - { - private PyScope ps; - - [SetUp] - public void SetUp() - { - using (Py.GIL()) - { - ps = Py.Scope("test"); - } - } - - [TearDown] - public void Dispose() - { - using (Py.GIL()) - { - ps.Dispose(); - ps = null; - } - } - - /// - /// Eval a Python expression and obtain its return value. - /// - [Test] - public void TestEval() - { - using (Py.GIL()) - { - ps.SetVariable("a", 1); - var result = ps.Eval("a + 2"); - Assert.AreEqual(result, 3); - } - } - - /// - /// Exec Python statements and obtain the local variables created. - /// - [Test] - public void TestExec() - { - using (Py.GIL()) - { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - ps.Exec("aa = bb + cc + 3"); - var result = ps.GetVariable("aa"); - Assert.AreEqual(result, 113); - } - } - - /// - /// Compile an expression into an ast object; - /// Execute the ast and obtain its return value. - /// - [Test] - public void TestCompileExpression() - { - using (Py.GIL()) - { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - PyObject script = ps.Compile("bb + cc + 3", "", RunFlagType.Eval); - var result = ps.Execute(script); - Assert.AreEqual(result, 113); - } - } - - /// - /// Compile Python statements into an ast object; - /// Execute the ast; - /// Obtain the local variables created. - /// - [Test] - public void TestCompileStatements() - { - using (Py.GIL()) - { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - PyObject script = ps.Compile("aa = bb + cc + 3", "", RunFlagType.File); - ps.Execute(script); - var result = ps.GetVariable("aa"); - Assert.AreEqual(result, 113); - } - } - - /// - /// Exec Python statements in a SubScope of the session then discard it. - /// - [Test] - public void TestSubScope() - { - using (Py.GIL()) - { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable - - PyScope scope = ps.SubScope(); - scope.Exec("aa = bb + cc + 3"); - var result = scope.GetVariable("aa"); - Assert.AreEqual(result, 113); // - scope.Dispose(); - - Assert.IsFalse(ps.ContainsVariable("aa")); - } - } - - /// - /// Import a python module into the session. - /// Equivalent to the Python "import" statement. - /// - [Test] - public void TestImport() - { - using (Py.GIL()) - { - dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.ContainsVariable("sys")); - - ps.Exec("sys.attr1 = 2"); - var value1 = ps.Eval("sys.attr1"); - var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); - Assert.AreEqual(value1, 2); - Assert.AreEqual(value2, 2); - } - } - - /// - /// Import a python module into the session with a new name. - /// Equivalent to the Python "import .. as .." statement. - /// - [Test] - public void TestImportAs() - { - using (Py.GIL()) - { - ps.ImportAs("sys", "sys1"); - Assert.IsTrue(ps.ContainsVariable("sys1")); - } - } - - /// - /// Suspend the Session, and reuse it later. - /// - [Test] - public void TestThread() - { - //I open an proposal here https://github.com/pythonnet/pythonnet/pull/419 - //after it merged, the BeginAllowThreads statement blow and the last EndAllowThreads statement - //should be removed. - var ts = PythonEngine.BeginAllowThreads(); - using (Py.GIL()) - { - ps.SetVariable("res", 0); - ps.SetVariable("bb", 100); - ps.SetVariable("th_cnt", 0); - } - int th_cnt = 3; - for (int i =0; i< th_cnt; i++) - { - System.Threading.Thread th = new System.Threading.Thread(()=> - { - using (Py.GIL()) - { - ps.Exec( - "res += bb + 1\n" + - "th_cnt += 1\n"); - } - }); - th.Start(); - } - //do not use Thread.Join to make this test more complicate - int cnt = 0; - while(cnt != th_cnt) - { - using (Py.GIL()) - { - cnt = ps.GetVariable("th_cnt"); - } - System.Threading.Thread.Sleep(10); - } - using (Py.GIL()) - { - var result = ps.GetVariable("res"); - Assert.AreEqual(101* th_cnt, result); - } - PythonEngine.EndAllowThreads(ts); - } - } -} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 8580b7f61..c2844d825 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -1,4 +1,4 @@ - + Debug @@ -70,6 +70,9 @@ false full + + TRACE;DEBUG;PYTHON3;PYTHON36;UCS2 + @@ -127,6 +130,7 @@ + @@ -163,4 +167,4 @@ - + \ No newline at end of file diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs new file mode 100644 index 000000000..3d9643cca --- /dev/null +++ b/src/runtime/pyscope.cs @@ -0,0 +1,396 @@ +using System; +using System.Dynamic; + +namespace Python.Runtime +{ + public class PyScopeException : Exception + { + public PyScopeException(string message) + : base(message) + { + + } + } + + public interface IPyObject : IDisposable + { + } + + public class PyScope : DynamicObject, IPyObject + { + public string Name + { + get; + private set; + } + + private bool isDisposed; + + internal PyScope(string name) + { + this.Name = name; + variables = Runtime.PyDict_New(); + if (variables == IntPtr.Zero) + { + throw new PythonException(); + } + Runtime.PyDict_SetItemString( + variables, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); + } + + /// + /// the dict for local variables + /// + internal IntPtr variables { get; private set; } + + public event Action OnDispose; + + public PyScope CreateScope() + { + var scope = new PyScope(null); + scope.ImportScope(this); + return scope; + } + + public void ImportScope(string name) + { + var scope = Py.GetScope(name); + ImportScope(scope); + } + + public void ImportScope(PyScope scope) + { + int result = Runtime.PyDict_Update(variables, scope.variables); + if (result < 0) + { + throw new PythonException(); + } + } + + /// + /// Import Method + /// + /// + /// The import .. as .. statement in Python. + /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// + public PyObject ImportModule(string name) + { + return ImportModule(name, name); + } + + /// + /// Import Method + /// + /// + /// The import .. as .. statement in Python. + /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// + public PyObject ImportModule(string name, string asname) + { + Check(); + PyObject module = PythonEngine.ImportModule(name); + if (asname == null) + { + asname = name; + } + SetVariable(asname, module); + return module; + } + + public PyObject Execute(PyObject script) + { + Check(); + IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, variables); + Runtime.CheckExceptionOccurred(); + if (ptr == Runtime.PyNone) + { + Runtime.XDecref(ptr); + return null; + } + return new PyObject(ptr); + } + + public T Execute(PyObject script) + { + Check(); + PyObject pyObj = Execute(script); + if (pyObj == null) + { + return default(T); + } + var obj = (T)ToManagedObject(pyObj); + return obj; + } + + public T ExecuteVariable(string name) + { + PyObject script = GetVariable(name); + return Execute(script); + } + + /// + /// Evaluate a Python expression + /// + /// + /// Evaluate a Python expression and return the result as a PyObject + /// or null if an exception is raised. + /// + public PyObject Eval(string code) + { + Check(); + var flag = (IntPtr)Runtime.Py_eval_input; + IntPtr ptr = Runtime.PyRun_String( + code, flag, variables, variables + ); + Runtime.CheckExceptionOccurred(); + return new PyObject(ptr); + } + + /// + /// Evaluate a Python expression + /// + /// + /// Evaluate a Python expression and convert the result to Managed Object. + /// + public T Eval(string code) + { + Check(); + PyObject pyObj = Eval(code); + var obj = (T)ToManagedObject(pyObj); + return obj; + } + + /// + /// Exec Method + /// + /// + /// Evaluate a Python script and save its local variables in the current local variable dict. + /// + public void Exec(string code) + { + Check(); + Exec(code, variables, variables); + } + + private void Exec(string code, IntPtr _globals, IntPtr _locals) + { + var flag = (IntPtr)Runtime.Py_file_input; + IntPtr ptr = Runtime.PyRun_String( + code, flag, _globals, _locals + ); + Runtime.CheckExceptionOccurred(); + if (ptr != Runtime.PyNone) + { + throw new PythonException(); + } + Runtime.XDecref(ptr); + } + + /// + /// SetLocal Method + /// + /// + /// Add a new variable to local variable dict if it not exists + /// or set the value of the local variable if it exists. + /// + public void SetVariable(string name, object value) + { + Check(); + using (var pyKey = new PyString(name)) + { + IntPtr _value = Converter.ToPython(value, value?.GetType()); + int r = Runtime.PyObject_SetItem(variables, pyKey.obj, _value); + if (r < 0) + { + throw new PythonException(); + } + Runtime.XDecref(_value); + } + } + + /// + /// DelLocal Method + /// + /// + /// Remove a variable from the local variable dict. + /// + public void RemoveVariable(string name) + { + Check(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_DelItem(variables, pyKey.obj); + if (r < 0) + { + throw new PythonException(); + } + } + } + + /// + /// Exists Method + /// + /// + /// Returns true if the variable appears in the local variable dict or the global variable dict. + /// + public bool ContainsVariable(string name) + { + Check(); + using (var pyKey = new PyString(name)) + { + return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; + } + } + + private PyObject _GetVariable(string name) + { + using (var pyKey = new PyString(name)) + { + IntPtr op; + if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) + { + op = Runtime.PyObject_GetItem(variables, pyKey.obj); + } + else + { + return null; + } + if (op == IntPtr.Zero) + { + throw new PythonException(); + } + return new PyObject(op); + } + } + + /// + /// GetVariable Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable is not exists, return null. + /// + public PyObject GetVariable(string name) + { + Check(); + var variable = _GetVariable(name); + if (variable == null) + { + throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); + } + if (variable.Handle == Runtime.PyNone) + { + variable.Dispose(); + return null; + } + return variable; + } + + /// + /// GetVariable Method + /// + /// + /// Returns the value of the variable, local variable first. + /// If the variable is not exists, return null. + /// + public bool TryGetVariable(string name, out PyObject value) + { + Check(); + var variable = _GetVariable(name); + if (variable == null) + { + value = null; + return false; + } + if (variable.Handle == Runtime.PyNone) + { + value = null; + return true; + } + value = variable; + return true; + } + + public T GetVariable(string name) + { + Check(); + PyObject pyObj = GetVariable(name); + if (pyObj == null) + { + return default(T); + } + return (T)ToManagedObject(pyObj); + } + + public bool TryGetVariable(string name, out T value) + { + Check(); + PyObject pyObj; + var result = TryGetVariable(name, out pyObj); + if (!result) + { + value = default(T); + return false; + } + if (pyObj == null) + { + value = default(T); + return true; + } + value = (T)ToManagedObject(pyObj); + return true; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = this.GetVariable(binder.Name); + return true; + } + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + this.SetVariable(binder.Name, value); + return true; + } + + // Currently, AsManagedObject method cannot accept 'dynamic' for the T parameter + private object ToManagedObject(PyObject pyObj) + { + if(typeof(T) == typeof(PyObject) || typeof(T) == typeof(object)) + { + return pyObj; + } + return pyObj.AsManagedObject(typeof(T)); + } + + private void Check() + { + if (isDisposed) + { + throw new PyScopeException("'ScopeStorage' object has been disposed"); + } + } + + public void Dispose() + { + if (isDisposed) + { + return; + } + isDisposed = true; + Runtime.XDecref(variables); + if (this.OnDispose != null) + { + this.OnDispose(this); + } + } + + ~PyScope() + { + Dispose(); + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 9b6f0afc1..25851e27d 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -301,6 +301,7 @@ public static void Shutdown() _programName = IntPtr.Zero; Marshal.FreeHGlobal(_pythonPath); _pythonPath = IntPtr.Zero; + Runtime.Shutdown(); initialized = false; } @@ -421,6 +422,13 @@ public static PyObject ModuleFromString(string name, string code) return new PyObject(m); } + public static PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) + { + var flag = (IntPtr)mode; + IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); + Runtime.CheckExceptionOccurred(); + return new PyObject(ptr); + } /// /// Eval Method @@ -527,441 +535,6 @@ public enum RunFlagType Eval = 258 } - public class PyScopeException : Exception - { - public PyScopeException(string message) - :base(message) - { - - } - } - - public interface IPyObject : IDisposable - { - } - - public class PyScope : IPyObject - { - public string Name - { - get; - private set; - } - - private bool isDisposed; - - internal PyScope(string name, PyScope origin) - { - this.Name = name; - globals = Runtime.PyDict_New(); - if (globals == IntPtr.Zero) - { - throw new PythonException(); - } - locals = Runtime.PyDict_New(); - if (locals == IntPtr.Zero) - { - throw new PythonException(); - } - if(origin == null) - { - Runtime.PyDict_SetItemString( - globals, "__builtins__", - Runtime.PyEval_GetBuiltins() - ); - } - else - { - int result = Runtime.PyDict_Update(globals, origin.globals); - if (result < 0) - { - throw new PythonException(); - } - result = Runtime.PyDict_Update(globals, origin.locals); - if (result < 0) - { - throw new PythonException(); - } - } - } - - /// - /// the dict for global variables - /// - internal IntPtr globals { get; private set; } - - /// - /// the dict for local variables - /// - internal IntPtr locals { get; private set; } - - public PyScope SubScope() - { - return new PyScope(null, this); - } - - /// - /// Import Method - /// - /// - /// The import .. as .. statement in Python. - /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. - /// - public PyObject Import(string name) - { - return ImportAs(name, name); - } - - /// - /// Import Method - /// - /// - /// The import .. as .. statement in Python. - /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. - /// - public PyObject ImportAs(string name, string asname) - { - Check(); - PyObject module = PythonEngine.ImportModule(name); - if (asname == null) - { - asname = name; - } - SetVariable(asname, module); - return module; - } - - public PyObject Execute(PyObject script) - { - Check(); - IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, globals, locals); - Runtime.CheckExceptionOccurred(); - if (ptr == Runtime.PyNone) - { - Runtime.XDecref(ptr); - return null; - } - return new PyObject(ptr); - } - - public T Execute(PyObject script) - { - Check(); - PyObject pyObj = Execute(script); - if (pyObj == null) - { - return default(T); - } - var obj = (T)pyObj.AsManagedObject(typeof(T)); - return obj; - } - - /// - /// Compile Method - /// - /// - /// Compile Python expression/statements into ast. - /// - public PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File) - { - Check(); - var flag = (IntPtr)mode; - IntPtr ptr = Runtime.Py_CompileString(code, filename, flag); - Runtime.CheckExceptionOccurred(); - return new PyObject(ptr); - } - - /// - /// Evaluate a Python expression - /// - /// - /// Evaluate a Python expression and return the result as a PyObject - /// or null if an exception is raised. - /// - public PyObject Eval(string code) - { - Check(); - var flag = (IntPtr)Runtime.Py_eval_input; - IntPtr ptr = Runtime.PyRun_String( - code, flag, globals, locals - ); - Runtime.CheckExceptionOccurred(); - return new PyObject(ptr); - } - - /// - /// Evaluate a Python expression - /// - /// - /// Evaluate a Python expression and convert the result to Managed Object. - /// - public T Eval(string code) - { - Check(); - PyObject pyObj = Eval(code); - var obj = (T)pyObj.AsManagedObject(typeof(T)); - return obj; - } - - /// - /// Exec Method - /// - /// - /// Evaluate a Python script and save its local variables in the current local variable dict. - /// - public void Exec(string code) - { - Check(); - Exec(code, globals, locals); - } - - private void Exec(string code, IntPtr _globals, IntPtr _locals) - { - var flag = (IntPtr)Runtime.Py_file_input; - IntPtr ptr = Runtime.PyRun_String( - code, flag, _globals, _locals - ); - Runtime.CheckExceptionOccurred(); - if (ptr != Runtime.PyNone) - { - throw new PythonException(); - } - Runtime.XDecref(ptr); - } - - /// - /// SetGlobalVariable Method - /// - /// - /// Add a new variable to global variable dict if it not exists - /// or set the value of the global variable if it exists. - /// - internal void SetGlobalVariable(string name, object value) - { - Check(); - using (var pyKey = new PyString(name)) - { - IntPtr _value = Converter.ToPython(value, value?.GetType()); - int r = Runtime.PyObject_SetItem(globals, pyKey.obj, _value); - if (r < 0) - { - throw new PythonException(); - } - Runtime.XDecref(_value); - } - } - - /// - /// RemoveGlobalVariable Method - /// - /// - /// Remove a variable from the global variable dict. - /// - internal void RemoveGlobalVariable(string name) - { - Check(); - using (var pyKey = new PyString(name)) - { - int r = Runtime.PyObject_DelItem(globals, pyKey.obj); - if (r < 0) - { - throw new PythonException(); - } - } - } - - /// - /// SetLocal Method - /// - /// - /// Add a new variable to local variable dict if it not exists - /// or set the value of the local variable if it exists. - /// - public void SetVariable(string name, object value) - { - Check(); - using (var pyKey = new PyString(name)) - { - IntPtr _value = Converter.ToPython(value, value?.GetType()); - int r = Runtime.PyObject_SetItem(locals, pyKey.obj, _value); - if (r < 0) - { - throw new PythonException(); - } - Runtime.XDecref(_value); - } - } - - /// - /// DelLocal Method - /// - /// - /// Remove a variable from the local variable dict. - /// - public void RemoveVariable(string name) - { - Check(); - using (var pyKey = new PyString(name)) - { - int r = Runtime.PyObject_DelItem(locals, pyKey.obj); - if (r < 0) - { - throw new PythonException(); - } - } - } - - /// - /// Exists Method - /// - /// - /// Returns true if the variable appears in the local variable dict or the global variable dict. - /// - public bool ContainsVariable(string name) - { - Check(); - using (var pyKey = new PyString(name)) - { - if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) - { - return true; - } - return Runtime.PyMapping_HasKey(globals, pyKey.obj) != 0; - } - } - - private PyObject _GetVariable(string name) - { - using (var pyKey = new PyString(name)) - { - IntPtr op; - if (Runtime.PyMapping_HasKey(locals, pyKey.obj) != 0) - { - op = Runtime.PyObject_GetItem(locals, pyKey.obj); - } - else if (Runtime.PyMapping_HasKey(globals, pyKey.obj) != 0) - { - op = Runtime.PyObject_GetItem(globals, pyKey.obj); - } - else - { - return null; - } - if (op == IntPtr.Zero) - { - throw new PythonException(); - } - return new PyObject(op); - } - } - - /// - /// GetVariable Method - /// - /// - /// Returns the value of the variable, local variable first. - /// If the variable is not exists, return null. - /// - public PyObject GetVariable(string name) - { - Check(); - var variable = _GetVariable(name); - if(variable == null) - { - throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); - } - if (variable.Handle == Runtime.PyNone) - { - variable.Dispose(); - return null; - } - return variable; - } - - /// - /// GetVariable Method - /// - /// - /// Returns the value of the variable, local variable first. - /// If the variable is not exists, return null. - /// - public bool TryGetVariable(string name, out PyObject value) - { - Check(); - var variable = _GetVariable(name); - if (variable == null) - { - value = null; - return false; - } - if (variable.Handle == Runtime.PyNone) - { - value = null; - return true; - } - value = variable; - return true; - } - - public T GetVariable(string name) - { - Check(); - PyObject obj = GetVariable(name); - if (obj == null) - { - return default(T); - } - return (T)obj.AsManagedObject(typeof(T)); - } - - public bool TryGetVariable(string name, out T value) - { - Check(); - PyObject obj; - var result = TryGetVariable(name, out obj); - if(!result) - { - value = default(T); - return false; - } - if(obj == null) - { - value = default(T); - return true; - } - value = (T)obj.AsManagedObject(typeof(T)); - return true; - } - - private void Check() - { - if (isDisposed) - { - throw new PyScopeException("'ScopeStorage' object has been disposed"); - } - } - - public void Dispose() - { - if (isDisposed) - { - return; - } - isDisposed = true; - Runtime.XDecref(globals); - Runtime.XDecref(locals); - if(this.Name != null) - { - Py.RemoveScope(this); - } - } - - ~PyScope() - { - Dispose(); - } - } - public static class Py { public static GILState GIL() @@ -976,38 +549,43 @@ public static GILState GIL() private static Dictionary NamedScopes = new Dictionary(); - public static PyScope Scope() - { - return Scope(null, null); - } - - public static PyScope Scope(string name) + public static PyScope CreateScope() { - return Scope(name, null); + return CreateScope(null); } - public static PyScope Scope(string name, PyScope origin) + public static PyScope CreateScope(string name) { if (name != null && NamedScopes.ContainsKey(name)) { - return NamedScopes[name]; + throw new PyScopeException(String.Format("ScopeStorage '{0}' has existed", name)); } - var scope = new PyScope(name, origin); - if(name != null) + var scope = new PyScope(name); + if (name != null) { NamedScopes[name] = scope; - } + scope.OnDispose += RemoveScope; + } return scope; } + + public static bool ContainsScope(string name) + { + return NamedScopes.ContainsKey(name); + } - internal static void RemoveScope(PyScope scope) + public static PyScope GetScope(string name) { - PyScope _scope; - NamedScopes.TryGetValue(scope.Name, out _scope); - if (_scope == scope) + if (name != null && NamedScopes.ContainsKey(name)) { - NamedScopes.Remove(scope.Name); + return NamedScopes[name]; } + throw new PyScopeException(String.Format("ScopeStorage '{0}' not exist", name)); + } + + internal static void RemoveScope(PyScope scope) + { + NamedScopes.Remove(scope.Name); } internal static void ClearScopes() From 904d9ed168a31c83438fdbf2f3838ef528bf71cb Mon Sep 17 00:00:00 2001 From: yag Date: Wed, 15 Mar 2017 00:24:43 +0800 Subject: [PATCH 11/20] add a Variables method --- src/embed_tests/TestPyScope.cs | 31 +++++++++++++++ src/runtime/pyscope.cs | 69 ++++++++++++++++++++++++---------- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 20f50e57d..f312985dd 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -125,6 +125,37 @@ public void TestScopeFunction() } } + /// + /// Create a class in the scope, the class can read variables in the scope. + /// Its methods can write the variables with the help of 'global' keyword. + /// + [Test] + public void TestScopeClass() + { + using (Py.GIL()) + { + dynamic _ps = ps; + _ps.bb = 100; + ps.Exec( + "class class1():\n" + + " def __init__(self, value):\n" + + " self.value = value\n" + + " def call(self, arg):\n" + + " return self.value + bb + arg\n" + //use scope variables + " def update(self, arg):\n" + + " global bb\n" + + " bb = self.value + arg\n" //update scope variable + ); + dynamic obj1 = _ps.class1(20); + var result = obj1.call(10).AsManagedObject(typeof(int)); + Assert.AreEqual(130, result); + + obj1.update(10); + result = ps.GetVariable("bb"); + Assert.AreEqual(30, result); + } + } + /// /// Import a python module into the session. /// Equivalent to the Python "import" statement. diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 3d9643cca..a9609a5e7 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -12,6 +12,9 @@ public PyScopeException(string message) } } + /// + /// Classes implement this interface must be used with GIL obtained. + /// public interface IPyObject : IDisposable { } @@ -38,6 +41,8 @@ internal PyScope(string name) variables, "__builtins__", Runtime.PyEval_GetBuiltins() ); + SetVariable("locals", (Func)Variables); + SetVariable("globals", (Func)Variables); } /// @@ -47,6 +52,11 @@ internal PyScope(string name) public event Action OnDispose; + public PyDict Variables() + { + return new PyDict(variables); + } + public PyScope CreateScope() { var scope = new PyScope(null); @@ -276,17 +286,26 @@ private PyObject _GetVariable(string name) public PyObject GetVariable(string name) { Check(); - var variable = _GetVariable(name); - if (variable == null) - { - throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); - } - if (variable.Handle == Runtime.PyNone) + using (var pyKey = new PyString(name)) { - variable.Dispose(); - return null; + if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) + { + IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj); + if (op == IntPtr.Zero) + { + throw new PythonException(); + } + if (op == Runtime.PyNone) + { + return null; + } + return new PyObject(op); + } + else + { + throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); + } } - return variable; } /// @@ -299,19 +318,29 @@ public PyObject GetVariable(string name) public bool TryGetVariable(string name, out PyObject value) { Check(); - var variable = _GetVariable(name); - if (variable == null) - { - value = null; - return false; - } - if (variable.Handle == Runtime.PyNone) + using (var pyKey = new PyString(name)) { - value = null; - return true; + if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) + { + IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj); + if (op == IntPtr.Zero) + { + throw new PythonException(); + } + if (op == Runtime.PyNone) + { + value = null; + return true; + } + value = new PyObject(op); + return true; + } + else + { + value = null; + return false; + } } - value = variable; - return true; } public T GetVariable(string name) From 5484451819e74bde7d7109523a8d88f09f2ca6a0 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Wed, 15 Mar 2017 13:46:37 +0800 Subject: [PATCH 12/20] fixup! add a Variables method --- src/runtime/pyscope.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index a9609a5e7..cadd5b1b9 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -41,6 +41,12 @@ internal PyScope(string name) variables, "__builtins__", Runtime.PyEval_GetBuiltins() ); + InitVariables(); + } + + //To compitable with the python module + private void InitVariables() + { SetVariable("locals", (Func)Variables); SetVariable("globals", (Func)Variables); } @@ -50,10 +56,19 @@ internal PyScope(string name) /// internal IntPtr variables { get; private set; } + public long Refcount + { + get + { + return Runtime.Refcount(variables); + } + } + public event Action OnDispose; public PyDict Variables() { + Runtime.XIncref(variables); return new PyDict(variables); } @@ -77,6 +92,7 @@ public void ImportScope(PyScope scope) { throw new PythonException(); } + InitVariables(); } /// From 2e063c2f4be8be09b41e7fecf1b80e799a5603ad Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Wed, 15 Mar 2017 13:57:22 +0800 Subject: [PATCH 13/20] remove private method _GetVariable --- src/runtime/pyscope.cs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index cadd5b1b9..9896b3373 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -270,28 +270,7 @@ public bool ContainsVariable(string name) return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; } } - - private PyObject _GetVariable(string name) - { - using (var pyKey = new PyString(name)) - { - IntPtr op; - if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) - { - op = Runtime.PyObject_GetItem(variables, pyKey.obj); - } - else - { - return null; - } - if (op == IntPtr.Zero) - { - throw new PythonException(); - } - return new PyObject(op); - } - } - + /// /// GetVariable Method /// From dd492f46f27c3f00dbc8e05e5507a095b866dece Mon Sep 17 00:00:00 2001 From: yag Date: Wed, 15 Mar 2017 19:02:25 +0800 Subject: [PATCH 14/20] add unit test for Variables() method --- src/embed_tests/TestPyScope.cs | 25 +++++++++++++++++ src/runtime/pyscope.cs | 49 +++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index f312985dd..b4fe1ad60 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -262,6 +262,31 @@ public void TestImportScopeByName() } } + /// + /// Use the locals() and globals() method just like in python module + /// + [Test] + public void TestVariables() + { + (ps.Variables() as dynamic)["ee"] = new PyInt(200); + var a0 = ps.GetVariable("ee"); + Assert.AreEqual(200, a0); + + ps.Exec("locals()['ee'] = 210"); + var a1 = ps.GetVariable("ee"); + Assert.AreEqual(210, a1); + + ps.Exec("globals()['ee'] = 220"); + var a2 = ps.GetVariable("ee"); + Assert.AreEqual(220, a2); + + var item = (ps as dynamic).locals(); + item["ee"] = new PyInt(230); + item.Dispose(); + var a3 = ps.GetVariable("ee"); + Assert.AreEqual(230, a3); + } + /// /// Share a pyscope by multiple threads. /// diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 9896b3373..7fc6f5057 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -44,7 +44,8 @@ internal PyScope(string name) InitVariables(); } - //To compitable with the python module + //To compitable with the python module. + //Enable locals() and glocals() usage in the python script. private void InitVariables() { SetVariable("locals", (Func)Variables); @@ -52,7 +53,7 @@ private void InitVariables() } /// - /// the dict for local variables + /// the dict for scope variables /// internal IntPtr variables { get; private set; } @@ -96,11 +97,11 @@ public void ImportScope(PyScope scope) } /// - /// Import Method + /// ImportModule Method /// /// /// The import .. as .. statement in Python. - /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// Import a module ,add it to the variables dict and return the resulting module object as a PyObject. /// public PyObject ImportModule(string name) { @@ -108,11 +109,11 @@ public PyObject ImportModule(string name) } /// - /// Import Method + /// ImportModule Method /// /// /// The import .. as .. statement in Python. - /// Import a module ,add it to the local variable dict and return the resulting module object as a PyObject. + /// Import a module ,add it to the variables dict and return the resulting module object as a PyObject. /// public PyObject ImportModule(string name, string asname) { @@ -126,6 +127,13 @@ public PyObject ImportModule(string name, string asname) return module; } + /// + /// Execute method + /// + /// + /// Execute a Python ast and return the result as a PyObject. + /// The ast can be either an expression or stmts. + /// public PyObject Execute(PyObject script) { Check(); @@ -156,9 +164,9 @@ public T ExecuteVariable(string name) PyObject script = GetVariable(name); return Execute(script); } - + /// - /// Evaluate a Python expression + /// Eval method /// /// /// Evaluate a Python expression and return the result as a PyObject @@ -193,7 +201,7 @@ public T Eval(string code) /// Exec Method /// /// - /// Evaluate a Python script and save its local variables in the current local variable dict. + /// Exec a Python script and save its local variables in the current local variable dict. /// public void Exec(string code) { @@ -214,13 +222,13 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) } Runtime.XDecref(ptr); } - + /// - /// SetLocal Method + /// SetVariable Method /// /// - /// Add a new variable to local variable dict if it not exists - /// or set the value of the local variable if it exists. + /// Add a new variable to the variables dict if it not exists + /// or update its value if the variable exists. /// public void SetVariable(string name, object value) { @@ -238,10 +246,10 @@ public void SetVariable(string name, object value) } /// - /// DelLocal Method + /// RemoveVariable Method /// /// - /// Remove a variable from the local variable dict. + /// Remove a variable from the variables dict. /// public void RemoveVariable(string name) { @@ -257,10 +265,10 @@ public void RemoveVariable(string name) } /// - /// Exists Method + /// ContainsVariable Method /// /// - /// Returns true if the variable appears in the local variable dict or the global variable dict. + /// Returns true if the variable exists in the variables dict. /// public bool ContainsVariable(string name) { @@ -270,13 +278,13 @@ public bool ContainsVariable(string name) return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; } } - + /// /// GetVariable Method /// /// /// Returns the value of the variable, local variable first. - /// If the variable is not exists, return null. + /// If the variable is not exists, throw an Exception. /// public PyObject GetVariable(string name) { @@ -304,7 +312,7 @@ public PyObject GetVariable(string name) } /// - /// GetVariable Method + /// TryGetVariable Method /// /// /// Returns the value of the variable, local variable first. @@ -380,7 +388,6 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } - // Currently, AsManagedObject method cannot accept 'dynamic' for the T parameter private object ToManagedObject(PyObject pyObj) { if(typeof(T) == typeof(PyObject) || typeof(T) == typeof(object)) From df6a49aced3f4040f97625c32f611da43695e041 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Fri, 7 Apr 2017 18:03:58 +0800 Subject: [PATCH 15/20] add several methods and rebased Add an optional dict parameter for Eval/Exec/Execute methods Add a new field obj which point to a Python Module (same as pyobject) Add a static method New Rename the old ImportScope method to ImportAllFromScope Add a new ImportScope method and add a unit test for it Rename the CreateScope method to NewScope --- src/embed_tests/TestPyScope.cs | 48 +++++++-- src/runtime/Python.Runtime.csproj | 5 +- src/runtime/pyobject.cs | 21 ++++ src/runtime/pyscope.cs | 162 ++++++++++++++++-------------- src/runtime/pythonengine.cs | 9 +- 5 files changed, 153 insertions(+), 92 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index b4fe1ad60..502f9e5c7 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -137,7 +137,7 @@ public void TestScopeClass() dynamic _ps = ps; _ps.bb = 100; ps.Exec( - "class class1():\n" + + "class Class1():\n" + " def __init__(self, value):\n" + " self.value = value\n" + " def call(self, arg):\n" + @@ -146,8 +146,8 @@ public void TestScopeClass() " global bb\n" + " bb = self.value + arg\n" //update scope variable ); - dynamic obj1 = _ps.class1(20); - var result = obj1.call(10).AsManagedObject(typeof(int)); + dynamic obj1 = _ps.Class1(20); + var result = obj1.call(10).As(); Assert.AreEqual(130, result); obj1.update(10); @@ -170,7 +170,7 @@ public void TestImportModule() ps.Exec("sys.attr1 = 2"); var value1 = ps.Eval("sys.attr1"); - var value2 = (int)sys.attr1.AsManagedObject(typeof(int)); + var value2 = (int)sys.attr1.As(); Assert.AreEqual(2, value1); Assert.AreEqual(2, value2); @@ -192,9 +192,10 @@ public void TestImportScope() ps.SetVariable("bb", 100); ps.SetVariable("cc", 10); - PyScope scope = ps.CreateScope(); + PyScope scope = Py.CreateScope(); + scope.ImportScope(ps, "ps"); - scope.Exec("aa = bb + cc + 3"); + scope.Exec("aa = ps.bb + ps.cc + 3"); var result = scope.GetVariable("aa"); Assert.AreEqual(113, result); @@ -204,6 +205,30 @@ public void TestImportScope() } } + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportAllFromScope() + { + using (Py.GIL()) + { + ps.SetVariable("bb", 100); + ps.SetVariable("cc", 10); + + PyScope scope = ps.NewScope(); + + scope.Exec("aa = bb + cc + 3"); + var result = scope.GetVariable("aa"); + Assert.AreEqual(113, result); + + scope.Dispose(); + + Assert.IsFalse(ps.ContainsVariable("aa")); + } + } + /// /// Create a scope and import variables from a scope, /// call the function imported. @@ -219,7 +244,7 @@ public void TestImportScopeFunction() "def func1():\n" + " return cc + bb\n"); - PyScope scope = ps.CreateScope(); + PyScope scope = ps.NewScope(); //'func1' is imported from the origion scope scope.Exec( @@ -227,16 +252,16 @@ public void TestImportScopeFunction() " return func1() - cc - bb\n"); dynamic func2 = scope.GetVariable("func2"); - var result1 = func2().AsManagedObject(typeof(int)); + var result1 = func2().As(); Assert.AreEqual(0, result1); scope.SetVariable("cc", 20);//it has no effect on the globals of 'func1' - var result2 = func2().AsManagedObject(typeof(int)); + var result2 = func2().As(); Assert.AreEqual(-10, result2); scope.SetVariable("cc", 10); //rollback ps.SetVariable("cc", 20); - var result3 = func2().AsManagedObject(typeof(int)); + var result3 = func2().As(); Assert.AreEqual(10, result3); ps.SetVariable("cc", 10); //rollback @@ -256,7 +281,8 @@ public void TestImportScopeByName() ps.SetVariable("bb", 100); var scope = Py.CreateScope(); - scope.ImportScope("test"); + scope.ImportAllFromScope("test"); + //scope.ImportModule("test"); Assert.IsTrue(scope.ContainsVariable("bb")); } diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index c2844d825..c68bfff5b 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -70,9 +70,6 @@ false full - - TRACE;DEBUG;PYTHON3;PYTHON36;UCS2 - @@ -167,4 +164,4 @@ - \ No newline at end of file + diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 47f413409..09b080a86 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -96,6 +96,27 @@ public object AsManagedObject(Type t) } return result; } + + /// + /// As Method + /// + /// + /// Return a managed object of the given type, based on the + /// value of the Python object. + /// + public T As() + { + if (typeof(T) == typeof(PyObject) || typeof(T) == typeof(object)) + { + return (T)(this as object); + } + object result; + if (!Converter.ToManaged(obj, typeof(T), out result, false)) + { + throw new InvalidCastException("cannot convert object to target type"); + } + return (T)result; + } /// diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 7fc6f5057..d5fd7e6d1 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Collections.Generic; using System.Dynamic; namespace Python.Runtime @@ -21,48 +23,45 @@ public interface IPyObject : IDisposable public class PyScope : DynamicObject, IPyObject { - public string Name - { - get; - private set; - } + public readonly string Name; - private bool isDisposed; + internal readonly IntPtr obj; + + /// + /// the dict for local variables + /// + internal readonly IntPtr variables; - internal PyScope(string name) + private bool isDisposed; + + internal static PyScope New(string name = null) { - this.Name = name; - variables = Runtime.PyDict_New(); - if (variables == IntPtr.Zero) + if (name == null) + { + name = ""; + } + var module = Runtime.PyModule_New(name); + if (module == IntPtr.Zero) { throw new PythonException(); } - Runtime.PyDict_SetItemString( - variables, "__builtins__", - Runtime.PyEval_GetBuiltins() - ); - InitVariables(); - } - - //To compitable with the python module. - //Enable locals() and glocals() usage in the python script. - private void InitVariables() - { - SetVariable("locals", (Func)Variables); - SetVariable("globals", (Func)Variables); + return new PyScope(module); } - /// - /// the dict for scope variables - /// - internal IntPtr variables { get; private set; } - - public long Refcount + private PyScope(IntPtr ptr) { - get + obj = ptr; + //Refcount of the variables not increase + variables = Runtime.PyModule_GetDict(obj); + if (variables == IntPtr.Zero) { - return Runtime.Refcount(variables); + throw new PythonException(); } + Runtime.PyDict_SetItemString( + variables, "__builtins__", + Runtime.PyEval_GetBuiltins() + ); + this.Name = this.GetVariable("__name__"); } public event Action OnDispose; @@ -73,27 +72,41 @@ public PyDict Variables() return new PyDict(variables); } - public PyScope CreateScope() + public PyScope NewScope() { - var scope = new PyScope(null); - scope.ImportScope(this); + var scope = PyScope.New(); + scope.ImportAllFromScope(this); return scope; } - public void ImportScope(string name) + public void ImportAllFromScope(string name) { var scope = Py.GetScope(name); - ImportScope(scope); + ImportAllFromScope(scope); } - public void ImportScope(PyScope scope) + public void ImportAllFromScope(PyScope scope) { int result = Runtime.PyDict_Update(variables, scope.variables); if (result < 0) { throw new PythonException(); } - InitVariables(); + } + + public void ImportScope(string name, string asname = null) + { + var scope = Py.GetScope(name); + if(asname == null) + { + asname = name; + } + ImportScope(scope, asname); + } + + public void ImportScope(PyScope scope, string asname) + { + this.SetVariable(asname, scope.obj); } /// @@ -134,10 +147,11 @@ public PyObject ImportModule(string name, string asname) /// Execute a Python ast and return the result as a PyObject. /// The ast can be either an expression or stmts. /// - public PyObject Execute(PyObject script) + public PyObject Execute(PyObject script, PyDict locals = null) { Check(); - IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, variables); + IntPtr _locals = locals == null ? variables : locals.obj; + IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, _locals); Runtime.CheckExceptionOccurred(); if (ptr == Runtime.PyNone) { @@ -147,24 +161,18 @@ public PyObject Execute(PyObject script) return new PyObject(ptr); } - public T Execute(PyObject script) + public T Execute(PyObject script, PyDict locals = null) { Check(); - PyObject pyObj = Execute(script); + PyObject pyObj = Execute(script, locals); if (pyObj == null) { return default(T); } - var obj = (T)ToManagedObject(pyObj); + var obj = pyObj.As(); return obj; } - public T ExecuteVariable(string name) - { - PyObject script = GetVariable(name); - return Execute(script); - } - /// /// Eval method /// @@ -172,12 +180,13 @@ public T ExecuteVariable(string name) /// Evaluate a Python expression and return the result as a PyObject /// or null if an exception is raised. /// - public PyObject Eval(string code) + public PyObject Eval(string code, PyDict locals = null) { Check(); + IntPtr _locals = locals == null ? variables : locals.obj; var flag = (IntPtr)Runtime.Py_eval_input; IntPtr ptr = Runtime.PyRun_String( - code, flag, variables, variables + code, flag, variables, _locals ); Runtime.CheckExceptionOccurred(); return new PyObject(ptr); @@ -189,11 +198,11 @@ public PyObject Eval(string code) /// /// Evaluate a Python expression and convert the result to Managed Object. /// - public T Eval(string code) + public T Eval(string code, PyDict locals = null) { Check(); - PyObject pyObj = Eval(code); - var obj = (T)ToManagedObject(pyObj); + PyObject pyObj = Eval(code, locals); + var obj = pyObj.As(); return obj; } @@ -203,10 +212,11 @@ public T Eval(string code) /// /// Exec a Python script and save its local variables in the current local variable dict. /// - public void Exec(string code) + public void Exec(string code, PyDict locals = null) { Check(); - Exec(code, variables, variables); + IntPtr _locals = locals == null ? variables : locals.obj; + Exec(code, variables, _locals); } private void Exec(string code, IntPtr _globals, IntPtr _locals) @@ -231,17 +241,31 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) /// or update its value if the variable exists. /// public void SetVariable(string name, object value) + { + IntPtr _value = Converter.ToPython(value, value?.GetType()); + SetVariable(name, _value); + Runtime.XDecref(_value); + } + + private void SetVariable(string name, IntPtr value) { Check(); using (var pyKey = new PyString(name)) { - IntPtr _value = Converter.ToPython(value, value?.GetType()); - int r = Runtime.PyObject_SetItem(variables, pyKey.obj, _value); + int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); if (r < 0) { throw new PythonException(); } - Runtime.XDecref(_value); + } + } + + public void AddVariables(PyDict dict) + { + int result = Runtime.PyDict_Update(variables, dict.obj); + if (result < 0) + { + throw new PythonException(); } } @@ -354,7 +378,7 @@ public T GetVariable(string name) { return default(T); } - return (T)ToManagedObject(pyObj); + return pyObj.As(); } public bool TryGetVariable(string name, out T value) @@ -372,7 +396,7 @@ public bool TryGetVariable(string name, out T value) value = default(T); return true; } - value = (T)ToManagedObject(pyObj); + value = pyObj.As(); return true; } @@ -388,15 +412,6 @@ public override bool TrySetMember(SetMemberBinder binder, object value) return true; } - private object ToManagedObject(PyObject pyObj) - { - if(typeof(T) == typeof(PyObject) || typeof(T) == typeof(object)) - { - return pyObj; - } - return pyObj.AsManagedObject(typeof(T)); - } - private void Check() { if (isDisposed) @@ -412,11 +427,8 @@ public void Dispose() return; } isDisposed = true; - Runtime.XDecref(variables); - if (this.OnDispose != null) - { - this.OnDispose(this); - } + Runtime.XDecref(obj); + this.OnDispose?.Invoke(this); } ~PyScope() diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 25851e27d..e24281838 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -551,16 +551,21 @@ public static GILState GIL() public static PyScope CreateScope() { - return CreateScope(null); + var scope = PyScope.New(); + return scope; } public static PyScope CreateScope(string name) { + if (String.IsNullOrEmpty(name)) + { + throw new PyScopeException("Name of ScopeStorage must not be empty"); + } if (name != null && NamedScopes.ContainsKey(name)) { throw new PyScopeException(String.Format("ScopeStorage '{0}' has existed", name)); } - var scope = new PyScope(name); + var scope = PyScope.New(name); if (name != null) { NamedScopes[name] = scope; From f35d75ba8e4e68ef1ca289e7ed58c020e919c149 Mon Sep 17 00:00:00 2001 From: ywg16-pc Date: Mon, 24 Apr 2017 13:11:50 +0800 Subject: [PATCH 16/20] add a new class PyScopeManager --- src/embed_tests/TestPyScope.cs | 2 +- src/runtime/pyscope.cs | 77 ++++++++++++++++++++++++++++++---- src/runtime/pythonengine.cs | 49 ++-------------------- src/runtime/runtime.cs | 4 +- 4 files changed, 77 insertions(+), 55 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 502f9e5c7..050c5ef71 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -306,7 +306,7 @@ public void TestVariables() var a2 = ps.GetVariable("ee"); Assert.AreEqual(220, a2); - var item = (ps as dynamic).locals(); + var item = ps.Variables(); item["ee"] = new PyInt(230); item.Dispose(); var a3 = ps.GetVariable("ee"); diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index d5fd7e6d1..26946e907 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -15,13 +15,14 @@ public PyScopeException(string message) } /// - /// Classes implement this interface must be used with GIL obtained. + /// Classes/methods have this attribute must be used with GIL obtained. /// - public interface IPyObject : IDisposable + public class PyGILAttribute : Attribute { } - public class PyScope : DynamicObject, IPyObject + [PyGIL] + public class PyScope : DynamicObject, IDisposable { public readonly string Name; @@ -33,6 +34,10 @@ public class PyScope : DynamicObject, IPyObject internal readonly IntPtr variables; private bool isDisposed; + + internal PyScopeManager Manager; + + public event Action OnDispose; internal static PyScope New(string name = null) { @@ -64,8 +69,6 @@ private PyScope(IntPtr ptr) this.Name = this.GetVariable("__name__"); } - public event Action OnDispose; - public PyDict Variables() { Runtime.XIncref(variables); @@ -81,7 +84,7 @@ public PyScope NewScope() public void ImportAllFromScope(string name) { - var scope = Py.GetScope(name); + var scope = Manager.Get(name); ImportAllFromScope(scope); } @@ -96,7 +99,7 @@ public void ImportAllFromScope(PyScope scope) public void ImportScope(string name, string asname = null) { - var scope = Py.GetScope(name); + var scope = Manager.Get(name); if(asname == null) { asname = name; @@ -436,4 +439,64 @@ public void Dispose() Dispose(); } } + + public class PyScopeManager + { + private Dictionary NamedScopes = new Dictionary(); + + [PyGIL] + public PyScope Create() + { + var scope = PyScope.New(); + scope.Manager = this; + return scope; + } + + [PyGIL] + public PyScope Create(string name) + { + if (String.IsNullOrEmpty(name)) + { + throw new PyScopeException("Name of ScopeStorage must not be empty"); + } + if (name != null && NamedScopes.ContainsKey(name)) + { + throw new PyScopeException(String.Format("ScopeStorage '{0}' has existed", name)); + } + var scope = PyScope.New(name); + scope.Manager = this; + scope.OnDispose += Remove; + NamedScopes[name] = scope; + return scope; + } + + public bool Contains(string name) + { + return NamedScopes.ContainsKey(name); + } + + public PyScope Get(string name) + { + if (name != null && NamedScopes.ContainsKey(name)) + { + return NamedScopes[name]; + } + throw new PyScopeException(String.Format("ScopeStorage '{0}' not exist", name)); + } + + public void Remove(PyScope scope) + { + NamedScopes.Remove(scope.Name); + } + + [PyGIL] + public void Clear() + { + var scopes = NamedScopes.Values.ToList(); + foreach (var scope in scopes) + { + scope.Dispose(); + } + } + } } diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index e24281838..0a51be1bc 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -294,7 +294,7 @@ public static void Shutdown() { if (initialized) { - Py.ClearScopes(); + Py.PyScopeManager.Clear(); Marshal.FreeHGlobal(_pythonHome); _pythonHome = IntPtr.Zero; Marshal.FreeHGlobal(_programName); @@ -547,61 +547,20 @@ public static GILState GIL() return new GILState(); } - private static Dictionary NamedScopes = new Dictionary(); + internal static PyScopeManager PyScopeManager = new PyScopeManager(); public static PyScope CreateScope() { - var scope = PyScope.New(); + var scope = PyScopeManager.Create(); return scope; } public static PyScope CreateScope(string name) { - if (String.IsNullOrEmpty(name)) - { - throw new PyScopeException("Name of ScopeStorage must not be empty"); - } - if (name != null && NamedScopes.ContainsKey(name)) - { - throw new PyScopeException(String.Format("ScopeStorage '{0}' has existed", name)); - } - var scope = PyScope.New(name); - if (name != null) - { - NamedScopes[name] = scope; - scope.OnDispose += RemoveScope; - } + var scope = PyScopeManager.Create(name); return scope; } - public static bool ContainsScope(string name) - { - return NamedScopes.ContainsKey(name); - } - - public static PyScope GetScope(string name) - { - if (name != null && NamedScopes.ContainsKey(name)) - { - return NamedScopes[name]; - } - throw new PyScopeException(String.Format("ScopeStorage '{0}' not exist", name)); - } - - internal static void RemoveScope(PyScope scope) - { - NamedScopes.Remove(scope.Name); - } - - internal static void ClearScopes() - { - var scopes = NamedScopes.Values.ToList(); - foreach (var scope in scopes) - { - scope.Dispose(); - } - } - public class GILState : IDisposable { private IntPtr state; diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 59a02e6b6..a24b6f6d4 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -707,10 +707,10 @@ public static extern int Py_Main( [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals); - [DllImport(PythonDll)] + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok); [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] From 497d7aabebd8719ca9bd226f878eb0640fa89cb3 Mon Sep 17 00:00:00 2001 From: yag Date: Fri, 28 Apr 2017 22:18:23 +0800 Subject: [PATCH 17/20] cleaned up the Import methods --- src/embed_tests/TestPyScope.cs | 8 +- src/runtime/pyscope.cs | 167 ++++++++++++++++++++------------- src/runtime/pythonengine.cs | 8 +- 3 files changed, 107 insertions(+), 76 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 050c5ef71..865c64771 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -165,7 +165,7 @@ public void TestImportModule() { using (Py.GIL()) { - dynamic sys = ps.ImportModule("sys"); + dynamic sys = ps.Import("sys"); Assert.IsTrue(ps.ContainsVariable("sys")); ps.Exec("sys.attr1 = 2"); @@ -175,7 +175,7 @@ public void TestImportModule() Assert.AreEqual(2, value2); //import as - ps.ImportModule("sys", "sys1"); + ps.Import("sys", "sys1"); Assert.IsTrue(ps.ContainsVariable("sys1")); } } @@ -193,7 +193,7 @@ public void TestImportScope() ps.SetVariable("cc", 10); PyScope scope = Py.CreateScope(); - scope.ImportScope(ps, "ps"); + scope.Import(ps, "ps"); scope.Exec("aa = ps.bb + ps.cc + 3"); var result = scope.GetVariable("aa"); @@ -281,7 +281,7 @@ public void TestImportScopeByName() ps.SetVariable("bb", 100); var scope = Py.CreateScope(); - scope.ImportAllFromScope("test"); + scope.ImportAll("test"); //scope.ImportModule("test"); Assert.IsTrue(scope.ContainsVariable("bb")); diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 26946e907..898147144 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -35,26 +35,21 @@ public class PyScope : DynamicObject, IDisposable private bool isDisposed; - internal PyScopeManager Manager; + internal readonly PyScopeManager Manager; public event Action OnDispose; - internal static PyScope New(string name = null) + public PyScope(IntPtr ptr, PyScopeManager manager) { - if (name == null) + if (Runtime.PyObject_Type(ptr) != Runtime.PyModuleType) { - name = ""; + throw new PyScopeException("object is not a module"); } - var module = Runtime.PyModule_New(name); - if (module == IntPtr.Zero) + if(manager == null) { - throw new PythonException(); + manager = PyScopeManager.Global; } - return new PyScope(module); - } - - private PyScope(IntPtr ptr) - { + Manager = manager; obj = ptr; //Refcount of the variables not increase variables = Runtime.PyModule_GetDict(obj); @@ -77,70 +72,92 @@ public PyDict Variables() public PyScope NewScope() { - var scope = PyScope.New(); - scope.ImportAllFromScope(this); + var scope = Manager.Create(); + scope.ImportAll(this); return scope; } - public void ImportAllFromScope(string name) - { - var scope = Manager.Get(name); - ImportAllFromScope(scope); - } - - public void ImportAllFromScope(PyScope scope) + public dynamic Import(string name, string asname = null) { - int result = Runtime.PyDict_Update(variables, scope.variables); - if (result < 0) + Check(); + if (asname == null) { - throw new PythonException(); + asname = name; } - } - - public void ImportScope(string name, string asname = null) - { - var scope = Manager.Get(name); - if(asname == null) + var scope = Manager.TryGet(name); + if(scope != null) { - asname = name; + Import(scope, asname); + return scope; } - ImportScope(scope, asname); + PyObject module = PythonEngine.ImportModule(name); + Import(module, asname); + return module; } - public void ImportScope(PyScope scope, string asname) + public void Import(PyScope scope, string asname) { this.SetVariable(asname, scope.obj); } /// - /// ImportModule Method + /// Import Method /// /// /// The import .. as .. statement in Python. - /// Import a module ,add it to the variables dict and return the resulting module object as a PyObject. + /// Import a module,add it to the variables dict and return the resulting module object as a PyObject. /// - public PyObject ImportModule(string name) + public void Import(PyObject module, string asname = null) { - return ImportModule(name, name); + if (asname == null) + { + asname = module.GetAttr("__name__").ToString(); + } + SetVariable(asname, module); } - /// - /// ImportModule Method - /// - /// - /// The import .. as .. statement in Python. - /// Import a module ,add it to the variables dict and return the resulting module object as a PyObject. - /// - public PyObject ImportModule(string name, string asname) + public void ImportAll(string name) { - Check(); + var scope = Manager.TryGet(name); + if(scope != null) + { + ImportAll(scope); + return; + } PyObject module = PythonEngine.ImportModule(name); - if (asname == null) + ImportAll(module); + } + + public void ImportAll(PyScope scope) + { + int result = Runtime.PyDict_Update(variables, scope.variables); + if (result < 0) { - asname = name; + throw new PythonException(); + } + } + + public void ImportAll(PyObject module) + { + if (Runtime.PyObject_Type(module.obj) != Runtime.PyModuleType) + { + throw new PyScopeException("object is not a module"); + } + var module_dict = Runtime.PyModule_GetDict(module.obj); + int result = Runtime.PyDict_Update(variables, module_dict); + if (result < 0) + { + throw new PythonException(); + } + } + + public void ImportAll(PyDict dict) + { + int result = Runtime.PyDict_Update(variables, dict.obj); + if (result < 0) + { + throw new PythonException(); } - SetVariable(asname, module); - return module; } /// @@ -263,15 +280,6 @@ private void SetVariable(string name, IntPtr value) } } - public void AddVariables(PyDict dict) - { - int result = Runtime.PyDict_Update(variables, dict.obj); - if (result < 0) - { - throw new PythonException(); - } - } - /// /// RemoveVariable Method /// @@ -442,13 +450,28 @@ public void Dispose() public class PyScopeManager { + public readonly static PyScopeManager Global = new PyScopeManager(); + private Dictionary NamedScopes = new Dictionary(); + internal PyScope NewScope(string name) + { + if (name == null) + { + name = ""; + } + var module = Runtime.PyModule_New(name); + if (module == IntPtr.Zero) + { + throw new PythonException(); + } + return new PyScope(module, this); + } + [PyGIL] public PyScope Create() { - var scope = PyScope.New(); - scope.Manager = this; + var scope = this.NewScope(null); return scope; } @@ -457,14 +480,13 @@ public PyScope Create(string name) { if (String.IsNullOrEmpty(name)) { - throw new PyScopeException("Name of ScopeStorage must not be empty"); + throw new ArgumentNullException(nameof(name)); } if (name != null && NamedScopes.ContainsKey(name)) { - throw new PyScopeException(String.Format("ScopeStorage '{0}' has existed", name)); + throw new PyScopeException($"PyScope '{name}' has existed"); } - var scope = PyScope.New(name); - scope.Manager = this; + var scope = this.NewScope(name); scope.OnDispose += Remove; NamedScopes[name] = scope; return scope; @@ -477,11 +499,22 @@ public bool Contains(string name) public PyScope Get(string name) { - if (name != null && NamedScopes.ContainsKey(name)) + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + if (NamedScopes.ContainsKey(name)) { return NamedScopes[name]; } - throw new PyScopeException(String.Format("ScopeStorage '{0}' not exist", name)); + throw new PyScopeException($"PyScope '{name}' not exist"); + } + + public PyScope TryGet(string name) + { + PyScope value; + NamedScopes.TryGetValue(name, out value); + return value; } public void Remove(PyScope scope) diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index 0a51be1bc..26af6a683 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -294,7 +294,7 @@ public static void Shutdown() { if (initialized) { - Py.PyScopeManager.Clear(); + PyScopeManager.Global.Clear(); Marshal.FreeHGlobal(_pythonHome); _pythonHome = IntPtr.Zero; Marshal.FreeHGlobal(_programName); @@ -546,18 +546,16 @@ public static GILState GIL() return new GILState(); } - - internal static PyScopeManager PyScopeManager = new PyScopeManager(); public static PyScope CreateScope() { - var scope = PyScopeManager.Create(); + var scope = PyScopeManager.Global.Create(); return scope; } public static PyScope CreateScope(string name) { - var scope = PyScopeManager.Create(name); + var scope = PyScopeManager.Global.Create(name); return scope; } From 2c3db3d6eec31f9f88127fbf641652266825fa1f Mon Sep 17 00:00:00 2001 From: yag Date: Thu, 1 Jun 2017 00:41:05 +0800 Subject: [PATCH 18/20] updated according to filmor's comments --- CHANGELOG.md | 4 -- src/embed_tests/TestPyScope.cs | 88 +++++++++++++++++----------------- src/runtime/pyscope.cs | 86 ++++++++++++++++----------------- 3 files changed, 87 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f43692b..6c245b17b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,10 +61,6 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Deprecated `RunString` (#401) -### Deprecated - -- Deprecated `RunString` (#401) - ### Fixed - Fixed crash during Initialization (#262)(#343) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index 865c64771..ae211af6c 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -192,14 +192,13 @@ public void TestImportScope() ps.SetVariable("bb", 100); ps.SetVariable("cc", 10); - PyScope scope = Py.CreateScope(); - scope.Import(ps, "ps"); - - scope.Exec("aa = ps.bb + ps.cc + 3"); - var result = scope.GetVariable("aa"); - Assert.AreEqual(113, result); - - scope.Dispose(); + using (var scope = Py.CreateScope()) + { + scope.Import(ps, "ps"); + scope.Exec("aa = ps.bb + ps.cc + 3"); + var result = scope.GetVariable("aa"); + Assert.AreEqual(113, result); + } Assert.IsFalse(ps.ContainsVariable("aa")); } @@ -217,13 +216,12 @@ public void TestImportAllFromScope() ps.SetVariable("bb", 100); ps.SetVariable("cc", 10); - PyScope scope = ps.NewScope(); - - scope.Exec("aa = bb + cc + 3"); - var result = scope.GetVariable("aa"); - Assert.AreEqual(113, result); - - scope.Dispose(); + using (var scope = ps.NewScope()) + { + scope.Exec("aa = bb + cc + 3"); + var result = scope.GetVariable("aa"); + Assert.AreEqual(113, result); + } Assert.IsFalse(ps.ContainsVariable("aa")); } @@ -244,28 +242,27 @@ public void TestImportScopeFunction() "def func1():\n" + " return cc + bb\n"); - PyScope scope = ps.NewScope(); - - //'func1' is imported from the origion scope - scope.Exec( - "def func2():\n" + - " return func1() - cc - bb\n"); - dynamic func2 = scope.GetVariable("func2"); - - var result1 = func2().As(); - Assert.AreEqual(0, result1); - - scope.SetVariable("cc", 20);//it has no effect on the globals of 'func1' - var result2 = func2().As(); - Assert.AreEqual(-10, result2); - scope.SetVariable("cc", 10); //rollback - - ps.SetVariable("cc", 20); - var result3 = func2().As(); - Assert.AreEqual(10, result3); - ps.SetVariable("cc", 10); //rollback - - scope.Dispose(); + using (PyScope scope = ps.NewScope()) + { + //'func1' is imported from the origion scope + scope.Exec( + "def func2():\n" + + " return func1() - cc - bb\n"); + dynamic func2 = scope.GetVariable("func2"); + + var result1 = func2().As(); + Assert.AreEqual(0, result1); + + scope.SetVariable("cc", 20);//it has no effect on the globals of 'func1' + var result2 = func2().As(); + Assert.AreEqual(-10, result2); + scope.SetVariable("cc", 10); //rollback + + ps.SetVariable("cc", 20); + var result3 = func2().As(); + Assert.AreEqual(10, result3); + ps.SetVariable("cc", 10); //rollback + } } } @@ -280,11 +277,13 @@ public void TestImportScopeByName() { ps.SetVariable("bb", 100); - var scope = Py.CreateScope(); - scope.ImportAll("test"); - //scope.ImportModule("test"); + using (var scope = Py.CreateScope()) + { + scope.ImportAll("test"); + //scope.ImportModule("test"); - Assert.IsTrue(scope.ContainsVariable("bb")); + Assert.IsTrue(scope.ContainsVariable("bb")); + } } } @@ -306,9 +305,10 @@ public void TestVariables() var a2 = ps.GetVariable("ee"); Assert.AreEqual(220, a2); - var item = ps.Variables(); - item["ee"] = new PyInt(230); - item.Dispose(); + using (var item = ps.Variables()) + { + item["ee"] = new PyInt(230); + } var a3 = ps.GetVariable("ee"); Assert.AreEqual(230, a3); } diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 898147144..e06a13310 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -38,14 +38,14 @@ public class PyScope : DynamicObject, IDisposable internal readonly PyScopeManager Manager; public event Action OnDispose; - - public PyScope(IntPtr ptr, PyScopeManager manager) + + internal PyScope(IntPtr ptr, PyScopeManager manager) { if (Runtime.PyObject_Type(ptr) != Runtime.PyModuleType) { throw new PyScopeException("object is not a module"); } - if(manager == null) + if (manager == null) { manager = PyScopeManager.Global; } @@ -80,19 +80,23 @@ public PyScope NewScope() public dynamic Import(string name, string asname = null) { Check(); - if (asname == null) + if (String.IsNullOrEmpty(asname)) { asname = name; } - var scope = Manager.TryGet(name); - if(scope != null) + PyScope scope; + Manager.TryGet(name, out scope); + if (scope != null) { Import(scope, asname); return scope; } - PyObject module = PythonEngine.ImportModule(name); - Import(module, asname); - return module; + else + { + PyObject module = PythonEngine.ImportModule(name); + Import(module, asname); + return module; + } } public void Import(PyScope scope, string asname) @@ -109,7 +113,7 @@ public void Import(PyScope scope, string asname) /// public void Import(PyObject module, string asname = null) { - if (asname == null) + if (String.IsNullOrEmpty(asname)) { asname = module.GetAttr("__name__").ToString(); } @@ -118,14 +122,18 @@ public void Import(PyObject module, string asname = null) public void ImportAll(string name) { - var scope = Manager.TryGet(name); - if(scope != null) + PyScope scope; + Manager.TryGet(name, out scope); + if (scope != null) { ImportAll(scope); return; } - PyObject module = PythonEngine.ImportModule(name); - ImportAll(module); + else + { + PyObject module = PythonEngine.ImportModule(name); + ImportAll(module); + } } public void ImportAll(PyScope scope) @@ -323,27 +331,13 @@ public bool ContainsVariable(string name) /// public PyObject GetVariable(string name) { - Check(); - using (var pyKey = new PyString(name)) + PyObject scope; + var state = TryGetVariable(name, out scope); + if(!state) { - if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0) - { - IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj); - if (op == IntPtr.Zero) - { - throw new PythonException(); - } - if (op == Runtime.PyNone) - { - return null; - } - return new PyObject(op); - } - else - { - throw new PyScopeException(String.Format("'ScopeStorage' object has no attribute '{0}'", name)); - } + throw new PyScopeException($"The scope of name '{Name}' has no attribute '{name}'"); } + return scope; } /// @@ -367,6 +361,7 @@ public bool TryGetVariable(string name, out PyObject value) } if (op == Runtime.PyNone) { + Runtime.XDecref(op); value = null; return true; } @@ -401,11 +396,18 @@ public bool TryGetVariable(string name, out T value) { value = default(T); return false; - } + } if (pyObj == null) { - value = default(T); - return true; + if(typeof(T).IsValueType) + { + throw new PyScopeException($"The value of the attribute '{name}' is None which cannot be convert to '{typeof(T).ToString()}'"); + } + else + { + value = default(T); + return true; + } } value = pyObj.As(); return true; @@ -427,7 +429,7 @@ private void Check() { if (isDisposed) { - throw new PyScopeException("'ScopeStorage' object has been disposed"); + throw new PyScopeException($"The scope of name '{Name}' object has been disposed"); } } @@ -484,7 +486,7 @@ public PyScope Create(string name) } if (name != null && NamedScopes.ContainsKey(name)) { - throw new PyScopeException($"PyScope '{name}' has existed"); + throw new PyScopeException($"A scope of name '{name}' does already exist"); } var scope = this.NewScope(name); scope.OnDispose += Remove; @@ -507,14 +509,12 @@ public PyScope Get(string name) { return NamedScopes[name]; } - throw new PyScopeException($"PyScope '{name}' not exist"); + throw new PyScopeException($"There is no scope named '{name}' registered in this manager"); } - public PyScope TryGet(string name) + public bool TryGet(string name, out PyScope scope) { - PyScope value; - NamedScopes.TryGetValue(name, out value); - return value; + return NamedScopes.TryGetValue(name, out scope); } public void Remove(PyScope scope) From 30543ebd17712abed126a5eac88abe525f2c75a0 Mon Sep 17 00:00:00 2001 From: yag Date: Thu, 1 Jun 2017 01:10:16 +0800 Subject: [PATCH 19/20] fixup! updated according to filmor's comments --- src/runtime/pyscope.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index e06a13310..527e09822 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -45,11 +45,7 @@ internal PyScope(IntPtr ptr, PyScopeManager manager) { throw new PyScopeException("object is not a module"); } - if (manager == null) - { - manager = PyScopeManager.Global; - } - Manager = manager; + Manager = manager ?? PyScopeManager.Global; obj = ptr; //Refcount of the variables not increase variables = Runtime.PyModule_GetDict(obj); From b9dcdacfd9014d5ad30048d1a3910423372c4c43 Mon Sep 17 00:00:00 2001 From: yag Date: Thu, 1 Jun 2017 09:00:31 +0800 Subject: [PATCH 20/20] Get/Set Methods renamed --- src/embed_tests/TestPyScope.cs | 88 +++++++-------- src/runtime/pyscope.cs | 200 ++++++++++++++++++++++++++------- 2 files changed, 206 insertions(+), 82 deletions(-) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs index ae211af6c..49c15a3a1 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/TestPyScope.cs @@ -35,7 +35,7 @@ public void TestEval() { using (Py.GIL()) { - ps.SetVariable("a", 1); + ps.Set("a", 1); var result = ps.Eval("a + 2"); Assert.AreEqual(3, result); } @@ -49,10 +49,10 @@ public void TestExec() { using (Py.GIL()) { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable ps.Exec("aa = bb + cc + 3"); - var result = ps.GetVariable("aa"); + var result = ps.Get("aa"); Assert.AreEqual(113, result); } } @@ -66,8 +66,8 @@ public void TestCompileExpression() { using (Py.GIL()) { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); var result = ps.Execute(script); Assert.AreEqual(113, result); @@ -84,11 +84,11 @@ public void TestCompileStatements() { using (Py.GIL()) { - ps.SetVariable("bb", 100); //declare a global variable - ps.SetVariable("cc", 10); //declare a local variable + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); ps.Execute(script); - var result = ps.GetVariable("aa"); + var result = ps.Get("aa"); Assert.AreEqual(113, result); } } @@ -102,25 +102,25 @@ public void TestScopeFunction() { using (Py.GIL()) { - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); + ps.Set("bb", 100); + ps.Set("cc", 10); ps.Exec( "def func1():\n" + " bb = cc + 10\n"); - dynamic func1 = ps.GetVariable("func1"); + dynamic func1 = ps.Get("func1"); func1(); //call the function, it can be called any times - var result = ps.GetVariable("bb"); + var result = ps.Get("bb"); Assert.AreEqual(100, result); - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); + ps.Set("bb", 100); + ps.Set("cc", 10); ps.Exec( "def func2():\n" + " global bb\n" + " bb = cc + 10\n"); - dynamic func2 = ps.GetVariable("func2"); + dynamic func2 = ps.Get("func2"); func2(); - result = ps.GetVariable("bb"); + result = ps.Get("bb"); Assert.AreEqual(20, result); } } @@ -151,7 +151,7 @@ public void TestScopeClass() Assert.AreEqual(130, result); obj1.update(10); - result = ps.GetVariable("bb"); + result = ps.Get("bb"); Assert.AreEqual(30, result); } } @@ -166,17 +166,17 @@ public void TestImportModule() using (Py.GIL()) { dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.ContainsVariable("sys")); + Assert.IsTrue(ps.Contains("sys")); ps.Exec("sys.attr1 = 2"); var value1 = ps.Eval("sys.attr1"); - var value2 = (int)sys.attr1.As(); + var value2 = sys.attr1.As(); Assert.AreEqual(2, value1); Assert.AreEqual(2, value2); //import as ps.Import("sys", "sys1"); - Assert.IsTrue(ps.ContainsVariable("sys1")); + Assert.IsTrue(ps.Contains("sys1")); } } @@ -189,18 +189,18 @@ public void TestImportScope() { using (Py.GIL()) { - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); + ps.Set("bb", 100); + ps.Set("cc", 10); using (var scope = Py.CreateScope()) { scope.Import(ps, "ps"); scope.Exec("aa = ps.bb + ps.cc + 3"); - var result = scope.GetVariable("aa"); + var result = scope.Get("aa"); Assert.AreEqual(113, result); } - Assert.IsFalse(ps.ContainsVariable("aa")); + Assert.IsFalse(ps.Contains("aa")); } } @@ -213,17 +213,17 @@ public void TestImportAllFromScope() { using (Py.GIL()) { - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); + ps.Set("bb", 100); + ps.Set("cc", 10); using (var scope = ps.NewScope()) { scope.Exec("aa = bb + cc + 3"); - var result = scope.GetVariable("aa"); + var result = scope.Get("aa"); Assert.AreEqual(113, result); } - Assert.IsFalse(ps.ContainsVariable("aa")); + Assert.IsFalse(ps.Contains("aa")); } } @@ -236,8 +236,8 @@ public void TestImportScopeFunction() { using (Py.GIL()) { - ps.SetVariable("bb", 100); - ps.SetVariable("cc", 10); + ps.Set("bb", 100); + ps.Set("cc", 10); ps.Exec( "def func1():\n" + " return cc + bb\n"); @@ -248,20 +248,20 @@ public void TestImportScopeFunction() scope.Exec( "def func2():\n" + " return func1() - cc - bb\n"); - dynamic func2 = scope.GetVariable("func2"); + dynamic func2 = scope.Get("func2"); var result1 = func2().As(); Assert.AreEqual(0, result1); - scope.SetVariable("cc", 20);//it has no effect on the globals of 'func1' + scope.Set("cc", 20);//it has no effect on the globals of 'func1' var result2 = func2().As(); Assert.AreEqual(-10, result2); - scope.SetVariable("cc", 10); //rollback + scope.Set("cc", 10); //rollback - ps.SetVariable("cc", 20); + ps.Set("cc", 20); var result3 = func2().As(); Assert.AreEqual(10, result3); - ps.SetVariable("cc", 10); //rollback + ps.Set("cc", 10); //rollback } } } @@ -275,14 +275,14 @@ public void TestImportScopeByName() { using (Py.GIL()) { - ps.SetVariable("bb", 100); + ps.Set("bb", 100); using (var scope = Py.CreateScope()) { scope.ImportAll("test"); //scope.ImportModule("test"); - Assert.IsTrue(scope.ContainsVariable("bb")); + Assert.IsTrue(scope.Contains("bb")); } } } @@ -294,22 +294,22 @@ public void TestImportScopeByName() public void TestVariables() { (ps.Variables() as dynamic)["ee"] = new PyInt(200); - var a0 = ps.GetVariable("ee"); + var a0 = ps.Get("ee"); Assert.AreEqual(200, a0); ps.Exec("locals()['ee'] = 210"); - var a1 = ps.GetVariable("ee"); + var a1 = ps.Get("ee"); Assert.AreEqual(210, a1); ps.Exec("globals()['ee'] = 220"); - var a2 = ps.GetVariable("ee"); + var a2 = ps.Get("ee"); Assert.AreEqual(220, a2); using (var item = ps.Variables()) { item["ee"] = new PyInt(230); } - var a3 = ps.GetVariable("ee"); + var a3 = ps.Get("ee"); Assert.AreEqual(230, a3); } @@ -357,13 +357,13 @@ public void TestThread() { using (Py.GIL()) { - cnt = ps.GetVariable("th_cnt"); + cnt = ps.Get("th_cnt"); } System.Threading.Thread.Sleep(10); } using (Py.GIL()) { - var result = ps.GetVariable("res"); + var result = ps.Get("res"); Assert.AreEqual(101* th_cnt, result); } PythonEngine.EndAllowThreads(ts); diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs index 527e09822..1991772dc 100644 --- a/src/runtime/pyscope.cs +++ b/src/runtime/pyscope.cs @@ -26,19 +26,35 @@ public class PyScope : DynamicObject, IDisposable { public readonly string Name; + /// + /// the python Module object the scope associated with. + /// internal readonly IntPtr obj; /// - /// the dict for local variables + /// the variable dict of the scope. /// internal readonly IntPtr variables; - private bool isDisposed; + private bool _isDisposed; + /// + /// The Manager this scope associated with. + /// It provides scopes this scope can import. + /// internal readonly PyScopeManager Manager; + /// + /// event which will be triggered after the scope disposed. + /// public event Action OnDispose; + /// + /// Constructor + /// + /// + /// Create a scope based on a Python Module. + /// internal PyScope(IntPtr ptr, PyScopeManager manager) { if (Runtime.PyObject_Type(ptr) != Runtime.PyModuleType) @@ -57,15 +73,23 @@ internal PyScope(IntPtr ptr, PyScopeManager manager) variables, "__builtins__", Runtime.PyEval_GetBuiltins() ); - this.Name = this.GetVariable("__name__"); + this.Name = this.Get("__name__"); } + /// + /// return the variable dict of the scope. + /// + /// public PyDict Variables() { Runtime.XIncref(variables); return new PyDict(variables); } + /// + /// Create a scope, and import all from this scope + /// + /// public PyScope NewScope() { var scope = Manager.Create(); @@ -73,6 +97,13 @@ public PyScope NewScope() return scope; } + /// + /// Import method + /// + /// + /// Import a scope or a module of given name, + /// scope will be looked up first. + /// public dynamic Import(string name, string asname = null) { Check(); @@ -95,27 +126,40 @@ public dynamic Import(string name, string asname = null) } } + /// + /// Import method + /// + /// + /// Import a scope as a variable of given name. + /// public void Import(PyScope scope, string asname) { - this.SetVariable(asname, scope.obj); + this.Set(asname, scope.obj); } /// /// Import Method /// /// - /// The import .. as .. statement in Python. - /// Import a module,add it to the variables dict and return the resulting module object as a PyObject. + /// The 'import .. as ..' statement in Python. + /// Import a module as a variable into the scope. /// public void Import(PyObject module, string asname = null) { if (String.IsNullOrEmpty(asname)) { - asname = module.GetAttr("__name__").ToString(); + asname = module.GetAttr("__name__").As(); } - SetVariable(asname, module); + Set(asname, module); } + /// + /// ImportAll Method + /// + /// + /// The 'import * from ..' statement in Python. + /// Import all content of a scope/module of given name into the scope, scope will be looked up first. + /// public void ImportAll(string name) { PyScope scope; @@ -132,6 +176,12 @@ public void ImportAll(string name) } } + /// + /// ImportAll Method + /// + /// + /// Import all variables of the scope into this scope. + /// public void ImportAll(PyScope scope) { int result = Runtime.PyDict_Update(variables, scope.variables); @@ -141,6 +191,12 @@ public void ImportAll(PyScope scope) } } + /// + /// ImportAll Method + /// + /// + /// Import all variables of the module into this scope. + /// public void ImportAll(PyObject module) { if (Runtime.PyObject_Type(module.obj) != Runtime.PyModuleType) @@ -155,6 +211,12 @@ public void ImportAll(PyObject module) } } + /// + /// ImportAll Method + /// + /// + /// Import all variables in the dictionary into this scope. + /// public void ImportAll(PyDict dict) { int result = Runtime.PyDict_Update(variables, dict.obj); @@ -185,6 +247,14 @@ public PyObject Execute(PyObject script, PyDict locals = null) return new PyObject(ptr); } + /// + /// Execute method + /// + /// + /// Execute a Python ast and return the result as a PyObject, + /// and convert the result to a Managed Object of given type. + /// The ast can be either an expression or stmts. + /// public T Execute(PyObject script, PyDict locals = null) { Check(); @@ -220,7 +290,8 @@ public PyObject Eval(string code, PyDict locals = null) /// Evaluate a Python expression /// /// - /// Evaluate a Python expression and convert the result to Managed Object. + /// Evaluate a Python expression + /// and convert the result to a Managed Object of given type. /// public T Eval(string code, PyDict locals = null) { @@ -258,20 +329,20 @@ private void Exec(string code, IntPtr _globals, IntPtr _locals) } /// - /// SetVariable Method + /// Set Variable Method /// /// - /// Add a new variable to the variables dict if it not exists + /// Add a new variable to the variables dict if it not exist /// or update its value if the variable exists. /// - public void SetVariable(string name, object value) + public void Set(string name, object value) { IntPtr _value = Converter.ToPython(value, value?.GetType()); - SetVariable(name, _value); + Set(name, _value); Runtime.XDecref(_value); } - private void SetVariable(string name, IntPtr value) + private void Set(string name, IntPtr value) { Check(); using (var pyKey = new PyString(name)) @@ -285,12 +356,12 @@ private void SetVariable(string name, IntPtr value) } /// - /// RemoveVariable Method + /// Remove Method /// /// /// Remove a variable from the variables dict. /// - public void RemoveVariable(string name) + public void Remove(string name) { Check(); using (var pyKey = new PyString(name)) @@ -304,12 +375,12 @@ public void RemoveVariable(string name) } /// - /// ContainsVariable Method + /// Contains Method /// /// - /// Returns true if the variable exists in the variables dict. + /// Returns true if the variable exists in the scope. /// - public bool ContainsVariable(string name) + public bool Contains(string name) { Check(); using (var pyKey = new PyString(name)) @@ -319,16 +390,16 @@ public bool ContainsVariable(string name) } /// - /// GetVariable Method + /// Get Method /// /// - /// Returns the value of the variable, local variable first. - /// If the variable is not exists, throw an Exception. + /// Returns the value of the variable of given name. + /// If the variable does not exist, throw an Exception. /// - public PyObject GetVariable(string name) + public PyObject Get(string name) { PyObject scope; - var state = TryGetVariable(name, out scope); + var state = TryGet(name, out scope); if(!state) { throw new PyScopeException($"The scope of name '{Name}' has no attribute '{name}'"); @@ -337,13 +408,13 @@ public PyObject GetVariable(string name) } /// - /// TryGetVariable Method + /// TryGet Method /// /// /// Returns the value of the variable, local variable first. - /// If the variable is not exists, return null. + /// If the variable does not exist, return null. /// - public bool TryGetVariable(string name, out PyObject value) + public bool TryGet(string name, out PyObject value) { Check(); using (var pyKey = new PyString(name)) @@ -372,10 +443,18 @@ public bool TryGetVariable(string name, out PyObject value) } } - public T GetVariable(string name) + /// + /// Get Method + /// + /// + /// Obtain the value of the variable of given name, + /// and convert the result to a Managed Object of given type. + /// If the variable does not exist, throw an Exception. + /// + public T Get(string name) { Check(); - PyObject pyObj = GetVariable(name); + PyObject pyObj = Get(name); if (pyObj == null) { return default(T); @@ -383,11 +462,19 @@ public T GetVariable(string name) return pyObj.As(); } - public bool TryGetVariable(string name, out T value) + /// + /// TryGet Method + /// + /// + /// Obtain the value of the variable of given name, + /// and convert the result to a Managed Object of given type. + /// If the variable does not exist, return false. + /// + public bool TryGet(string name, out T value) { Check(); PyObject pyObj; - var result = TryGetVariable(name, out pyObj); + var result = TryGet(name, out pyObj); if (!result) { value = default(T); @@ -411,19 +498,19 @@ public bool TryGetVariable(string name, out T value) public override bool TryGetMember(GetMemberBinder binder, out object result) { - result = this.GetVariable(binder.Name); + result = this.Get(binder.Name); return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { - this.SetVariable(binder.Name, value); + this.Set(binder.Name, value); return true; } private void Check() { - if (isDisposed) + if (_isDisposed) { throw new PyScopeException($"The scope of name '{Name}' object has been disposed"); } @@ -431,11 +518,11 @@ private void Check() public void Dispose() { - if (isDisposed) + if (_isDisposed) { return; } - isDisposed = true; + _isDisposed = true; Runtime.XDecref(obj); this.OnDispose?.Invoke(this); } @@ -466,6 +553,12 @@ internal PyScope NewScope(string name) return new PyScope(module, this); } + /// + /// Create Method + /// + /// + /// Create an anonymous scope. + /// [PyGIL] public PyScope Create() { @@ -473,6 +566,12 @@ public PyScope Create() return scope; } + /// + /// Create Method + /// + /// + /// Create an named scope of given name. + /// [PyGIL] public PyScope Create(string name) { @@ -480,7 +579,7 @@ public PyScope Create(string name) { throw new ArgumentNullException(nameof(name)); } - if (name != null && NamedScopes.ContainsKey(name)) + if (name != null && Contains(name)) { throw new PyScopeException($"A scope of name '{name}' does already exist"); } @@ -490,14 +589,27 @@ public PyScope Create(string name) return scope; } + /// + /// Contains Method + /// + /// + /// return true if the scope exists in this manager. + /// public bool Contains(string name) { return NamedScopes.ContainsKey(name); } + /// + /// Get Method + /// + /// + /// Find the scope in this manager. + /// If the scope not exist, an Exception will be thrown. + /// public PyScope Get(string name) { - if (name == null) + if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } @@ -508,11 +620,23 @@ public PyScope Get(string name) throw new PyScopeException($"There is no scope named '{name}' registered in this manager"); } + /// + /// Get Method + /// + /// + /// Try to find the scope in this manager. + /// public bool TryGet(string name, out PyScope scope) { return NamedScopes.TryGetValue(name, out scope); } + /// + /// Remove Method + /// + /// + /// remove the scope from this manager. + /// public void Remove(PyScope scope) { NamedScopes.Remove(scope.Name);