Skip to content

Commit 42af3e6

Browse files
authored
Merge branch 'master' into PR/ReferenceExploration
2 parents 26c4858 + 8ad1062 commit 42af3e6

15 files changed

+202
-47
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
5858
- Reattach python exception traceback information (#545)
5959
- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449])
6060
- Refactored MethodBinder.Bind in preparation to make it extensible (#829)
61+
- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881])
6162
- Look for installed Windows 10 sdk's during installation instead of relying on specific versions.
6263

6364
### Fixed

appveyor.yml

-6
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,16 @@ environment:
2323
BUILD_OPTS: --xplat
2424
- PYTHON_VERSION: 3.7
2525
BUILD_OPTS: --xplat
26-
- PYTHON_VERSION: 3.8
27-
BUILD_OPTS: --xplat
2826
- PYTHON_VERSION: 2.7
2927
- PYTHON_VERSION: 3.5
3028
- PYTHON_VERSION: 3.6
3129
- PYTHON_VERSION: 3.7
32-
- PYTHON_VERSION: 3.8
3330

3431
matrix:
3532
allow_failures:
3633
- PYTHON_VERSION: 3.4
3734
BUILD_OPTS: --xplat
3835
- PYTHON_VERSION: 3.4
39-
- PYTHON_VERSION: 3.8
40-
BUILD_OPTS: --xplat
41-
- PYTHON_VERSION: 3.8
4236

4337
init:
4438
# Update Environment Variables based on matrix/platform

src/embed_tests/Python.EmbeddingTest.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@
8888
<Compile Include="pyimport.cs" />
8989
<Compile Include="pyinitialize.cs" />
9090
<Compile Include="pyrunstring.cs" />
91+
<Compile Include="References.cs" />
9192
<Compile Include="TestConverter.cs" />
9293
<Compile Include="TestCustomMarshal.cs" />
9394
<Compile Include="TestDomainReload.cs" />
9495
<Compile Include="TestExample.cs" />
9596
<Compile Include="TestFinalizer.cs" />
97+
<Compile Include="TestInstanceWrapping.cs" />
9698
<Compile Include="TestPyAnsiString.cs" />
9799
<Compile Include="TestPyFloat.cs" />
98100
<Compile Include="TestPyInt.cs" />

src/embed_tests/References.cs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Python.EmbeddingTest
2+
{
3+
using NUnit.Framework;
4+
using Python.Runtime;
5+
6+
public class References
7+
{
8+
private Py.GILState _gs;
9+
10+
[SetUp]
11+
public void SetUp()
12+
{
13+
_gs = Py.GIL();
14+
}
15+
16+
[TearDown]
17+
public void Dispose()
18+
{
19+
_gs.Dispose();
20+
}
21+
22+
[Test]
23+
public void MoveToPyObject_SetsNull()
24+
{
25+
var dict = new PyDict();
26+
NewReference reference = Runtime.PyDict_Items(dict.Handle);
27+
try
28+
{
29+
Assert.IsFalse(reference.IsNull());
30+
31+
using (reference.MoveToPyObject())
32+
Assert.IsTrue(reference.IsNull());
33+
}
34+
finally
35+
{
36+
reference.Dispose();
37+
}
38+
}
39+
}
40+
}
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Globalization;
3+
using NUnit.Framework;
4+
using Python.Runtime;
5+
6+
namespace Python.EmbeddingTest
7+
{
8+
public class TestInstanceWrapping
9+
{
10+
[OneTimeSetUp]
11+
public void SetUp()
12+
{
13+
PythonEngine.Initialize();
14+
}
15+
16+
[OneTimeTearDown]
17+
public void Dispose()
18+
{
19+
PythonEngine.Shutdown();
20+
}
21+
22+
// regression test for https://github.com/pythonnet/pythonnet/issues/811
23+
[Test]
24+
public void OverloadResolution_UnknownToObject()
25+
{
26+
var overloaded = new Overloaded();
27+
using (Py.GIL())
28+
{
29+
var o = overloaded.ToPython();
30+
31+
dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())");
32+
callWithSelf(o);
33+
Assert.AreEqual(Overloaded.Object, overloaded.Value);
34+
}
35+
}
36+
37+
class Base {}
38+
class Derived: Base { }
39+
40+
class Overloaded: Derived
41+
{
42+
public int Value { get; set; }
43+
public void IntOrStr(int arg) => this.Value = arg;
44+
public void IntOrStr(string arg) =>
45+
this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture);
46+
47+
public const int Object = 1;
48+
public const int ConcreteClass = 2;
49+
public void ObjOrClass(object _) => this.Value = Object;
50+
public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass;
51+
52+
public const int Base = ConcreteClass + 1;
53+
public const int Derived = Base + 1;
54+
public void BaseOrDerived(Base _) => this.Value = Base;
55+
public void BaseOrDerived(Derived _) => this.Value = Derived;
56+
}
57+
}
58+
}

