Skip to content

Commit 96cee3e

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

File tree

8 files changed

+144
-6
lines changed

8 files changed

+144
-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: 9 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>
@@ -67,6 +75,7 @@ static class NewReferenceExtensions
6775
[Pure]
6876
public static IntPtr DangerousGetAddress(this in NewReference reference)
6977
=> NewReference.DangerousGetAddress(reference);
78+
/// <summary>Gets a raw pointer to the Python object</summary>
7079
[Pure]
7180
public static bool IsNull(this in NewReference reference)
7281
=> NewReference.IsNull(reference);

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy