Skip to content

Commit 50834ba

Browse files
committed
introduced PyModule (inherits PyScope)
changed PyScope to inherit from PyObject
1 parent 60bb68c commit 50834ba

13 files changed

+145
-123
lines changed

src/embed_tests/TestFinalizer.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,17 @@ public void CollectOnShutdown()
101101

102102
PythonEngine.Shutdown();
103103
garbage = Finalizer.Instance.GetCollectedObjects();
104-
Assert.IsEmpty(garbage);
104+
105+
if (garbage.Count > 0)
106+
{
107+
PythonEngine.Initialize();
108+
string objects = string.Join("\n", garbage.Select(ob =>
109+
{
110+
var obj = new PyObject(new BorrowedReference(ob));
111+
return $"{obj} [{obj.GetPythonType()}@{obj.Handle}]";
112+
}));
113+
Assert.Fail("Garbage is not empty:\n" + objects);
114+
}
105115
}
106116

107117
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] // ensure lack of references to obj
@@ -173,7 +183,7 @@ public void SimpleTestMemory()
173183
bool oldState = Finalizer.Instance.Enable;
174184
try
175185
{
176-
using (PyObject gcModule = PythonEngine.ImportModule("gc"))
186+
using (PyModule gcModule = PyModule.Import("gc"))
177187
using (PyObject pyCollect = gcModule.GetAttr("collect"))
178188
{
179189
long span1 = CompareWithFinalizerOn(pyCollect, false);

src/embed_tests/TestPythonException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void TestPythonErrorTypeName()
4646
{
4747
try
4848
{
49-
var module = PythonEngine.ImportModule("really____unknown___module");
49+
var module = PyModule.Import("really____unknown___module");
5050
Assert.Fail("Unknown module should not be loaded");
5151
}
5252
catch (PythonException ex)
@@ -95,7 +95,7 @@ public void TestPythonExceptionFormatNoTraceback()
9595
{
9696
try
9797
{
98-
var module = PythonEngine.ImportModule("really____unknown___module");
98+
var module = PyModule.Import("really____unknown___module");
9999
Assert.Fail("Unknown module should not be loaded");
100100
}
101101
catch (PythonException ex)

src/embed_tests/TestRuntime.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test()
9696
// TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check.
9797
var threading = Runtime.Runtime.PyImport_ImportModule("threading");
9898
Exceptions.ErrorCheck(threading);
99-
var threadingDict = Runtime.Runtime.PyModule_GetDict(new BorrowedReference(threading));
99+
var threadingDict = Runtime.Runtime.PyModule_GetDict(threading);
100100
Exceptions.ErrorCheck(threadingDict);
101101
var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock");
102102
if (lockType.IsNull)
@@ -110,6 +110,8 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test()
110110
Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance));
111111
Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance));
112112

113+
threading.Dispose();
114+
113115
Runtime.Runtime.Py_Finalize();
114116
}
115117
}

src/embed_tests/pyimport.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void Dispose()
5252
[Test]
5353
public void TestDottedName()
5454
{
55-
PyObject module = PythonEngine.ImportModule("PyImportTest.test.one");
55+
var module = PyModule.Import("PyImportTest.test.one");
5656
Assert.IsNotNull(module);
5757
}
5858

@@ -62,7 +62,7 @@ public void TestDottedName()
6262
[Test]
6363
public void TestSysArgsImportException()
6464
{
65-
PyObject module = PythonEngine.ImportModule("PyImportTest.sysargv");
65+
var module = PyModule.Import("PyImportTest.sysargv");
6666
Assert.IsNotNull(module);
6767
}
6868

src/embed_tests/pyinitialize.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ public static void TestRunExitFuncs()
175175
{
176176
called = true;
177177
};
178-
atexit.InvokeMethod("register", callback.ToPython());
178+
atexit.InvokeMethod("register", callback.ToPython()).Dispose();
179+
atexit.Dispose();
179180
Runtime.Runtime.Shutdown();
180181
Assert.True(called);
181182
}