src/runtime/NewReference.cs

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
namespace Python.Runtime
22
{
33
using System;
4+
using System.Diagnostics.Contracts;
5+
46
/// <summary>
57
/// Represents a reference to a Python object, that is tracked by Python's reference counting.
68
/// </summary>
79
[NonCopyable]
810
ref struct NewReference
911
{
1012
IntPtr pointer;
11-
public bool IsNull => this.pointer == IntPtr.Zero;
12-
13-
/// <summary>Gets a raw pointer to the Python object</summary>
14-
public IntPtr DangerousGetAddress()
15-
=> this.IsNull ? throw new NullReferenceException() : this.pointer;
1613

1714
/// <summary>
1815
/// Returns <see cref="PyObject"/> wrapper around this reference, which now owns
1916
/// the pointer. Sets the original reference to <c>null</c>, as it no longer owns it.
2017
/// </summary>
2118
public PyObject MoveToPyObject()
2219
{
23-
if (this.IsNull) throw new NullReferenceException();
20+
if (this.IsNull()) throw new NullReferenceException();
2421

2522
var result = new PyObject(this.pointer);
2623
this.pointer = IntPtr.Zero;
@@ -31,9 +28,32 @@ public PyObject MoveToPyObject()
3128
/// </summary>
3229
public void Dispose()
3330
{
34-
if (!this.IsNull)
31+
if (!this.IsNull())
3532
Runtime.XDecref(this.pointer);
3633
this.pointer = IntPtr.Zero;
3734
}
35+
36+
[Pure]
37+
internal static IntPtr DangerousGetAddress(in NewReference reference)
38+
=> IsNull(reference) ? throw new NullReferenceException() : reference.pointer;
39+
[Pure]
40+
internal static bool IsNull(in NewReference reference)
41+
=> reference.pointer == IntPtr.Zero;
42+
}
43+
44+
/// <summary>
45+
/// These members can not be directly in <see cref="NewReference"/> type,
46+
/// because <c>this</c> is always passed by value, which we need to avoid.
47+
/// (note <code>this in NewReference</code> vs the usual <code>this NewReference</code>)
48+
/// </summary>
49+
static class NewReferenceExtensions
50+
{
51+
/// <summary>Gets a raw pointer to the Python object</summary>
52+
[Pure]
53+
public static IntPtr DangerousGetAddress(this in NewReference reference)
54+
=> NewReference.DangerousGetAddress(reference);
55+
[Pure]
56+
public static bool IsNull(this in NewReference reference)
57+
=> NewReference.IsNull(reference);
3858
}
3959
}

src/runtime/Python.Runtime.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
<Compile Include="pythonengine.cs" />
142142
<Compile Include="pythonexception.cs" />
143143
<Compile Include="pytuple.cs" />
144+
<Compile Include="ReferenceExtensions.cs" />
144145
<Compile Include="runtime.cs" />
145146
<Compile Include="typemanager.cs" />
146147
<Compile Include="typemethod.cs" />

src/runtime/ReferenceExtensions.cs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Python.Runtime
2+
{
3+
using System.Diagnostics.Contracts;
4+
5+
static class ReferenceExtensions
6+
{
7+
/// <summary>
8+
/// Checks if the reference points to Python object <c>None</c>.
9+
/// </summary>
10+
[Pure]
11+
public static bool IsNone(this in NewReference reference)
12+
=> reference.DangerousGetAddress() == Runtime.PyNone;
13+
/// <summary>
14+
/// Checks if the reference points to Python object <c>None</c>.
15+
/// </summary>
16+
[Pure]
17+
public static bool IsNone(this BorrowedReference reference)
18+
=> reference.DangerousGetAddress() == Runtime.PyNone;
19+
}
20+
}

src/runtime/converter.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -398,12 +398,9 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
398398
return ToArray(value, typeof(object[]), out result, setError);
399399
}
400400

401-
if (setError)
402-
{
403-
Exceptions.SetError(Exceptions.TypeError, "value cannot be converted to Object");
404-
}
405-
406-
return false;
401+
Runtime.XIncref(value); // PyObject() assumes ownership
402+
result = new PyObject(value);
403+
return true;
407404
}
408405

