diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj
index 2edf4f515..fc887d815 100644
--- a/src/embed_tests/Python.EmbeddingTest.csproj
+++ b/src/embed_tests/Python.EmbeddingTest.csproj
@@ -100,6 +100,7 @@
+
diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/TestPyScope.cs
new file mode 100644
index 000000000..49c15a3a1
--- /dev/null
+++ b/src/embed_tests/TestPyScope.cs
@@ -0,0 +1,372 @@
+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.Set("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.Set("bb", 100); //declare a global variable
+ ps.Set("cc", 10); //declare a local variable
+ ps.Exec("aa = bb + cc + 3");
+ var result = ps.Get("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.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);
+ }
+ }
+
+ ///
+ /// Compile Python statements into an ast object;
+ /// Execute the ast;
+ /// Obtain the local variables created.
+ ///
+ [Test]
+ public void TestCompileStatements()
+ {
+ using (Py.GIL())
+ {
+ 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.Get("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.Set("bb", 100);
+ ps.Set("cc", 10);
+ ps.Exec(
+ "def func1():\n" +
+ " bb = cc + 10\n");
+ dynamic func1 = ps.Get("func1");
+ func1(); //call the function, it can be called any times
+ var result = ps.Get("bb");
+ Assert.AreEqual(100, result);
+
+ ps.Set("bb", 100);
+ ps.Set("cc", 10);
+ ps.Exec(
+ "def func2():\n" +
+ " global bb\n" +
+ " bb = cc + 10\n");
+ dynamic func2 = ps.Get("func2");
+ func2();
+ result = ps.Get("bb");
+ Assert.AreEqual(20, result);
+ }
+ }
+
+ ///
+ /// 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).As();
+ Assert.AreEqual(130, result);
+
+ obj1.update(10);
+ result = ps.Get("bb");
+ Assert.AreEqual(30, result);
+ }
+ }
+
+ ///
+ /// Import a python module into the session.
+ /// Equivalent to the Python "import" statement.
+ ///
+ [Test]
+ public void TestImportModule()
+ {
+ using (Py.GIL())
+ {
+ dynamic sys = ps.Import("sys");
+ Assert.IsTrue(ps.Contains("sys"));
+
+ ps.Exec("sys.attr1 = 2");
+ var value1 = ps.Eval("sys.attr1");
+ var value2 = sys.attr1.As();
+ Assert.AreEqual(2, value1);
+ Assert.AreEqual(2, value2);
+
+ //import as
+ ps.Import("sys", "sys1");
+ Assert.IsTrue(ps.Contains("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.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.Get("aa");
+ Assert.AreEqual(113, result);
+ }
+
+ Assert.IsFalse(ps.Contains("aa"));
+ }
+ }
+
+ ///
+ /// 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.Set("bb", 100);
+ ps.Set("cc", 10);
+
+ using (var scope = ps.NewScope())
+ {
+ scope.Exec("aa = bb + cc + 3");
+ var result = scope.Get("aa");
+ Assert.AreEqual(113, result);
+ }
+
+ Assert.IsFalse(ps.Contains("aa"));
+ }
+ }
+
+ ///
+ /// Create a scope and import variables from a scope,
+ /// call the function imported.
+ ///
+ [Test]
+ public void TestImportScopeFunction()
+ {
+ using (Py.GIL())
+ {
+ ps.Set("bb", 100);
+ ps.Set("cc", 10);
+ ps.Exec(
+ "def func1():\n" +
+ " return cc + bb\n");
+
+ 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.Get("func2");
+
+ var result1 = func2().As();
+ Assert.AreEqual(0, result1);
+
+ scope.Set("cc", 20);//it has no effect on the globals of 'func1'
+ var result2 = func2().As();
+ Assert.AreEqual(-10, result2);
+ scope.Set("cc", 10); //rollback
+
+ ps.Set("cc", 20);
+ var result3 = func2().As();
+ Assert.AreEqual(10, result3);
+ ps.Set("cc", 10); //rollback
+ }
+ }
+ }
+
+ ///
+ /// 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.Set("bb", 100);
+
+ using (var scope = Py.CreateScope())
+ {
+ scope.ImportAll("test");
+ //scope.ImportModule("test");
+
+ Assert.IsTrue(scope.Contains("bb"));
+ }
+ }
+ }
+
+ ///
+ /// 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.Get("ee");
+ Assert.AreEqual(200, a0);
+
+ ps.Exec("locals()['ee'] = 210");
+ var a1 = ps.Get("ee");
+ Assert.AreEqual(210, a1);
+
+ ps.Exec("globals()['ee'] = 220");
+ var a2 = ps.Get("ee");
+ Assert.AreEqual(220, a2);
+
+ using (var item = ps.Variables())
+ {
+ item["ee"] = new PyInt(230);
+ }
+ var a3 = ps.Get("ee");
+ Assert.AreEqual(230, a3);
+ }
+
+ ///
+ /// 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.Get("th_cnt");
+ }
+ System.Threading.Thread.Sleep(10);
+ }
+ using (Py.GIL())
+ {
+ var result = ps.Get("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 2fd66ad73..ee1a3c701 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -127,6 +127,7 @@
+
diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs
index 80704c59e..b7df2a924 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
new file mode 100644
index 000000000..1991772dc
--- /dev/null
+++ b/src/runtime/pyscope.cs
@@ -0,0 +1,655 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Dynamic;
+
+namespace Python.Runtime
+{
+ public class PyScopeException : Exception
+ {
+ public PyScopeException(string message)
+ : base(message)
+ {
+
+ }
+ }
+
+ ///
+ /// Classes/methods have this attribute must be used with GIL obtained.
+ ///
+ public class PyGILAttribute : Attribute
+ {
+ }
+
+ [PyGIL]
+ public class PyScope : DynamicObject, IDisposable
+ {
+ public readonly string Name;
+
+ ///
+ /// the python Module object the scope associated with.
+ ///
+ internal readonly IntPtr obj;
+
+ ///
+ /// the variable dict of the scope.
+ ///
+ internal readonly IntPtr variables;
+
+ 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)
+ {
+ throw new PyScopeException("object is not a module");
+ }
+ Manager = manager ?? PyScopeManager.Global;
+ obj = ptr;
+ //Refcount of the variables not increase
+ variables = Runtime.PyModule_GetDict(obj);
+ if (variables == IntPtr.Zero)
+ {
+ throw new PythonException();
+ }
+ Runtime.PyDict_SetItemString(
+ variables, "__builtins__",
+ Runtime.PyEval_GetBuiltins()
+ );
+ 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();
+ scope.ImportAll(this);
+ 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();
+ if (String.IsNullOrEmpty(asname))
+ {
+ asname = name;
+ }
+ PyScope scope;
+ Manager.TryGet(name, out scope);
+ if (scope != null)
+ {
+ Import(scope, asname);
+ return scope;
+ }
+ else
+ {
+ PyObject module = PythonEngine.ImportModule(name);
+ Import(module, asname);
+ return module;
+ }
+ }
+
+ ///
+ /// Import method
+ ///
+ ///
+ /// Import a scope as a variable of given name.
+ ///
+ public void Import(PyScope scope, string asname)
+ {
+ this.Set(asname, scope.obj);
+ }
+
+ ///
+ /// Import Method
+ ///
+ ///
+ /// 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__").As();
+ }
+ 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;
+ Manager.TryGet(name, out scope);
+ if (scope != null)
+ {
+ ImportAll(scope);
+ return;
+ }
+ else
+ {
+ PyObject module = PythonEngine.ImportModule(name);
+ ImportAll(module);
+ }
+ }
+
+ ///
+ /// ImportAll Method
+ ///
+ ///
+ /// Import all variables of the scope into this scope.
+ ///
+ public void ImportAll(PyScope scope)
+ {
+ int result = Runtime.PyDict_Update(variables, scope.variables);
+ if (result < 0)
+ {
+ throw new PythonException();
+ }
+ }
+
+ ///
+ /// ImportAll Method
+ ///
+ ///
+ /// Import all variables of the module into this scope.
+ ///
+ 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();
+ }
+ }
+
+ ///
+ /// ImportAll Method
+ ///
+ ///
+ /// Import all variables in the dictionary into this scope.
+ ///
+ public void ImportAll(PyDict dict)
+ {
+ int result = Runtime.PyDict_Update(variables, dict.obj);
+ if (result < 0)
+ {
+ throw new PythonException();
+ }
+ }
+
+ ///
+ /// 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, PyDict locals = null)
+ {
+ Check();
+ IntPtr _locals = locals == null ? variables : locals.obj;
+ IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, _locals);
+ Runtime.CheckExceptionOccurred();
+ if (ptr == Runtime.PyNone)
+ {
+ Runtime.XDecref(ptr);
+ return 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();
+ PyObject pyObj = Execute(script, locals);
+ if (pyObj == null)
+ {
+ return default(T);
+ }
+ var obj = pyObj.As();
+ return obj;
+ }
+
+ ///
+ /// Eval method
+ ///
+ ///
+ /// Evaluate a Python expression and return the result as a PyObject
+ /// or null if an exception is raised.
+ ///
+ 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, _locals
+ );
+ Runtime.CheckExceptionOccurred();
+ return new PyObject(ptr);
+ }
+
+ ///
+ /// Evaluate a Python expression
+ ///
+ ///
+ /// Evaluate a Python expression
+ /// and convert the result to a Managed Object of given type.
+ ///
+ public T Eval(string code, PyDict locals = null)
+ {
+ Check();
+ PyObject pyObj = Eval(code, locals);
+ var obj = pyObj.As();
+ return obj;
+ }
+
+ ///
+ /// Exec Method
+ ///
+ ///
+ /// Exec a Python script and save its local variables in the current local variable dict.
+ ///
+ public void Exec(string code, PyDict locals = null)
+ {
+ Check();
+ IntPtr _locals = locals == null ? variables : locals.obj;
+ Exec(code, variables, _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);
+ }
+
+ ///
+ /// Set Variable Method
+ ///
+ ///
+ /// Add a new variable to the variables dict if it not exist
+ /// or update its value if the variable exists.
+ ///
+ public void Set(string name, object value)
+ {
+ IntPtr _value = Converter.ToPython(value, value?.GetType());
+ Set(name, _value);
+ Runtime.XDecref(_value);
+ }
+
+ private void Set(string name, IntPtr value)
+ {
+ Check();
+ using (var pyKey = new PyString(name))
+ {
+ int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value);
+ if (r < 0)
+ {
+ throw new PythonException();
+ }
+ }
+ }
+
+ ///
+ /// Remove Method
+ ///
+ ///
+ /// Remove a variable from the variables dict.
+ ///
+ public void Remove(string name)
+ {
+ Check();
+ using (var pyKey = new PyString(name))
+ {
+ int r = Runtime.PyObject_DelItem(variables, pyKey.obj);
+ if (r < 0)
+ {
+ throw new PythonException();
+ }
+ }
+ }
+
+ ///
+ /// Contains Method
+ ///
+ ///
+ /// Returns true if the variable exists in the scope.
+ ///
+ public bool Contains(string name)
+ {
+ Check();
+ using (var pyKey = new PyString(name))
+ {
+ return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0;
+ }
+ }
+
+ ///
+ /// Get Method
+ ///
+ ///
+ /// Returns the value of the variable of given name.
+ /// If the variable does not exist, throw an Exception.
+ ///
+ public PyObject Get(string name)
+ {
+ PyObject scope;
+ var state = TryGet(name, out scope);
+ if(!state)
+ {
+ throw new PyScopeException($"The scope of name '{Name}' has no attribute '{name}'");
+ }
+ return scope;
+ }
+
+ ///
+ /// TryGet Method
+ ///
+ ///
+ /// Returns the value of the variable, local variable first.
+ /// If the variable does not exist, return null.
+ ///
+ public bool TryGet(string name, out PyObject value)
+ {
+ Check();
+ using (var pyKey = new PyString(name))
+ {
+ 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)
+ {
+ Runtime.XDecref(op);
+ value = null;
+ return true;
+ }
+ value = new PyObject(op);
+ return true;
+ }
+ else
+ {
+ value = null;
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// 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 = Get(name);
+ if (pyObj == null)
+ {
+ return default(T);
+ }
+ return pyObj.As();
+ }
+
+ ///
+ /// 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 = TryGet(name, out pyObj);
+ if (!result)
+ {
+ value = default(T);
+ return false;
+ }
+ if (pyObj == null)
+ {
+ 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;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ result = this.Get(binder.Name);
+ return true;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ this.Set(binder.Name, value);
+ return true;
+ }
+
+ private void Check()
+ {
+ if (_isDisposed)
+ {
+ throw new PyScopeException($"The scope of name '{Name}' object has been disposed");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+ _isDisposed = true;
+ Runtime.XDecref(obj);
+ this.OnDispose?.Invoke(this);
+ }
+
+ ~PyScope()
+ {
+ 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);
+ }
+
+ ///
+ /// Create Method
+ ///
+ ///
+ /// Create an anonymous scope.
+ ///
+ [PyGIL]
+ public PyScope Create()
+ {
+ var scope = this.NewScope(null);
+ return scope;
+ }
+
+ ///
+ /// Create Method
+ ///
+ ///
+ /// Create an named scope of given name.
+ ///
+ [PyGIL]
+ public PyScope Create(string name)
+ {
+ if (String.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (name != null && Contains(name))
+ {
+ throw new PyScopeException($"A scope of name '{name}' does already exist");
+ }
+ var scope = this.NewScope(name);
+ scope.OnDispose += Remove;
+ NamedScopes[name] = scope;
+ 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 (String.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (NamedScopes.ContainsKey(name))
+ {
+ return NamedScopes[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);
+ }
+
+ [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 9ddd85da6..556da698f 100644
--- a/src/runtime/pythonengine.cs
+++ b/src/runtime/pythonengine.cs
@@ -294,6 +294,7 @@ public static void Shutdown()
{
if (initialized)
{
+ PyScopeManager.Global.Clear();
Marshal.FreeHGlobal(_pythonHome);
_pythonHome = IntPtr.Zero;
Marshal.FreeHGlobal(_programName);
@@ -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
@@ -539,6 +547,18 @@ public static GILState GIL()
return new GILState();
}
+ public static PyScope CreateScope()
+ {
+ var scope = PyScopeManager.Global.Create();
+ return scope;
+ }
+
+ public static PyScope CreateScope(string name)
+ {
+ var scope = PyScopeManager.Global.Create(name);
+ return scope;
+ }
+
public class GILState : IDisposable
{
private IntPtr state;
diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs
index 9ee693cf3..a24b6f6d4 100644
--- a/src/runtime/runtime.cs
+++ b/src/runtime/runtime.cs
@@ -707,6 +707,9 @@ 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)]
+ internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals);
+
[DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr Py_CompileString(string code, string file, IntPtr tok);