Skip to content

Commit 182faed

Browse files
committed
allow creating new .NET arrays from Python using Array[T](dim1, dim2, ...) syntax
fixes #251
1 parent c81c3c3 commit 182faed

File tree

8 files changed

+143
-6
lines changed

8 files changed

+143
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
99

1010
### Added
1111

12+
- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax
13+
1214
### Changed
1315
- Drop support for Python 2, 3.4, and 3.5
1416
- `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more

src/runtime/BorrowedReference.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ public BorrowedReference(IntPtr pointer)
2222
{
2323
this.pointer = pointer;
2424
}
25+
26+
public static bool operator ==(BorrowedReference a, BorrowedReference b)
27+
=> a.pointer == b.pointer;
28+
public static bool operator !=(BorrowedReference a, BorrowedReference b)
29+
=> a.pointer != b.pointer;
2530
}
2631
}

src/runtime/NewReference.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public PyObject MoveToPyObject()
2828
return result;
2929
}
3030

31+
/// <summary>Moves ownership of this instance to unmanged pointer</summary>
32+
public IntPtr DangerousMoveToPointerOrNull()
33+
{
34+
var result = this.pointer;
35+
this.pointer = IntPtr.Zero;
36+
return result;
37+
}
38+
3139
/// <summary>
3240
/// Removes this reference to a Python object, and sets it to <c>null</c>.
3341
/// </summary>

src/runtime/arrayobject.cs

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,109 @@ internal override bool CanSubclass()
2020
return false;
2121
}
2222

23-
public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
23+
public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw)
2424
{
25+
if (kw != IntPtr.Zero)
26+
{
27+
return Exceptions.RaiseTypeError("array constructor takes no keyword arguments");
28+
}
29+
30+
var tp = new BorrowedReference(tpRaw);
31+
2532
var self = GetManagedObject(tp) as ArrayObject;
26-
if (Runtime.PyTuple_Size(args) != 1)
33+
34+
long[] dimensions = new long[Runtime.PyTuple_Size(args)];
35+
if (dimensions.Length == 0)
2736
{
28-
return Exceptions.RaiseTypeError("array expects 1 argument");
37+
return Exceptions.RaiseTypeError("array constructor requires at least one integer argument or an object convertible to array");
2938
}
39+
if (dimensions.Length != 1)
40+
{
41+
return CreateMultidimensional(self.type.GetElementType(), dimensions,
42+
shapeTuple: new BorrowedReference(args),
43+
pyType: tp)
44+
.DangerousMoveToPointerOrNull();
45+
}
46+
3047
IntPtr op = Runtime.PyTuple_GetItem(args, 0);
48+
49+
// create single dimensional array
50+
if (Runtime.PyInt_Check(op))
51+
{
52+
dimensions[0] = Runtime.PyLong_AsLongLong(op);
53+
if (dimensions[0] == -1 && Exceptions.ErrorOccurred())
54+
{
55+
Exceptions.Clear();
56+
}
57+
else
58+
{
59+
return NewInstance(self.type.GetElementType(), tp, dimensions)
60+
.DangerousMoveToPointerOrNull();
61+
}
62+
}
3163
object result;
3264

65+
// this implements casting to Array[T]
3366
if (!Converter.ToManaged(op, self.type, out result, true))
3467
{
3568
return IntPtr.Zero;
3669
}
37-
return CLRObject.GetInstHandle(result, tp);
70+
return CLRObject.GetInstHandle(result, tp)
71+
.DangerousGetAddress();
72+
}
73+
74+
static NewReference CreateMultidimensional(Type elementType, long[] dimensions, BorrowedReference shapeTuple, BorrowedReference pyType)
75+
{
76+
for (int dimIndex = 0; dimIndex < dimensions.Length; dimIndex++)
77+
{
78+
BorrowedReference dimObj = Runtime.PyTuple_GetItem(shapeTuple, dimIndex);
79+
PythonException.ThrowIfIsNull(dimObj);
80+
81+
if (!Runtime.PyInt_Check(dimObj))
82+
{
83+
Exceptions.RaiseTypeError("array constructor expects integer dimensions");
84+
return default;
85+
}
86+
87+
dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj);
88+
if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred())
89+
{
90+
Exceptions.RaiseTypeError("array constructor expects integer dimensions");
91+
return default;
92+
}
93+
}
94+
95+
return NewInstance(elementType, pyType, dimensions);
96+
}
97+
98+
static NewReference NewInstance(Type elementType, BorrowedReference arrayPyType, long[] dimensions)
99+
{
100+
object result;
101+
try
102+
{
103+
result = Array.CreateInstance(elementType, dimensions);
104+
}
105+
catch (ArgumentException badArgument)
106+
{
107+
Exceptions.SetError(Exceptions.ValueError, badArgument.Message);
108+
return default;
109+
}
110+
catch (OverflowException overflow)
111+
{
112+
Exceptions.SetError(overflow);
113+
return default;
114+
}
115+
catch (NotSupportedException notSupported)
116+
{
117+
Exceptions.SetError(notSupported);
118+
return default;
119+
}
120+
catch (OutOfMemoryException oom)
121+
{
122+
Exceptions.SetError(Exceptions.MemoryError, oom.Message);
123+
return default;
124+
}
125+
return CLRObject.GetInstHandle(result, arrayPyType);
38126
}
39127

40128

src/runtime/clrobject.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ static CLRObject GetInstance(object ob)
5151
return GetInstance(ob, cc.tpHandle);
5252
}
5353

54-
54+
internal static NewReference GetInstHandle(object ob, BorrowedReference pyType)
55+
{
56+
CLRObject co = GetInstance(ob, pyType.DangerousGetAddress());
57+
return NewReference.DangerousFromPointer(co.pyHandle);
58+
}
5559
internal static IntPtr GetInstHandle(object ob, IntPtr pyType)
5660
{
5761
CLRObject co = GetInstance(ob, pyType);

src/runtime/managedtype.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ internal void FreeGCHandle()
7575
}
7676
}
7777

78+
internal static ManagedType GetManagedObject(BorrowedReference ob)
79+
=> GetManagedObject(ob.DangerousGetAddress());
7880
/// <summary>
7981
/// Given a Python object, return the associated managed object or null.
8082
/// </summary>

src/runtime/runtime.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,8 @@ internal static unsafe IntPtr PyObject_TYPE(IntPtr op)
10131013
? new IntPtr((void*)(*((uint*)p + n)))
10141014
: new IntPtr((void*)(*((ulong*)p + n)));
10151015
}
1016+
internal static unsafe BorrowedReference PyObject_TYPE(BorrowedReference op)
1017+
=> new BorrowedReference(PyObject_TYPE(op.DangerousGetAddress()));
10161018

10171019
/// <summary>
10181020
/// Managed version of the standard Python C API PyObject_Type call.
@@ -1202,6 +1204,8 @@ internal static long PyObject_Size(IntPtr pointer)
12021204
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12031205
internal static extern bool PyNumber_Check(IntPtr ob);
12041206

1207+
internal static bool PyInt_Check(BorrowedReference ob)
1208+
=> PyObject_TypeCheck(ob, new BorrowedReference(PyIntType));
12051209
internal static bool PyInt_Check(IntPtr ob)
12061210
{
12071211
return PyObject_TypeCheck(ob, PyIntType);
@@ -1291,6 +1295,8 @@ internal static object PyLong_AsUnsignedLong(IntPtr value)
12911295
return PyLong_AsUnsignedLong64(value);
12921296
}
12931297

1298+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1299+
internal static extern long PyLong_AsLongLong(BorrowedReference value);
12941300
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12951301
internal static extern long PyLong_AsLongLong(IntPtr value);
12961302

@@ -1829,11 +1835,15 @@ internal static IntPtr PyTuple_New(long size)
18291835
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
18301836
private static extern IntPtr PyTuple_New(IntPtr size);
18311837

1838+
internal static BorrowedReference PyTuple_GetItem(BorrowedReference pointer, long index)
1839+
=> PyTuple_GetItem(pointer, new IntPtr(index));
18321840
internal static IntPtr PyTuple_GetItem(IntPtr pointer, long index)
18331841
{
18341842
return PyTuple_GetItem(pointer, new IntPtr(index));
18351843
}
18361844

1845+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1846+
private static extern BorrowedReference PyTuple_GetItem(BorrowedReference pointer, IntPtr index);
18371847
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
18381848
private static extern IntPtr PyTuple_GetItem(IntPtr pointer, IntPtr index);
18391849

@@ -1950,10 +1960,14 @@ internal static bool PyType_Check(IntPtr ob)
19501960

19511961
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
19521962
internal static extern bool PyType_IsSubtype(IntPtr t1, IntPtr t2);
1963+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1964+
internal static extern bool PyType_IsSubtype(BorrowedReference t1, BorrowedReference t2);
19531965

19541966
internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp)
1967+
=> PyObject_TypeCheck(new BorrowedReference(ob), new BorrowedReference(tp));
1968+
internal static bool PyObject_TypeCheck(BorrowedReference ob, BorrowedReference tp)
19551969
{
1956-
IntPtr t = PyObject_TYPE(ob);
1970+
BorrowedReference t = PyObject_TYPE(ob);
19571971
return (t == tp) || PyType_IsSubtype(t, tp);
19581972
}
19591973

src/tests/test_array.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,20 @@ def test_boxed_value_type_mutation_result():
11741174
assert items[i].X == i + 1
11751175
assert items[i].Y == i + 1
11761176

1177+
def test_create_array_from_shape():
1178+
from System import Array
1179+
1180+
value = Array[int](3)
1181+
assert value[1] == 0
1182+
assert value.Length == 3
1183+
1184+
value = Array[int](3, 4)
1185+
assert value[1, 1] == 0
1186+
assert value.GetLength(0) == 3
1187+
assert value.GetLength(1) == 4
1188+
1189+
with pytest.raises(ValueError):
1190+
Array[int](-1)
11771191

11781192
def test_special_array_creation():
11791193
"""Test using the Array[<type>] syntax for creating arrays."""

0 commit comments

Comments
 (0)