409406
// Conversion to 'Type' is done using the same mappings as above for objects.

src/runtime/pydict.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public PyObject Items()
142142
var items = Runtime.PyDict_Items(this.obj);
143143
try
144144
{
145-
if (items.IsNull)
145+
if (items.IsNull())
146146
{
147147
throw new PythonException();
148148
}

src/runtime/pyscope.cs

+23-8
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,19 @@ public PyObject Eval(string code, PyDict locals = null)
278278
Check();
279279
IntPtr _locals = locals == null ? variables : locals.obj;
280280
var flag = (IntPtr)Runtime.Py_eval_input;
281-
IntPtr ptr = Runtime.PyRun_String(
281+
282+
NewReference reference = Runtime.PyRun_String(
282283
code, flag, variables, _locals
283284
);
284-
Runtime.CheckExceptionOccurred();
285-
return new PyObject(ptr);
285+
try
286+
{
287+
Runtime.CheckExceptionOccurred();
288+
return reference.MoveToPyObject();
289+
}
290+
finally
291+
{
292+
reference.Dispose();
293+
}
286294
}
287295

288296
/// <summary>
@@ -316,15 +324,22 @@ public void Exec(string code, PyDict locals = null)
316324
private void Exec(string code, IntPtr _globals, IntPtr _locals)
317325
{
318326
var flag = (IntPtr)Runtime.Py_file_input;
319-
IntPtr ptr = Runtime.PyRun_String(
327+
NewReference reference = Runtime.PyRun_String(
320328
code, flag, _globals, _locals
321329
);
322-
Runtime.CheckExceptionOccurred();
323-
if (ptr != Runtime.PyNone)
330+
331+
try
324332
{
325-
throw new PythonException();
333+
Runtime.CheckExceptionOccurred();
334+
if (!reference.IsNone())
335+
{
336+
throw new PythonException();
337+
}
338+
}
339+
finally
340+
{
341+
reference.Dispose();
326342
}
327-
Runtime.XDecref(ptr);
328343
}
329344

330345
/// <summary>

src/runtime/pythonengine.cs

+15-7
Original file line numberDiff line numberDiff line change
@@ -543,12 +543,13 @@ public static PyObject Eval(string code, IntPtr? globals = null, IntPtr? locals
543543
/// </remarks>
544544
public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = null)
545545
{
546-
PyObject result = RunString(code, globals, locals, RunFlagType.File);
547-
if (result.obj != Runtime.PyNone)
546+
using (PyObject result = RunString(code, globals, locals, RunFlagType.File))
548547
{
549-
throw new PythonException();
548+
if (result.obj != Runtime.PyNone)
549+
{
550+
throw new PythonException();
551+
}
550552
}
551-
result.Dispose();
552553
}
553554

554555

@@ -594,13 +595,20 @@ internal static PyObject RunString(string code, IntPtr? globals, IntPtr? locals,
594595

595596
try
596597
{
597-
IntPtr result = Runtime.PyRun_String(
598+
NewReference result = Runtime.PyRun_String(
598599
code, (IntPtr)flag, globals.Value, locals.Value
599600
);
600601

601-
Runtime.CheckExceptionOccurred();
602+
try
603+
{
604+
Runtime.CheckExceptionOccurred();
602605

603-
return new PyObject(result);
606+
return result.MoveToPyObject();
607+
}
608+
finally
609+
{
610+
result.Dispose();
611+
}
604612
}
605613
finally
606614
{

src/runtime/runtime.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ public static extern int Py_Main(
802802
internal static extern int PyRun_SimpleString(string code);
803803

804804
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
805-
internal static extern IntPtr PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals);
805+
internal static extern NewReference PyRun_String(string code, IntPtr st, IntPtr globals, IntPtr locals);
806806

807807
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
808808
internal static extern IntPtr PyEval_EvalCode(IntPtr co, IntPtr globals, IntPtr locals);

src/tests/test_conversion.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -595,11 +595,10 @@ def test_object_conversion():
595595

596596
# need to test subclass here
597597

598-
with pytest.raises(TypeError):
599-
class Foo(object):
600-
pass
601-
ob = ConversionTest()
602-
ob.ObjectField = Foo
598+
class Foo(object):
599+
pass
600+
ob.ObjectField = Foo
601+
assert ob.ObjectField == Foo
603602

604603

605604
def test_enum_conversion():

0 commit comments

Comments
 (0)