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] 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