src/runtime/exceptions.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,24 +95,21 @@ internal static Exception ToException(IntPtr ob)
9595
/// </remarks>
9696
public static class Exceptions
9797
{
98-
internal static IntPtr warnings_module;
99-
internal static IntPtr exceptions_module;
98+
internal static PyModule warnings_module;
99+
internal static PyModule exceptions_module;
100100

101101
/// <summary>
102102
/// Initialization performed on startup of the Python runtime.
103103
/// </summary>
104104
internal static void Initialize()
105105
{
106106
string exceptionsModuleName = "builtins";
107-
exceptions_module = Runtime.PyImport_ImportModule(exceptionsModuleName);
108-
109-
Exceptions.ErrorCheck(exceptions_module);
110-
warnings_module = Runtime.PyImport_ImportModule("warnings");
111-
Exceptions.ErrorCheck(warnings_module);
107+
exceptions_module = PyModule.Import(exceptionsModuleName);
108+
warnings_module = PyModule.Import("warnings");
112109
Type type = typeof(Exceptions);
113110
foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.Static))
114111
{
115-
IntPtr op = Runtime.PyObject_GetAttrString(exceptions_module, fi.Name);
112+
IntPtr op = Runtime.PyObject_GetAttrString(exceptions_module.obj, fi.Name);
116113
if (op != IntPtr.Zero)
117114
{
118115
fi.SetValue(type, op);
@@ -147,8 +144,8 @@ internal static void Shutdown()
147144
Runtime.XDecref(op);
148145
fi.SetValue(null, IntPtr.Zero);
149146
}
150-
Runtime.Py_CLEAR(ref exceptions_module);
151-
Runtime.Py_CLEAR(ref warnings_module);
147+
exceptions_module.Dispose();
148+
warnings_module.Dispose();
152149
}
153150

154151
/// <summary>
@@ -348,9 +345,7 @@ public static void warn(string message, IntPtr exception, int stacklevel)
348345
Exceptions.RaiseTypeError("Invalid exception");
349346
}
350347

351-
Runtime.XIncref(warnings_module);
352-
IntPtr warn = Runtime.PyObject_GetAttrString(warnings_module, "warn");
353-
Runtime.XDecref(warnings_module);
348+
IntPtr warn = Runtime.PyObject_GetAttrString(warnings_module.obj, "warn");
354349
Exceptions.ErrorCheck(warn);
355350

356351
IntPtr args = Runtime.PyTuple_New(3);

src/runtime/pymodule.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
3+
namespace Python.Runtime
4+
{
5+
public class PyModule : PyScope
6+
{
7+
internal PyModule(ref NewReference reference) : base(ref reference, PyScopeManager.Global) { }
8+
public PyModule(PyObject o) : base(o.Reference, PyScopeManager.Global) { }
9+
10+
/// <summary>
11+
/// Given a module or package name, import the
12+
/// module and return the resulting module object as a <see cref="PyModule"/>.
13+
/// </summary>
14+
/// <param name="name">Fully-qualified module or package name</param>
15+
public static PyModule Import(string name)
16+
{
17+
NewReference op = Runtime.PyImport_ImportModule(name);
18+
PythonException.ThrowIfIsNull(op);
19+
return new PyModule(ref op);
20+
}
21+
22+
/// <summary>
23+
/// Reloads the module, and returns the updated object
24+
/// </summary>
25+
public PyModule Reload()
26+
{
27+
NewReference op = Runtime.PyImport_ReloadModule(this.Reference);
28+
PythonException.ThrowIfIsNull(op);
29+
return new PyModule(ref op);
30+
}
31+
32+
public static PyModule FromString(string name, string code)
33+
{
34+
using NewReference c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File);
35+
PythonException.ThrowIfIsNull(c);
36+
NewReference m = Runtime.PyImport_ExecCodeModule(name, c);
37+
PythonException.ThrowIfIsNull(m);
38+
return new PyModule(ref m);
39+
}
40+
}
41+
}

