diff --git a/CHANGELOG.md b/CHANGELOG.md index b59b2f040..c3075e2e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ details about the cause of the failure - floating point values passed from Python are no longer silently truncated when .NET expects an integer [#1342][i1342] - More specific error messages for method argument mismatch +- BREAKING: most `PyScope` methods will never return `null`. Instead, `PyObject` `None` will be returned. +- BREAKING: `PyScope` was renamed to `PyModule` - BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters. - BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name or the DLL must be loaded in advance. This must be done before calling any other Python.NET functions. @@ -97,6 +99,7 @@ Instead, `PyIterable` does that. - implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) - messages in `PythonException` no longer start with exception type +- `PyScopeManager`, `PyScopeException`, `PyScope` (use `PyModule` instead) - support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 (see [the matrix](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support)) diff --git a/src/embed_tests/TestPyScope.cs b/src/embed_tests/Modules.cs similarity index 91% rename from src/embed_tests/TestPyScope.cs rename to src/embed_tests/Modules.cs index a94b8ce28..a88ab8552 100644 --- a/src/embed_tests/TestPyScope.cs +++ b/src/embed_tests/Modules.cs @@ -5,9 +5,9 @@ namespace Python.EmbeddingTest { - public class PyScopeTest + public class Modules { - private PyScope ps; + private PyModule ps; [SetUp] public void SetUp() @@ -15,7 +15,7 @@ public void SetUp() using (Py.GIL()) { ps = Py.CreateScope("test"); - } + } } [TearDown] @@ -28,6 +28,18 @@ public void Dispose() } } + [OneTimeSetUp] + public void OneTimeSetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + PythonEngine.Shutdown(); + } + /// /// Eval a Python expression and obtain its return value. /// @@ -243,7 +255,7 @@ public void TestImportScopeFunction() "def func1():\n" + " return cc + bb\n"); - using (PyScope scope = ps.NewScope()) + using (var scope = ps.NewScope()) { //'func1' is imported from the origion scope scope.Exec( @@ -267,27 +279,6 @@ public void TestImportScopeFunction() } } - /// - /// 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 /// @@ -381,5 +372,34 @@ public void TestThread() PythonEngine.EndAllowThreads(ts); } } + + [Test] + public void TestCreate() + { + using var scope = Py.CreateScope(); + + Assert.IsFalse(PyModule.SysModules.HasKey("testmod")); + + PyModule testmod = new PyModule("testmod"); + + testmod.SetAttr("testattr1", "True".ToPython()); + + PyModule.SysModules.SetItem("testmod", testmod); + + using PyObject code = PythonEngine.Compile( + "import testmod\n" + + "x = testmod.testattr1" + ); + scope.Execute(code); + + Assert.IsTrue(scope.TryGet("x", out dynamic x)); + Assert.AreEqual("True", x.ToString()); + } + + [Test] + public void ImportClrNamespace() + { + Py.Import(GetType().Namespace); + } } } diff --git a/src/embed_tests/TestPyModule.cs b/src/embed_tests/TestPyModule.cs deleted file mode 100644 index 623f93d52..000000000 --- a/src/embed_tests/TestPyModule.cs +++ /dev/null @@ -1,50 +0,0 @@ -using NUnit.Framework; - -using Python.Runtime; - -namespace Python.EmbeddingTest -{ - public class TestPyModule - { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestCreate() - { - using PyScope scope = Py.CreateScope(); - - Assert.IsFalse(PyModule.SysModules.HasKey("testmod")); - - PyModule testmod = new PyModule("testmod"); - - testmod.SetAttr("testattr1", "True".ToPython()); - - PyModule.SysModules.SetItem("testmod", testmod); - - using PyObject code = PythonEngine.Compile( - "import testmod\n" + - "x = testmod.testattr1" - ); - scope.Execute(code); - - Assert.IsTrue(scope.TryGet("x", out dynamic x)); - Assert.AreEqual("True", x.ToString()); - } - - [Test] - public void ImportClrNamespace() - { - Py.Import(typeof(TestPyModule).Namespace); - } - } -} diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index df791d664..a15aff585 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -95,15 +95,6 @@ public void ReInitialize() PythonEngine.Shutdown(); } - [Test] - public void TestScopeIsShutdown() - { - PythonEngine.Initialize(); - var scope = PyScopeManager.Global.Create("test"); - PythonEngine.Shutdown(); - Assert.That(PyScopeManager.Global.Contains("test"), Is.False); - } - /// /// Helper for testing the shutdown handlers. /// diff --git a/src/runtime/module.cs b/src/runtime/module.cs new file mode 100644 index 000000000..050df87eb --- /dev/null +++ b/src/runtime/module.cs @@ -0,0 +1,477 @@ +#nullable enable +using System; +using System.Linq; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Runtime +{ + public class PyModule : PyObject + { + /// + /// the variable dict of the module. Borrowed. + /// + internal readonly IntPtr variables; + internal BorrowedReference VarsRef => new BorrowedReference(variables); + + public PyModule(string name = "") + : this(Create(name ?? throw new ArgumentNullException(nameof(name)))) + { + } + + public PyModule(string name, string? fileName = null) : this(Create(name, fileName)) { } + + static StolenReference Create(string name, string? filename = null) + { + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + + NewReference op = Runtime.PyModule_New(name); + PythonException.ThrowIfIsNull(op); + + if (filename is not null) + { + BorrowedReference globals = Runtime.PyModule_GetDict(op); + PythonException.ThrowIfIsNull(globals); + using var pyFileName = filename.ToPython(); + int rc = Runtime.PyDict_SetItemString(globals, "__file__", pyFileName.Reference); + PythonException.ThrowIfIsNotZero(rc); + } + + return op.Steal(); + } + + internal PyModule(in StolenReference reference) : base(reference) + { + if (!IsModule(Reference)) + { + throw new ArgumentException("object is not a module"); + } + //Refcount of the variables not increase + variables = Runtime.PyModule_GetDict(Reference).DangerousGetAddress(); + PythonException.ThrowIfIsNull(variables); + + int res = Runtime.PyDict_SetItem( + VarsRef, new BorrowedReference(PyIdentifier.__builtins__), + Runtime.PyEval_GetBuiltins() + ); + PythonException.ThrowIfIsNotZero(res); + } + internal PyModule(BorrowedReference reference) : this(new NewReference(reference).Steal()) + { + } + + /// + /// Given a module or package name, import the module and return the resulting object. + /// + /// Fully-qualified module or package name + public static PyObject Import(string name) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + NewReference op = Runtime.PyImport_ImportModule(name); + PythonException.ThrowIfIsNull(op); + return IsModule(op) ? new PyModule(op.Steal()) : op.MoveToPyObject(); + } + + /// + /// Reloads the module, and returns the updated object + /// + public PyModule Reload() + { + NewReference op = Runtime.PyImport_ReloadModule(this.Reference); + PythonException.ThrowIfIsNull(op); + return new PyModule(op.Steal()); + } + + public static PyModule FromString(string name, string code) + { + using NewReference c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File); + PythonException.ThrowIfIsNull(c); + NewReference m = Runtime.PyImport_ExecCodeModule(name, c); + PythonException.ThrowIfIsNull(m); + return new PyModule(m.Steal()); + } + + public void SetBuiltins(PyDict builtins) + { + if (builtins == null || builtins.IsNone()) + { + throw new ArgumentNullException(nameof(builtins)); + } + + BorrowedReference globals = Runtime.PyModule_GetDict(this.Reference); + PythonException.ThrowIfIsNull(globals); + int rc = Runtime.PyDict_SetItemString(globals, "__builtins__", builtins.Reference); + PythonException.ThrowIfIsNotZero(rc); + } + + public static PyDict SysModules + { + get + { + BorrowedReference sysModulesRef = Runtime.PyImport_GetModuleDict(); + PythonException.ThrowIfIsNull(sysModulesRef); + return new PyDict(sysModulesRef); + } + } + + internal static bool IsModule(BorrowedReference reference) + { + if (reference == null) return false; + BorrowedReference type = Runtime.PyObject_TYPE(reference); + return Runtime.PyType_IsSubtype(type, Runtime.PyModuleType); + } + + /// + /// Returns the variables dict of the module. + /// + public PyDict Variables() => new(VarsRef); + + /// + /// Create a scope, and import all from this scope + /// + public PyModule NewScope() + { + var scope = new PyModule(); + scope.ImportAll(this); + return scope; + } + /// + /// Import module by its name. + /// + public PyObject Import(string name, string? asname = null) + { + Check(); + + asname ??= name; + + var module = PyModule.Import(name); + Import(module, asname); + return module; + } + + /// + /// Import module as a variable of given name. + /// + public void Import(PyModule module, string asname) + { + this.SetPyValue(asname, module.Handle); + } + + /// + /// The 'import .. as ..' statement in Python. + /// Import a module as a variable. + /// + public void Import(PyObject module, string? asname = null) + { + asname ??= module.GetAttr("__name__").As(); + Set(asname, module); + } + + /// + /// Import all variables of the module into this module. + /// + public void ImportAll(PyModule module) + { + int result = Runtime.PyDict_Update(VarsRef, module.VarsRef); + if (result < 0) + { + throw PythonException.ThrowLastAsClrException(); + } + } + + /// + /// Import all variables of the module into this module. + /// + public void ImportAll(PyObject module) + { + if (module is null) throw new ArgumentNullException(nameof(module)); + + if (!IsModule(module.Reference)) + { + throw new ArgumentException("object is not a module", paramName: nameof(module)); + } + var module_dict = Runtime.PyModule_GetDict(module.Reference); + int result = Runtime.PyDict_Update(VarsRef, module_dict); + if (result < 0) + { + throw PythonException.ThrowLastAsClrException(); + } + } + + /// + /// Import all variables in the dictionary into this module. + /// + public void ImportAll(PyDict dict) + { + int result = Runtime.PyDict_Update(VarsRef, dict.Reference); + if (result < 0) + { + throw PythonException.ThrowLastAsClrException(); + } + } + + /// + /// 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); + PythonException.ThrowIfIsNull(ptr); + 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); + 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(); + BorrowedReference _locals = locals == null ? VarsRef : locals.Reference; + + NewReference reference = Runtime.PyRun_String( + code, RunFlagType.Eval, VarsRef, _locals + ); + PythonException.ThrowIfIsNull(reference); + return reference.MoveToPyObject(); + } + + /// + /// 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(); + BorrowedReference _locals = locals == null ? VarsRef : locals.Reference; + Exec(code, VarsRef, _locals); + } + + private void Exec(string code, BorrowedReference _globals, BorrowedReference _locals) + { + using NewReference reference = Runtime.PyRun_String( + code, RunFlagType.File, _globals, _locals + ); + PythonException.ThrowIfIsNull(reference); + } + + /// + /// 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) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + IntPtr _value = Converter.ToPython(value, value?.GetType()); + SetPyValue(name, _value); + Runtime.XDecref(_value); + } + + private void SetPyValue(string name, IntPtr value) + { + Check(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); + if (r < 0) + { + throw PythonException.ThrowLastAsClrException(); + } + } + } + + /// + /// Remove Method + /// + /// + /// Remove a variable from the variables dict. + /// + public void Remove(string name) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + Check(); + using (var pyKey = new PyString(name)) + { + int r = Runtime.PyObject_DelItem(variables, pyKey.obj); + if (r < 0) + { + throw PythonException.ThrowLastAsClrException(); + } + } + } + + /// + /// Returns true if the variable exists in the module. + /// + public bool Contains(string name) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + Check(); + using (var pyKey = new PyString(name)) + { + return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0; + } + } + + /// + /// Returns the value of the variable with the given name. + /// + /// + /// Thrown when variable with the given name does not exist. + /// + public PyObject Get(string name) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + var state = TryGet(name, out var value); + if (!state) + { + throw new KeyNotFoundException($"The module has no attribute '{name}'"); + } + return value!; + } + + /// + /// 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) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + 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 PythonException.ThrowLastAsClrException(); + } + + 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) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + Check(); + PyObject pyObj = Get(name); + 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(); + var result = TryGet(name, out var pyObj); + if (!result) + { + value = default(T); + return false; + } + value = pyObj!.As(); + return true; + } + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = CheckNone(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 (this.obj == IntPtr.Zero) + { + throw new ObjectDisposedException(nameof(PyModule)); + } + } + } +} diff --git a/src/runtime/pymodule.cs b/src/runtime/pymodule.cs deleted file mode 100644 index f36147ce8..000000000 --- a/src/runtime/pymodule.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; - -namespace Python.Runtime -{ - public class PyModule : PyScope - { - internal PyModule(ref NewReference reference) : base(ref reference, PyScopeManager.Global) { } - public PyModule(PyObject o) : base(o.Reference, PyScopeManager.Global) { } - public PyModule(string name, string filename = null) : this(Create(name, filename)) { } - - /// - /// Given a module or package name, import the module and return the resulting object. - /// - /// Fully-qualified module or package name - public static PyObject Import(string name) - { - NewReference op = Runtime.PyImport_ImportModule(name); - PythonException.ThrowIfIsNull(op); - return IsModule(op) ? new PyModule(ref op) : op.MoveToPyObject(); - } - - /// - /// Reloads the module, and returns the updated object - /// - public PyModule Reload() - { - NewReference op = Runtime.PyImport_ReloadModule(this.Reference); - PythonException.ThrowIfIsNull(op); - return new PyModule(ref op); - } - - public static PyModule FromString(string name, string code) - { - using NewReference c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File); - PythonException.ThrowIfIsNull(c); - NewReference m = Runtime.PyImport_ExecCodeModule(name, c); - PythonException.ThrowIfIsNull(m); - return new PyModule(ref m); - } - - private static PyModule Create(string name, string filename=null) - { - if(string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - NewReference op = Runtime.PyModule_New(name); - PythonException.ThrowIfIsNull(op); - - if (filename != null) - { - BorrowedReference globals = Runtime.PyModule_GetDict(op); - PythonException.ThrowIfIsNull(globals); - int rc = Runtime.PyDict_SetItemString(globals, "__file__", filename.ToPython().Reference); - PythonException.ThrowIfIsNotZero(rc); - } - - return new PyModule(ref op); - } - - public void SetBuiltins(PyDict builtins) - { - if(builtins == null || builtins.IsNone()) - { - throw new ArgumentNullException(nameof(builtins)); - } - - BorrowedReference globals = Runtime.PyModule_GetDict(this.Reference); - PythonException.ThrowIfIsNull(globals); - int rc = Runtime.PyDict_SetItemString(globals, "__builtins__", builtins.Reference); - PythonException.ThrowIfIsNotZero(rc); - } - - public static PyDict SysModules - { - get - { - BorrowedReference sysModulesRef = Runtime.PyImport_GetModuleDict(); - PythonException.ThrowIfIsNull(sysModulesRef); - return new PyDict(sysModulesRef); - } - } - - internal static bool IsModule(BorrowedReference reference) - { - if (reference == null) return false; - BorrowedReference type = Runtime.PyObject_TYPE(reference); - return Runtime.PyType_IsSubtype(type, Runtime.PyModuleType); - } - } -} diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 635adbd74..05c482454 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1366,7 +1366,7 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg // Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509 // See https://github.com/pythonnet/pythonnet/pull/219 - private static object CheckNone(PyObject pyObj) + internal static object CheckNone(PyObject pyObj) { if (pyObj != null) { diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs deleted file mode 100644 index 66c299811..000000000 --- a/src/runtime/pyscope.cs +++ /dev/null @@ -1,633 +0,0 @@ -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 : PyObject - { - public string Name { get; } - - /// - /// the variable dict of the scope. Borrowed. - /// - internal readonly IntPtr variables; - internal BorrowedReference VarsRef => new BorrowedReference(variables); - - /// - /// 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; - - /// Create a scope based on a Python Module. - internal PyScope(ref NewReference reference, PyScopeManager manager) - : this(reference.DangerousMoveToPointer(), manager) { } - /// Create a scope based on a Python Module. - internal PyScope(BorrowedReference reference, PyScopeManager manager) - : this(reference.DangerousGetAddress(), manager) - { - Runtime.XIncref(reference.DangerousGetAddress()); - } - - /// Create a scope based on a Python Module. - private PyScope(IntPtr ptr, PyScopeManager manager) : base(ptr) - { - if (!PyModule.IsModule(Reference)) - { - throw new PyScopeException("object is not a module"); - } - Manager = manager ?? PyScopeManager.Global; - //Refcount of the variables not increase - variables = Runtime.PyModule_GetDict(Reference).DangerousGetAddress(); - PythonException.ThrowIfIsNull(variables); - - int res = Runtime.PyDict_SetItem( - VarsRef, new BorrowedReference(PyIdentifier.__builtins__), - Runtime.PyEval_GetBuiltins() - ); - PythonException.ThrowIfIsNotZero(res); - using var name = this.Get("__name__"); - this.Name = name.As(); - } - - /// - /// return the variable dict of the scope. - /// - public PyDict Variables() - { - return new PyDict(VarsRef); - } - - /// - /// 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 PyObject 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 - { - var module = PyModule.Import(name); - Import(module, asname); - return module; - } - } - - /// - /// Import a scope as a variable of given name. - /// - public void Import(PyScope scope, string asname) - { - if (scope is null) throw new ArgumentNullException(nameof(scope)); - this.SetPyValue(asname, scope.Handle); - } - - /// - /// The 'import .. as ..' statement in Python. - /// Import a module as a variable into the scope. - /// - public void Import(PyObject module, string asname = null) - { - if (module is null) throw new ArgumentNullException(nameof(module)); - - if (String.IsNullOrEmpty(asname)) - { - asname = module.GetAttr("__name__").As(); - } - Set(asname, module); - } - - /// - /// 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 - { - var module = PyModule.Import(name); - ImportAll(module); - } - } - - /// - /// Import all variables of the scope into this scope. - /// - public void ImportAll(PyScope scope) - { - if (scope is null) throw new ArgumentNullException(nameof(scope)); - - int result = Runtime.PyDict_Update(VarsRef, scope.VarsRef); - if (result < 0) - { - throw PythonException.ThrowLastAsClrException(); - } - } - - /// - /// Import all variables of the module into this scope. - /// - public void ImportAll(PyObject module) - { - if (module is null) throw new ArgumentNullException(nameof(module)); - - if (Runtime.PyObject_Type(module.obj) != Runtime.PyModuleType) - { - throw new PyScopeException("object is not a module"); - } - var module_dict = Runtime.PyModule_GetDict(module.Reference); - int result = Runtime.PyDict_Update(VarsRef, module_dict); - if (result < 0) - { - throw PythonException.ThrowLastAsClrException(); - } - } - - /// - /// Import all variables in the dictionary into this scope. - /// - public void ImportAll(PyDict dict) - { - if (dict is null) throw new ArgumentNullException(nameof(dict)); - - int result = Runtime.PyDict_Update(VarsRef, dict.Reference); - if (result < 0) - { - throw PythonException.ThrowLastAsClrException(); - } - } - - /// - /// 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) - { - if (script is null) throw new ArgumentNullException(nameof(script)); - - Check(); - IntPtr _locals = locals == null ? variables : locals.obj; - IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, _locals); - PythonException.ThrowIfIsNull(ptr); - if (ptr == Runtime.PyNone) - { - Runtime.XDecref(ptr); - return null; - } - return new PyObject(ptr); - } - - /// - /// 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) - { - if (script is null) throw new ArgumentNullException(nameof(script)); - - Check(); - PyObject pyObj = Execute(script, locals); - if (pyObj == null) - { - return default(T); - } - var obj = pyObj.As(); - return obj; - } - - /// - /// 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) - { - if (code is null) throw new ArgumentNullException(nameof(code)); - - Check(); - BorrowedReference _locals = locals == null ? VarsRef : locals.Reference; - - NewReference reference = Runtime.PyRun_String( - code, RunFlagType.Eval, VarsRef, _locals - ); - PythonException.ThrowIfIsNull(reference); - return reference.MoveToPyObject(); - } - - /// - /// Evaluate a Python expression - /// and convert the result to a managed object of given type. - /// - public T Eval(string code, PyDict locals = null) - { - if (code is null) throw new ArgumentNullException(nameof(code)); - - 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) - { - if (code is null) throw new ArgumentNullException(nameof(code)); - - Check(); - BorrowedReference _locals = locals == null ? VarsRef : locals.Reference; - Exec(code, VarsRef, _locals); - } - - private void Exec(string code, BorrowedReference _globals, BorrowedReference _locals) - { - using NewReference reference = Runtime.PyRun_String( - code, RunFlagType.File, _globals, _locals - ); - PythonException.ThrowIfIsNull(reference); - } - - /// - /// 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()); - SetPyValue(name, _value); - Runtime.XDecref(_value); - } - - private void SetPyValue(string name, IntPtr value) - { - Check(); - using (var pyKey = new PyString(name)) - { - int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value); - if (r < 0) - { - throw PythonException.ThrowLastAsClrException(); - } - } - } - - /// - /// 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 PythonException.ThrowLastAsClrException(); - } - } - } - - /// - /// 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 PythonException.ThrowLastAsClrException(); - } - 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 (this.obj == IntPtr.Zero) - { - throw new PyScopeException($"The scope of name '{Name}' object has been disposed"); - } - } - - protected override void Dispose(bool disposing) - { - if (this.obj == IntPtr.Zero) - { - return; - } - base.Dispose(disposing); - this.OnDispose?.Invoke(this); - } - } - - public class PyScopeManager - { - public static PyScopeManager Global; - - private Dictionary NamedScopes = new Dictionary(); - - internal static void Reset() - { - Global = new PyScopeManager(); - } - - internal PyScope NewScope(string name) - { - if (name == null) - { - name = ""; - } - var module = Runtime.PyModule_New(name); - if (module.IsNull()) - { - throw PythonException.ThrowLastAsClrException(); - } - return new PyScope(ref 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 7156c3edd..10808a1cd 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -214,10 +214,6 @@ public static void Initialize(IEnumerable args, bool setSysArgv = true, AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; AppDomain.CurrentDomain.ProcessExit += OnProcessExit; - // The global scope gets used implicitly quite early on, remember - // to clear it out when we shut down. - AddShutdownHandler(PyScopeManager.Global.Clear); - if (setSysArgv) { Py.SetArgv(args); @@ -381,7 +377,6 @@ public static void Shutdown(ShutdownMode mode) AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; - PyScopeManager.Global.Clear(); ExecuteShutdownHandlers(); // Remember to shut down the runtime. Runtime.Shutdown(mode); @@ -694,19 +689,10 @@ public static GILState GIL() return PythonEngine.DebugGIL ? new DebugGILState() : new GILState(); } - public static PyScope CreateScope() - { - var scope = PyScopeManager.Global.Create(); - return scope; - } - - public static PyScope CreateScope(string name) - { - if (name is null) throw new ArgumentNullException(nameof(name)); + public static PyModule CreateScope() => new(); + public static PyModule CreateScope(string name) + => new(name ?? throw new ArgumentNullException(nameof(name))); - var scope = PyScopeManager.Global.Create(name); - return scope; - } public class GILState : IDisposable { diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index e3fba7e80..8cdd6eb70 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -147,7 +147,6 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd ABI.Initialize(PyVersion); GenericUtil.Reset(); - PyScopeManager.Reset(); ClassManager.Reset(); ClassDerivedObject.Reset(); TypeManager.Initialize();