From 2f3562a40ed56ee769ee44dc688721bb873a1f0a Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 23 Sep 2021 11:17:04 -0700 Subject: [PATCH 1/3] Disable implicit conversions, that might lose information: PyInt -> int32 .NET arrays and collections -> Python list (due to mutability) --- CHANGELOG.md | 11 +- src/embed_tests/TestConverter.cs | 19 ++++ src/runtime/converter.cs | 170 +++++++++++++------------------ src/runtime/pyint.cs | 5 + src/runtime/pynumber.cs | 2 + src/runtime/pythonexception.cs | 4 +- 6 files changed, 111 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c769796f8..1f6cb79cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). - .NET arrays implement Python buffer protocol +- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, +and other `PyObject` derived types when called from Python. ### Changed @@ -51,13 +53,20 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru - Sign Runtime DLL with a strong name - Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core -- .NET and Python exceptions are preserved when crossing Python/.NET boundary +- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary - BREAKING: custom encoders are no longer called for instances of `System.Type` - `PythonException.Restore` no longer clears `PythonException` instance. - Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader - BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types - BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will be chosen. +- BREAKING: .NET collections and arrays are no longer automatically converted to +Python collections. Instead, they implement standard Python +collection interfaces from `collections.abc`. +See [Mixins/collections.py](src/runtime/Mixins/collections.py). +- BREAKING: When trying to convert Python `int` to `System.Object`, result will +be of type `PyInt` instead of `System.Int32` due to possible loss of information. +Python `float` will continue to be converted to `System.Double`. ### Fixed diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 875adf8ef..71eb463bf 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -116,6 +116,25 @@ public void ConvertOverflow() } } + [Test] + public void ToNullable() + { + const int Const = 42; + var i = new PyInt(Const); + var ni = i.As(); + Assert.AreEqual(Const, ni); + } + + [Test] + public void ToPyList() + { + var list = new PyList(); + list.Append("hello".ToPython()); + list.Append("world".ToPython()); + var back = list.ToPython().As(); + Assert.AreEqual(list.Length(), back.Length()); + } + [Test] public void RawListProxy() { diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 9dfb6cc45..6bcf3fb59 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -1,8 +1,9 @@ +#nullable enable using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; +using System.Reflection; using System.Runtime.InteropServices; using System.Security; @@ -18,12 +19,10 @@ private Converter() { } - private static NumberFormatInfo nfi; private static Type objectType; private static Type stringType; private static Type singleType; private static Type doubleType; - private static Type decimalType; private static Type int16Type; private static Type int32Type; private static Type int64Type; @@ -32,7 +31,6 @@ private Converter() static Converter() { - nfi = NumberFormatInfo.InvariantInfo; objectType = typeof(Object); stringType = typeof(String); int16Type = typeof(Int16); @@ -40,7 +38,6 @@ static Converter() int64Type = typeof(Int64); singleType = typeof(Single); doubleType = typeof(Double); - decimalType = typeof(Decimal); boolType = typeof(Boolean); typeType = typeof(Type); } @@ -49,7 +46,7 @@ static Converter() /// /// Given a builtin Python type, return the corresponding CLR type. /// - internal static Type GetTypeByAlias(IntPtr op) + internal static Type? GetTypeByAlias(IntPtr op) { if (op == Runtime.PyStringType) return stringType; @@ -132,7 +129,7 @@ private static Func GetIsTransparentProxy() throwOnBindFailure: true); } - internal static IntPtr ToPython(object value, Type type) + internal static IntPtr ToPython(object? value, Type type) { if (value is PyObject) { @@ -161,33 +158,13 @@ internal static IntPtr ToPython(object value, Type type) } } - if (value is IList && !(value is INotifyPropertyChanged) && value.GetType().IsGenericType) - { - using (var resultlist = new PyList()) - { - foreach (object o in (IEnumerable)value) - { - using (var p = new PyObject(ToPython(o, o?.GetType()))) - { - resultlist.Append(p); - } - } - Runtime.XIncref(resultlist.Handle); - return resultlist.Handle; - } - } - if (type.IsInterface) { var ifaceObj = (InterfaceObject)ClassManager.GetClass(type); return ifaceObj.WrapObject(value); } - // We need to special case interface array handling to ensure we - // produce the correct type. Value may be an array of some concrete - // type (FooImpl[]), but we want access to go via the interface type - // (IFoo[]). - if (type.IsArray && type.GetElementType().IsInterface) + if (type.IsArray || type.IsEnum) { return CLRObject.GetInstHandle(value, type); } @@ -245,33 +222,28 @@ internal static IntPtr ToPython(object value, Type type) return Runtime.PyFalse; case TypeCode.Byte: - return Runtime.PyInt_FromInt32((int)((byte)value)); + return Runtime.PyInt_FromInt32((byte)value); case TypeCode.Char: return Runtime.PyUnicode_FromOrdinal((int)((char)value)); case TypeCode.Int16: - return Runtime.PyInt_FromInt32((int)((short)value)); + return Runtime.PyInt_FromInt32((short)value); case TypeCode.Int64: return Runtime.PyLong_FromLongLong((long)value); case TypeCode.Single: - // return Runtime.PyFloat_FromDouble((double)((float)value)); - string ss = ((float)value).ToString(nfi); - IntPtr ps = Runtime.PyString_FromString(ss); - NewReference op = Runtime.PyFloat_FromString(new BorrowedReference(ps));; - Runtime.XDecref(ps); - return op.DangerousMoveToPointerOrNull(); + return Runtime.PyFloat_FromDouble((float)value); case TypeCode.Double: return Runtime.PyFloat_FromDouble((double)value); case TypeCode.SByte: - return Runtime.PyInt_FromInt32((int)((sbyte)value)); + return Runtime.PyInt_FromInt32((sbyte)value); case TypeCode.UInt16: - return Runtime.PyInt_FromInt32((int)((ushort)value)); + return Runtime.PyInt_FromInt32((ushort)value); case TypeCode.UInt32: return Runtime.PyLong_FromUnsignedLong((uint)value); @@ -280,23 +252,7 @@ internal static IntPtr ToPython(object value, Type type) return Runtime.PyLong_FromUnsignedLongLong((ulong)value); default: - if (value is IEnumerable) - { - using (var resultlist = new PyList()) - { - foreach (object o in (IEnumerable)value) - { - using (var p = new PyObject(ToPython(o, o?.GetType()))) - { - resultlist.Append(p); - } - } - Runtime.XIncref(resultlist.Handle); - return resultlist.Handle; - } - } - result = CLRObject.GetInstHandle(value, type); - return result; + return CLRObject.GetInstHandle(value, type); } } @@ -335,7 +291,7 @@ internal static IntPtr ToPythonImplicit(object value) /// If true, call Exceptions.SetError with the reason for failure. /// True on success internal static bool ToManaged(IntPtr value, Type type, - out object result, bool setError) + out object? result, bool setError) { if (type.IsByRef) { @@ -353,14 +309,14 @@ internal static bool ToManaged(IntPtr value, Type type, /// If true, call Exceptions.SetError with the reason for failure. /// True on success internal static bool ToManaged(BorrowedReference value, Type type, - out object result, bool setError) + out object? result, bool setError) => ToManaged(value.DangerousGetAddress(), type, out result, setError); internal static bool ToManagedValue(BorrowedReference value, Type obType, - out object result, bool setError) + out object? result, bool setError) => ToManagedValue(value.DangerousGetAddress(), obType, out result, setError); internal static bool ToManagedValue(IntPtr value, Type obType, - out object result, bool setError) + out object? result, bool setError) { if (obType == typeof(PyObject)) { @@ -369,15 +325,21 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return true; } + if (obType.IsSubclassOf(typeof(PyObject)) + && !obType.IsAbstract + && obType.GetConstructor(new[] { typeof(PyObject) }) is { } ctor) + { + var untyped = new PyObject(new BorrowedReference(value)); + result = ToPyObjectSubclass(ctor, untyped, setError); + return result is not null; + } + // Common case: if the Python value is a wrapped managed object // instance, just return the wrapped object. - ManagedType mt = ManagedType.GetManagedObject(value); result = null; - - if (mt != null) + switch (ManagedType.GetManagedObject(value)) { - if (mt is CLRObject co) - { + case CLRObject co: object tmp = co.inst; if (obType.IsInstanceOfType(tmp)) { @@ -390,9 +352,8 @@ internal static bool ToManagedValue(IntPtr value, Type obType, Exceptions.SetError(Exceptions.TypeError, $"{typeString} value cannot be converted to {obType}"); } return false; - } - if (mt is ClassBase cb) - { + + case ClassBase cb: if (!cb.type.Valid) { Exceptions.SetError(Exceptions.TypeError, cb.type.DeletedMessage); @@ -400,9 +361,6 @@ internal static bool ToManagedValue(IntPtr value, Type obType, } result = cb.type.Value; return true; - } - // shouldn't happen - return false; } if (value == Runtime.PyNone && !obType.IsValueType) @@ -437,7 +395,7 @@ internal static bool ToManagedValue(IntPtr value, Type obType, } // Conversion to 'Object' is done based on some reasonable default - // conversions (Python string -> managed string, Python int -> Int32 etc.). + // conversions (Python string -> managed string). if (obType == objectType) { if (Runtime.IsStringType(value)) @@ -450,28 +408,24 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToPrimitive(value, boolType, out result, setError); } - if (Runtime.PyInt_Check(value)) - { - return ToPrimitive(value, int32Type, out result, setError); - } - - if (Runtime.PyLong_Check(value)) - { - return ToPrimitive(value, int64Type, out result, setError); - } - if (Runtime.PyFloat_Check(value)) { return ToPrimitive(value, doubleType, out result, setError); } - // give custom codecs a chance to take over conversion of sequences + // give custom codecs a chance to take over conversion of ints and sequences IntPtr pyType = Runtime.PyObject_TYPE(value); if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) { return true; } + if (Runtime.PyInt_Check(value)) + { + result = new PyInt(new BorrowedReference(value)); + return true; + } + if (Runtime.PySequence_Check(value)) { return ToArray(value, typeof(object[]), out result, setError); @@ -497,27 +451,27 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return true; } - if (value == Runtime.PyIntType) + if (value == Runtime.PyIntType || value == Runtime.PyLongType) { - result = int32Type; + result = typeof(PyInt); return true; } - if (value == Runtime.PyLongType) + if (value == Runtime.PyFloatType) { - result = int64Type; + result = doubleType; return true; } - if (value == Runtime.PyFloatType) + if (value == Runtime.PyListType) { - result = doubleType; + result = typeof(PyList); return true; } - if (value == Runtime.PyListType || value == Runtime.PyTupleType) + if (value == Runtime.PyTupleType) { - result = typeof(object[]); + result = typeof(PyTuple); return true; } @@ -541,6 +495,30 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToPrimitive(value, obType, out result, setError); } + static object? ToPyObjectSubclass(ConstructorInfo ctor, PyObject instance, bool setError) + { + try + { + return ctor.Invoke(new object[] { instance }); + } + catch (TargetInvocationException ex) + { + if (setError) + { + Exceptions.SetError(ex.InnerException); + } + return null; + } + catch (SecurityException ex) + { + if (setError) + { + Exceptions.SetError(ex.InnerException); + } + return null; + } + } + static bool DecodableByUser(Type type) { TypeCode typeCode = Type.GetTypeCode(type); @@ -563,7 +541,7 @@ internal static int ToInt32(BorrowedReference value) /// /// Convert a Python value to an instance of a primitive managed type. /// - private static bool ToPrimitive(IntPtr value, Type obType, out object result, bool setError) + private static bool ToPrimitive(IntPtr value, Type obType, out object? result, bool setError) { result = null; if (obType.IsEnum) @@ -846,7 +824,6 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo return false; } - private static void SetConversionError(IntPtr value, Type target) { // PyObject_Repr might clear the error @@ -866,7 +843,7 @@ private static void SetConversionError(IntPtr value, Type target) /// The Python value must support the Python iterator protocol or and the /// items in the sequence must be convertible to the target array type. /// - private static bool ToArray(IntPtr value, Type obType, out object result, bool setError) + private static bool ToArray(IntPtr value, Type obType, out object? result, bool setError) { Type elementType = obType.GetElementType(); result = null; @@ -929,9 +906,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero) { - object obj; - - if (!Converter.ToManaged(item, elementType, out obj, setError)) + if (!Converter.ToManaged(item, elementType, out var obj, setError)) { Runtime.XDecref(item); Runtime.XDecref(IterObject); @@ -961,7 +936,8 @@ public static class ConverterExtension { public static PyObject ToPython(this object o) { - return new PyObject(Converter.ToPython(o, o?.GetType())); + if (o is null) return Runtime.None; + return new PyObject(Converter.ToPython(o, o.GetType())); } } } diff --git a/src/runtime/pyint.cs b/src/runtime/pyint.cs index 7b02c68e5..f7e4cdf62 100644 --- a/src/runtime/pyint.cs +++ b/src/runtime/pyint.cs @@ -22,6 +22,11 @@ public PyInt(IntPtr ptr) : base(ptr) { } + internal PyInt(BorrowedReference reference): base(reference) + { + if (!Runtime.PyInt_Check(reference)) throw new ArgumentException("object is not an int"); + } + /// /// PyInt Constructor diff --git a/src/runtime/pynumber.cs b/src/runtime/pynumber.cs index 1af67b4e0..9c2699d6b 100644 --- a/src/runtime/pynumber.cs +++ b/src/runtime/pynumber.cs @@ -17,6 +17,8 @@ protected PyNumber(IntPtr ptr) : base(ptr) { } + internal PyNumber(BorrowedReference reference): base(reference) { } + /// /// IsNumberType Method /// diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 8ca596cb9..42d75d577 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -139,9 +139,9 @@ internal static Exception FetchCurrent() try { - if (Converter.ToManagedValue(pyInfo, typeof(ExceptionDispatchInfo), out object result, setError: false)) + if (Converter.ToManagedValue(pyInfo, typeof(ExceptionDispatchInfo), out object? result, setError: false)) { - return (ExceptionDispatchInfo)result; + return (ExceptionDispatchInfo)result!; } return null; From f8becef17ef8ae9672b62dce2a29c9fba49112e8 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 23 Sep 2021 11:56:06 -0700 Subject: [PATCH 2/3] Introduced PyIterable, PyObject no longer implements IEnumerable --- CHANGELOG.md | 3 +++ src/embed_tests/Codecs.cs | 12 ++++++------ src/runtime/Util.cs | 3 +++ src/runtime/classderived.cs | 11 +++-------- src/runtime/pydict.cs | 28 ++++++++++------------------ src/runtime/pyiterable.cs | 28 ++++++++++++++++++++++++++++ src/runtime/pyobject.cs | 19 ++----------------- src/runtime/pysequence.cs | 7 +++---- src/runtime/pythonexception.cs | 2 +- 9 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 src/runtime/pyiterable.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6cb79cb..b59b2f040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py). - .NET arrays implement Python buffer protocol - Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, and other `PyObject` derived types when called from Python. +- `PyIterable` type, that wraps any iterable object in Python ### Changed @@ -67,6 +68,8 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py). - BREAKING: When trying to convert Python `int` to `System.Object`, result will be of type `PyInt` instead of `System.Int32` due to possible loss of information. Python `float` will continue to be converted to `System.Double`. +- BREAKING: `PyObject` no longer implements `IEnumerable`. +Instead, `PyIterable` does that. ### Fixed diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index b8b1b8c78..1169bca34 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -45,12 +45,12 @@ static void TupleConversionsGeneric() [Test] public void TupleConversionsObject() { - TupleConversionsObject, ValueTuple>(); + TupleConversionsObject, ValueTuple>(); } static void TupleConversionsObject() { TupleCodec.Register(); - var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); T restored = default; using (var scope = Py.CreateScope()) { @@ -66,11 +66,11 @@ static void TupleConversionsObject() [Test] public void TupleRoundtripObject() { - TupleRoundtripObject, ValueTuple>(); + TupleRoundtripObject, ValueTuple>(); } static void TupleRoundtripObject() { - var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); Assert.AreEqual(expected: tuple, actual: restored); @@ -231,7 +231,7 @@ public void IterableDecoderTest() //ensure a PyList can be converted to a plain IEnumerable System.Collections.IEnumerable plainEnumerable1 = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); }); - CollectionAssert.AreEqual(plainEnumerable1, new List { 1, 2, 3 }); + CollectionAssert.AreEqual(plainEnumerable1.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); //can convert to any generic ienumerable. If the type is not assignable from the python element //it will lead to an empty iterable when decoding. TODO - should it throw? @@ -271,7 +271,7 @@ public void IterableDecoderTest() var fooType = foo.GetPythonType(); System.Collections.IEnumerable plainEnumerable2 = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); }); - CollectionAssert.AreEqual(plainEnumerable2, new List { 1, 2, 3 }); + CollectionAssert.AreEqual(plainEnumerable2.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); //can convert to any generic ienumerable. If the type is not assignable from the python element //it will be an exception during TryDecode diff --git a/src/runtime/Util.cs b/src/runtime/Util.cs index 04bc631bb..f48bb5ab8 100644 --- a/src/runtime/Util.cs +++ b/src/runtime/Util.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -68,5 +69,7 @@ internal static string ReadStringResource(this System.Reflection.Assembly assemb using var reader = new StreamReader(stream); return reader.ReadToEnd(); } + + public static IEnumerator GetEnumerator(this IEnumerator enumerator) => enumerator; } } diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index cc2397225..e0105afab 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -174,7 +174,7 @@ internal static Type CreateDerivedType(string name, { Runtime.XIncref(py_dict); using (var dict = new PyDict(py_dict)) - using (PyObject keys = dict.Keys()) + using (PyIterable keys = dict.Keys()) { foreach (PyObject pyKey in keys) { @@ -223,7 +223,7 @@ internal static Type CreateDerivedType(string name, { Runtime.XIncref(py_dict); using (var dict = new PyDict(py_dict)) - using (PyObject keys = dict.Keys()) + using (PyIterable keys = dict.Keys()) { foreach (PyObject pyKey in keys) { @@ -439,7 +439,7 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde } using (PyObject pyReturnType = func.GetAttr("_clr_return_type_")) - using (PyObject pyArgTypes = func.GetAttr("_clr_arg_types_")) + using (var pyArgTypes = PyIter.GetIter(func.GetAttr("_clr_arg_types_"))) { var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type; if (returnType == null) @@ -447,11 +447,6 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde returnType = typeof(void); } - if (!pyArgTypes.IsIterable()) - { - throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); - } - var argTypes = new List(); foreach (PyObject pyArgType in pyArgTypes) { diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index 0a5b2ad82..a715e2e08 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -8,7 +8,7 @@ namespace Python.Runtime /// PY3: https://docs.python.org/3/c-api/dict.html /// for details. /// - public class PyDict : PyObject + public class PyDict : PyIterable { /// /// PyDict Constructor @@ -102,14 +102,14 @@ public bool HasKey(string key) /// /// Returns a sequence containing the keys of the dictionary. /// - public PyObject Keys() + public PyIterable Keys() { using var items = Runtime.PyDict_Keys(Reference); if (items.IsNull()) { throw PythonException.ThrowLastAsClrException(); } - return items.MoveToPyObject(); + return new PyIterable(items.Steal()); } @@ -119,14 +119,14 @@ public PyObject Keys() /// /// Returns a sequence containing the values of the dictionary. /// - public PyObject Values() + public PyIterable Values() { IntPtr items = Runtime.PyDict_Values(obj); if (items == IntPtr.Zero) { throw PythonException.ThrowLastAsClrException(); } - return new PyObject(items); + return new PyIterable(items); } @@ -136,22 +136,14 @@ public PyObject Values() /// /// Returns a sequence containing the items of the dictionary. /// - public PyObject Items() + public PyIterable Items() { - var items = Runtime.PyDict_Items(this.Reference); - try - { - if (items.IsNull()) - { - throw PythonException.ThrowLastAsClrException(); - } - - return items.MoveToPyObject(); - } - finally + using var items = Runtime.PyDict_Items(this.Reference); + if (items.IsNull()) { - items.Dispose(); + throw PythonException.ThrowLastAsClrException(); } + return new PyIterable(items.Steal()); } diff --git a/src/runtime/pyiterable.cs b/src/runtime/pyiterable.cs new file mode 100644 index 000000000..47e6984d7 --- /dev/null +++ b/src/runtime/pyiterable.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Python.Runtime +{ + public class PyIterable : PyObject, IEnumerable + { + internal PyIterable(IntPtr ptr) : base(ptr) + { + } + + internal PyIterable(BorrowedReference reference) : base(reference) { } + internal PyIterable(in StolenReference reference) : base(reference) { } + + /// + /// Return a new PyIter object for the object. This allows any iterable + /// python object to be iterated over in C#. A PythonException will be + /// raised if the object is not iterable. + /// + public PyIter GetEnumerator() + { + return PyIter.GetIter(this); + } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index a684dab88..f1e72df9c 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -17,7 +17,7 @@ namespace Python.Runtime /// [Serializable] [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - public partial class PyObject : DynamicObject, IEnumerable, IDisposable + public partial class PyObject : DynamicObject, IDisposable { #if TRACE_ALLOC /// @@ -80,7 +80,7 @@ internal PyObject(BorrowedReference reference) #endif } - internal PyObject(StolenReference reference) + internal PyObject(in StolenReference reference) { if (reference == null) throw new ArgumentNullException(nameof(reference)); @@ -703,21 +703,6 @@ public PyObject GetIterator() return new PyObject(r); } - /// - /// GetEnumerator Method - /// - /// - /// Return a new PyIter object for the object. This allows any iterable - /// python object to be iterated over in C#. A PythonException will be - /// raised if the object is not iterable. - /// - public IEnumerator GetEnumerator() - { - return PyIter.GetIter(this); - } - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - - /// /// Invoke Method /// diff --git a/src/runtime/pysequence.cs b/src/runtime/pysequence.cs index 536cd3e46..463c2ec52 100644 --- a/src/runtime/pysequence.cs +++ b/src/runtime/pysequence.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; namespace Python.Runtime { @@ -10,13 +9,14 @@ namespace Python.Runtime /// PY3: https://docs.python.org/3/c-api/sequence.html /// for details. /// - public class PySequence : PyObject, IEnumerable + public class PySequence : PyIterable { - protected PySequence(IntPtr ptr) : base(ptr) + protected internal PySequence(IntPtr ptr) : base(ptr) { } internal PySequence(BorrowedReference reference) : base(reference) { } + internal PySequence(StolenReference reference) : base(reference) { } /// @@ -30,7 +30,6 @@ public static bool IsSequenceType(PyObject value) return Runtime.PySequence_Check(value.obj); } - /// /// GetSlice Method /// diff --git a/src/runtime/pythonexception.cs b/src/runtime/pythonexception.cs index 42d75d577..f663b3c02 100644 --- a/src/runtime/pythonexception.cs +++ b/src/runtime/pythonexception.cs @@ -387,7 +387,7 @@ public string Format() using var traceback = PyModule.Import("traceback"); var buffer = new StringBuilder(); using var values = traceback.InvokeMethod("format_exception", copy.Type, copy.Value, copy.Traceback); - foreach (PyObject val in values) + foreach (PyObject val in PyIter.GetIter(values)) { buffer.Append(val); val.Dispose(); From b2e6d4ed4a5500e92d79749130b78fd2a15c0dc9 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 23 Sep 2021 13:37:29 -0700 Subject: [PATCH 3/3] fixed conversion tests to match new behavior --- tests/test_array.py | 7 ++++--- tests/test_conversion.py | 10 ++-------- tests/test_field.py | 2 +- tests/test_generic.py | 8 ++++---- tests/test_method.py | 4 ++-- tests/test_module.py | 2 +- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index d6f08a961..d207a36fb 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -8,6 +8,7 @@ import pytest from collections import UserList +from System import Single as float32 def test_public_array(): @@ -533,8 +534,8 @@ def test_single_array(): assert items[0] == 0.0 assert items[4] == 4.0 - max_ = 3.402823e38 - min_ = -3.402823e38 + max_ = float32(3.402823e38) + min_ = float32(-3.402823e38) items[0] = max_ assert items[0] == max_ @@ -1291,7 +1292,7 @@ def test_special_array_creation(): value = Array[System.Single]([0.0, 3.402823e38]) assert value[0] == 0.0 - assert value[1] == 3.402823e38 + assert value[1] == System.Single(3.402823e38) assert value.Length == 2 value = Array[System.Double]([0.0, 1.7976931348623157e308]) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 3322b836f..c895951e1 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -413,16 +413,10 @@ def test_single_conversion(): assert ob.SingleField == 0.0 ob.SingleField = 3.402823e38 - assert ob.SingleField == 3.402823e38 + assert ob.SingleField == System.Single(3.402823e38) ob.SingleField = -3.402823e38 - assert ob.SingleField == -3.402823e38 - - ob.SingleField = System.Single(3.402823e38) - assert ob.SingleField == 3.402823e38 - - ob.SingleField = System.Single(-3.402823e38) - assert ob.SingleField == -3.402823e38 + assert ob.SingleField == System.Single(-3.402823e38) with pytest.raises(TypeError): ConversionTest().SingleField = "spam" diff --git a/tests/test_field.py b/tests/test_field.py index d187de5d2..0becd99e5 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -300,7 +300,7 @@ def test_single_field(): assert ob.SingleField == 0.0 ob.SingleField = 1.1 - assert ob.SingleField == 1.1 + assert ob.SingleField == System.Single(1.1) def test_double_field(): diff --git a/tests/test_generic.py b/tests/test_generic.py index 248303179..9e1f1226b 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -259,7 +259,7 @@ def test_generic_type_binding(): assert_generic_wrapper_by_type(System.UInt16, 65000) assert_generic_wrapper_by_type(System.UInt32, 4294967295) assert_generic_wrapper_by_type(System.UInt64, 18446744073709551615) - assert_generic_wrapper_by_type(System.Single, 3.402823e38) + assert_generic_wrapper_by_type(System.Single, System.Single(3.402823e38)) assert_generic_wrapper_by_type(System.Double, 1.7976931348623157e308) assert_generic_wrapper_by_type(float, 1.7976931348623157e308) assert_generic_wrapper_by_type(System.Decimal, System.Decimal.One) @@ -309,7 +309,7 @@ def test_generic_method_type_handling(): assert_generic_method_by_type(System.Int32, 2147483647) assert_generic_method_by_type(int, 2147483647) assert_generic_method_by_type(System.UInt16, 65000) - assert_generic_method_by_type(System.Single, 3.402823e38) + assert_generic_method_by_type(System.Single, System.Single(3.402823e38)) assert_generic_method_by_type(System.Double, 1.7976931348623157e308) assert_generic_method_by_type(float, 1.7976931348623157e308) assert_generic_method_by_type(System.Decimal, System.Decimal.One) @@ -504,7 +504,7 @@ def test_method_overload_selection_with_generic_types(): vtype = GenericWrapper[System.Single] input_ = vtype(3.402823e38) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value.value == 3.402823e38 + assert value.value == System.Single(3.402823e38) vtype = GenericWrapper[System.Double] input_ = vtype(1.7976931348623157e308) @@ -663,7 +663,7 @@ def test_overload_selection_with_arrays_of_generic_types(): vtype = System.Array[gtype] input_ = vtype([gtype(3.402823e38), gtype(3.402823e38)]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0].value == 3.402823e38 + assert value[0].value == System.Single(3.402823e38) assert value.Length == 2 gtype = GenericWrapper[System.Double] diff --git a/tests/test_method.py b/tests/test_method.py index 4c5255fab..e81652b54 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -510,7 +510,7 @@ def test_explicit_overload_selection(): assert value == 18446744073709551615 value = MethodTest.Overloaded.__overloads__[System.Single](3.402823e38) - assert value == 3.402823e38 + assert value == System.Single(3.402823e38) value = MethodTest.Overloaded.__overloads__[System.Double]( 1.7976931348623157e308) @@ -645,7 +645,7 @@ def test_overload_selection_with_array_types(): input_ = vtype([0.0, 3.402823e38]) value = MethodTest.Overloaded.__overloads__[vtype](input_) assert value[0] == 0.0 - assert value[1] == 3.402823e38 + assert value[1] == System.Single(3.402823e38) vtype = Array[System.Double] input_ = vtype([0.0, 1.7976931348623157e308]) diff --git a/tests/test_module.py b/tests/test_module.py index 3737dccf6..6949f2712 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -357,7 +357,7 @@ def test_clr_get_clr_type(): comparable = GetClrType(IComparable) assert comparable.FullName == "System.IComparable" assert comparable.IsInterface - assert GetClrType(int).FullName == "System.Int32" + assert GetClrType(int).FullName == "Python.Runtime.PyInt" assert GetClrType(str).FullName == "System.String" assert GetClrType(float).FullName == "System.Double" dblarr = System.Array[System.Double]