Skip to content

Commit b13bafd

Browse files
committed
Introduced PyIterable, PyObject no longer implements IEnumerable<PyObject>
1 parent 5370dc8 commit b13bafd

File tree

9 files changed

+59
-54
lines changed

9 files changed

+59
-54
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py).
2222
- .NET arrays implement Python buffer protocol
2323
- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`,
2424
and other `PyObject` derived types when called from Python.
25+
- `PyIterable` type, that wraps any iterable object in Python
2526

2627

2728
### Changed
@@ -67,6 +68,8 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py).
6768
- BREAKING: When trying to convert Python `int` to `System.Object`, result will
6869
be of type `PyInt` instead of `System.Int32` due to possible loss of information.
6970
Python `float` will continue to be converted to `System.Double`.
71+
- BREAKING: `PyObject` no longer implements `IEnumerable<PyObject>`.
72+
Instead, `PyIterable` does that.
7073

7174
### Fixed
7275

src/embed_tests/Codecs.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ static void TupleConversionsGeneric<T, TTuple>()
4545
[Test]
4646
public void TupleConversionsObject()
4747
{
48-
TupleConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
48+
TupleConversionsObject<ValueTuple<double, string, object>, ValueTuple>();
4949
}
5050
static void TupleConversionsObject<T, TTuple>()
5151
{
5252
TupleCodec<TTuple>.Register();
53-
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
53+
var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object());
5454
T restored = default;
5555
using (var scope = Py.CreateScope())
5656
{
@@ -66,11 +66,11 @@ static void TupleConversionsObject<T, TTuple>()
6666
[Test]
6767
public void TupleRoundtripObject()
6868
{
69-
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
69+
TupleRoundtripObject<ValueTuple<double, string, object>, ValueTuple>();
7070
}
7171
static void TupleRoundtripObject<T, TTuple>()
7272
{
73-
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
73+
var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object());
7474
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
7575
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
7676
Assert.AreEqual(expected: tuple, actual: restored);
@@ -231,7 +231,7 @@ public void IterableDecoderTest()
231231
//ensure a PyList can be converted to a plain IEnumerable
232232
System.Collections.IEnumerable plainEnumerable1 = null;
233233
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); });
234-
CollectionAssert.AreEqual(plainEnumerable1, new List<object> { 1, 2, 3 });
234+
CollectionAssert.AreEqual(plainEnumerable1.Cast<PyInt>().Select(i => i.ToInt32()), new List<object> { 1, 2, 3 });
235235

236236
//can convert to any generic ienumerable. If the type is not assignable from the python element
237237
//it will lead to an empty iterable when decoding. TODO - should it throw?
@@ -271,7 +271,7 @@ public void IterableDecoderTest()
271271
var fooType = foo.GetPythonType();
272272
System.Collections.IEnumerable plainEnumerable2 = null;
273273
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); });
274-
CollectionAssert.AreEqual(plainEnumerable2, new List<object> { 1, 2, 3 });
274+
CollectionAssert.AreEqual(plainEnumerable2.Cast<PyInt>().Select(i => i.ToInt32()), new List<object> { 1, 2, 3 });
275275

276276
//can convert to any generic ienumerable. If the type is not assignable from the python element
277277
//it will be an exception during TryDecode

src/runtime/Util.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable enable
22
using System;
3+
using System.Collections.Generic;
34
using System.IO;
45
using System.Runtime.InteropServices;
56

@@ -68,5 +69,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb
6869
using var reader = new StreamReader(stream);
6970
return reader.ReadToEnd();
7071
}
72+
73+
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
7174
}
7275
}

src/runtime/classderived.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ internal static Type CreateDerivedType(string name,
174174
{
175175
Runtime.XIncref(py_dict);
176176
using (var dict = new PyDict(py_dict))
177-
using (PyObject keys = dict.Keys())
177+
using (PyIterable keys = dict.Keys())
178178
{
179179
foreach (PyObject pyKey in keys)
180180
{
@@ -223,7 +223,7 @@ internal static Type CreateDerivedType(string name,
223223
{
224224
Runtime.XIncref(py_dict);
225225
using (var dict = new PyDict(py_dict))
226-
using (PyObject keys = dict.Keys())
226+
using (PyIterable keys = dict.Keys())
227227
{
228228
foreach (PyObject pyKey in keys)
229229
{
@@ -439,19 +439,14 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde
439439
}
440440

441441
using (PyObject pyReturnType = func.GetAttr("_clr_return_type_"))
442-
using (PyObject pyArgTypes = func.GetAttr("_clr_arg_types_"))
442+
using (var pyArgTypes = PyIter.GetIter(func.GetAttr("_clr_arg_types_")))
443443
{
444444
var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type;
445445
if (returnType == null)
446446
{
447447
returnType = typeof(void);
448448
}
449449

450-
if (!pyArgTypes.IsIterable())
451-
{
452-
throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types");
453-
}
454-
455450
var argTypes = new List<Type>();
456451
foreach (PyObject pyArgType in pyArgTypes)
457452
{

src/runtime/pydict.cs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Python.Runtime
88
/// PY3: https://docs.python.org/3/c-api/dict.html
99
/// for details.
1010
/// </summary>
11-
public class PyDict : PyObject
11+
public class PyDict : PyIterable
1212
{
1313
/// <summary>
1414
/// PyDict Constructor
@@ -102,14 +102,14 @@ public bool HasKey(string key)
102102
/// <remarks>
103103
/// Returns a sequence containing the keys of the dictionary.
104104
/// </remarks>
105-
public PyObject Keys()
105+
public PyIterable Keys()
106106
{
107107
using var items = Runtime.PyDict_Keys(Reference);
108108
if (items.IsNull())
109109
{
110110
throw PythonException.ThrowLastAsClrException();
111111
}
112-
return items.MoveToPyObject();
112+
return new PyIterable(items.Steal());
113113
}
114114

115115

@@ -119,14 +119,14 @@ public PyObject Keys()
119119
/// <remarks>
120120
/// Returns a sequence containing the values of the dictionary.
121121
/// </remarks>
122-
public PyObject Values()
122+
public PyIterable Values()
123123
{
124124
IntPtr items = Runtime.PyDict_Values(obj);
125125
if (items == IntPtr.Zero)
126126
{
127127
throw PythonException.ThrowLastAsClrException();
128128
}
129-
return new PyObject(items);
129+
return new PyIterable(items);
130130
}
131131

132132

@@ -136,22 +136,14 @@ public PyObject Values()
136136
/// <remarks>
137137
/// Returns a sequence containing the items of the dictionary.
138138
/// </remarks>
139-
public PyObject Items()
139+
public PyIterable Items()
140140
{
141-
var items = Runtime.PyDict_Items(this.Reference);
142-
try
143-
{
144-
if (items.IsNull())
145-
{
146-
throw PythonException.ThrowLastAsClrException();
147-
}
148-
149-
return items.MoveToPyObject();
150-
}
151-
finally
141+
using var items = Runtime.PyDict_Items(this.Reference);
142+
if (items.IsNull())
152143
{
153-
items.Dispose();
144+
throw PythonException.ThrowLastAsClrException();
154145
}
146+
return new PyIterable(items.Steal());
155147
}
156148

157149

src/runtime/pyiterable.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace Python.Runtime
6+
{
7+
public class PyIterable : PyObject, IEnumerable<PyObject>
8+
{
9+
internal PyIterable(IntPtr ptr) : base(ptr)
10+
{
11+
}
12+
13+
internal PyIterable(BorrowedReference reference) : base(reference) { }
14+
internal PyIterable(in StolenReference reference) : base(reference) { }
15+
16+
/// <summary>
17+
/// Return a new PyIter object for the object. This allows any iterable
18+
/// python object to be iterated over in C#. A PythonException will be
19+
/// raised if the object is not iterable.
20+
/// </summary>
21+
public PyIter GetEnumerator()
22+
{
23+
return PyIter.GetIter(this);
24+
}
25+
IEnumerator<PyObject> IEnumerable<PyObject>.GetEnumerator() => this.GetEnumerator();
26+
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
27+
}
28+
}

src/runtime/pyobject.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Python.Runtime
1717
/// </summary>
1818
[Serializable]
1919
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
20-
public partial class PyObject : DynamicObject, IEnumerable<PyObject>, IDisposable
20+
public partial class PyObject : DynamicObject, IDisposable
2121
{
2222
#if TRACE_ALLOC
2323
/// <summary>
@@ -80,7 +80,7 @@ internal PyObject(BorrowedReference reference)
8080
#endif
8181
}
8282

83-
internal PyObject(StolenReference reference)
83+
internal PyObject(in StolenReference reference)
8484
{
8585
if (reference == null) throw new ArgumentNullException(nameof(reference));
8686

@@ -703,21 +703,6 @@ public PyObject GetIterator()
703703
return new PyObject(r);
704704
}
705705

706-
/// <summary>
707-
/// GetEnumerator Method
708-
/// </summary>
709-
/// <remarks>
710-
/// Return a new PyIter object for the object. This allows any iterable
711-
/// python object to be iterated over in C#. A PythonException will be
712-
/// raised if the object is not iterable.
713-
/// </remarks>
714-
public IEnumerator<PyObject> GetEnumerator()
715-
{
716-
return PyIter.GetIter(this);
717-
}
718-
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
719-
720-
721706
/// <summary>
722707
/// Invoke Method
723708
/// </summary>

src/runtime/pysequence.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections;
32

43
namespace Python.Runtime
54
{
@@ -10,13 +9,14 @@ namespace Python.Runtime
109
/// PY3: https://docs.python.org/3/c-api/sequence.html
1110
/// for details.
1211
/// </summary>
13-
public class PySequence : PyObject, IEnumerable
12+
public class PySequence : PyIterable
1413
{
15-
protected PySequence(IntPtr ptr) : base(ptr)
14+
protected internal PySequence(IntPtr ptr) : base(ptr)
1615
{
1716
}
1817

1918
internal PySequence(BorrowedReference reference) : base(reference) { }
19+
internal PySequence(StolenReference reference) : base(reference) { }
2020

2121

2222
/// <summary>
@@ -30,7 +30,6 @@ public static bool IsSequenceType(PyObject value)
3030
return Runtime.PySequence_Check(value.obj);
3131
}
3232

33-
3433
/// <summary>
3534
/// GetSlice Method
3635
/// </summary>

src/runtime/pythonexception.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public string Format()
387387
using var traceback = PyModule.Import("traceback");
388388
var buffer = new StringBuilder();
389389
using var values = traceback.InvokeMethod("format_exception", copy.Type, copy.Value, copy.Traceback);
390-
foreach (PyObject val in values)
390+
foreach (PyObject val in PyIter.GetIter(values))
391391
{
392392
buffer.Append(val);
393393
val.Dispose();

0 commit comments

Comments
 (0)