src/runtime/pyscope.cs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,9 @@ public class PyGILAttribute : Attribute
2222
}
2323

2424
[PyGIL]
25-
public class PyScope : DynamicObject, IDisposable
25+
public class PyScope : PyObject
2626
{
27-
public readonly string Name;
28-
29-
/// <summary>
30-
/// the python Module object the scope associated with.
31-
/// </summary>
32-
readonly PyObject obj;
33-
internal BorrowedReference Reference => obj.Reference;
27+
public string Name { get; }
3428

3529
/// <summary>
3630
/// the variable dict of the scope. Borrowed.
@@ -49,20 +43,24 @@ public class PyScope : DynamicObject, IDisposable
4943
/// </summary>
5044
public event Action<PyScope> OnDispose;
5145

52-
/// <summary>
53-
/// Constructor
54-
/// </summary>
55-
/// <remarks>
56-
/// Create a scope based on a Python Module.
57-
/// </remarks>
58-
internal PyScope(ref NewReference ptr, PyScopeManager manager)
46+
/// <summary>Create a scope based on a Python Module.</summary>
47+
internal PyScope(ref NewReference reference, PyScopeManager manager)
48+
: this(reference.DangerousMoveToPointer(), manager) { }
49+
/// <summary>Create a scope based on a Python Module.</summary>
50+
internal PyScope(BorrowedReference reference, PyScopeManager manager)
51+
: this(reference.DangerousGetAddress(), manager)
5952
{
60-
if (!Runtime.PyType_IsSubtype(Runtime.PyObject_TYPE(ptr), Runtime.PyModuleType))
53+
Runtime.XIncref(reference.DangerousGetAddress());
54+
}
55+
56+
/// <summary>Create a scope based on a Python Module.</summary>
57+
private PyScope(IntPtr ptr, PyScopeManager manager) : base(ptr)
58+
{
59+
if (!Runtime.PyType_IsSubtype(Runtime.PyObject_TYPE(Reference), Runtime.PyModuleType))
6160
{
6261
throw new PyScopeException("object is not a module");
6362
}
6463
Manager = manager ?? PyScopeManager.Global;
65-
obj = ptr.MoveToPyObject();
6664
//Refcount of the variables not increase
6765
variables = Runtime.PyModule_GetDict(Reference).DangerousGetAddress();
6866
PythonException.ThrowIfIsNull(variables);
@@ -72,7 +70,8 @@ internal PyScope(ref NewReference ptr, PyScopeManager manager)
7270
Runtime.PyEval_GetBuiltins()
7371
);
7472
PythonException.ThrowIfIsNotZero(res);
75-
this.Name = this.Get<string>("__name__");
73+
using var name = this.Get("__name__");
74+
this.Name = name.As<string>();
7675
}
7776

7877
/// <summary>
@@ -118,7 +117,7 @@ public dynamic Import(string name, string asname = null)
118117
}
119118
else
120119
{
121-
PyObject module = PythonEngine.ImportModule(name);
120+
var module = PyModule.Import(name);
122121
Import(module, asname);
123122
return module;
124123
}
@@ -132,7 +131,7 @@ public dynamic Import(string name, string asname = null)
132131
/// </remarks>
133132
public void Import(PyScope scope, string asname)
134133
{
135-
this.SetPyValue(asname, scope.obj.Handle);
134+
this.SetPyValue(asname, scope.Handle);
136135
}
137136

138137
/// <summary>
@@ -169,7 +168,7 @@ public void ImportAll(string name)
169168
}
170169
else
171170
{
172-
PyObject module = PythonEngine.ImportModule(name);
171+
var module = PyModule.Import(name);
173172
ImportAll(module);
174173
}
175174
}
@@ -503,16 +502,20 @@ public override bool TrySetMember(SetMemberBinder binder, object value)
503502

504503
private void Check()
505504
{
506-
if (this.obj.IsDisposed)
505+
if (this.obj == IntPtr.Zero)
507506
{
508507
throw new PyScopeException($"The scope of name '{Name}' object has been disposed");
509508
}
510509
}
511510

512-
public void Dispose()
511+
protected override void Dispose(bool disposing)
513512
{
513+
if (this.obj == IntPtr.Zero)
514+
{
515+
return;
516+
}
517+
base.Dispose(disposing);
514518
this.OnDispose?.Invoke(this);
515-
this.obj.Dispose();
516519
}
517520
}
518521

src/runtime/pythonengine.cs

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -468,62 +468,27 @@ public static void EndAllowThreads(IntPtr ts)
468468
Runtime.PyEval_RestoreThread(ts);
469469
}
470470

471+
[Obsolete("Use PyModule.Import")]
472+
public static PyObject ImportModule(string name) => PyModule.Import(name);
471473

472-
/// <summary>
473-
/// ImportModule Method
474-
/// </summary>
475-
/// <remarks>
476-
/// Given a fully-qualified module or package name, import the
477-
/// module and return the resulting module object as a PyObject
478-
/// or null if an exception is raised.
479-
/// </remarks>
480-
public static PyObject ImportModule(string name)
481-
{
482-
IntPtr op = Runtime.PyImport_ImportModule(name);
483-
PythonException.ThrowIfIsNull(op);
484-
return new PyObject(op);
485-
}
486-
487-
488-
/// <summary>
489-
/// ReloadModule Method
490-
/// </summary>
491-
/// <remarks>
492-
/// Given a PyObject representing a previously loaded module, reload
493-
/// the module.
494-
/// </remarks>
474+
[Obsolete("Use PyModule.Reload")]
495475
public static PyObject ReloadModule(PyObject module)
496-
{
497-
IntPtr op = Runtime.PyImport_ReloadModule(module.Handle);
498-
PythonException.ThrowIfIsNull(op);
499-
return new PyObject(op);
500-
}
476+
=> module is PyModule pyModule ? pyModule.Reload() : new PyModule(module).Reload();
501477

502-
503-
/// <summary>
504-
/// ModuleFromString Method
505-
/// </summary>
506-
/// <remarks>
507-
/// Given a string module name and a string containing Python code,
508-
/// execute the code in and return a module of the given name.
509-
/// </remarks>
478+
[Obsolete("Use PyModule.FromString")]
510479
public static PyObject ModuleFromString(string name, string code)
511-
{
512-
IntPtr c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File);
513-
PythonException.ThrowIfIsNull(c);
514-
IntPtr m = Runtime.PyImport_ExecCodeModule(name, c);
515-
PythonException.ThrowIfIsNull(m);
516-
return new PyObject(m);
517-
}
480+
=> PyModule.FromString(name, code);
481+
518482

519483
public static PyObject Compile(string code, string filename = "", RunFlagType mode = RunFlagType.File)
520484
{
521485
var flag = (int)mode;
522-
IntPtr ptr = Runtime.Py_CompileString(code, filename, flag);
486+
NewReference ptr = Runtime.Py_CompileString(code, filename, flag);
523487
PythonException.ThrowIfIsNull(ptr);
524-
return new PyObject(ptr);
488+
return ptr.MoveToPyObject();
525489
}
526490

491+
527492
/// <summary>
528493
/// Eval Method
529494
/// </summary>
@@ -742,10 +707,12 @@ public static KeywordArguments kw(params object[] kv)
742707
return dict;
743708
}
744709

745-
public static PyObject Import(string name)
746-
{
747-
return PythonEngine.ImportModule(name);
748-
}
710+
/// <summary>
711+
/// Given a module or package name, import the
712+
/// module and return the resulting module object as a <see cref="PyModule"/>.
713+
/// </summary>
714+
/// <param name="name">Fully-qualified module or package name</param>
715+
public static PyModule Import(string name) => PyModule.Import(name);
749716

750717
public static void SetArgv()
751718
{

0 commit comments

Comments
 (0)