From 93d48406c49cf50fff2e01aee84e9a00ee0bc0f6 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Sat, 2 Oct 2021 10:33:37 -0700 Subject: [PATCH] disabled using __float__ during implicit conversions to .NET floating point types arbitrary Python objects are no longer implicitly converted to .NET bool type this is a continuation of https://github.com/pythonnet/pythonnet/pull/1568 --- CHANGELOG.md | 3 + README.rst | 2 +- src/embed_tests/NumPyTests.cs | 2 +- src/embed_tests/TestConverter.cs | 7 ++ src/python_tests_runner/PythonTestRunner.cs | 4 +- src/runtime/converter.cs | 73 ++++++++++++++++++++- src/runtime/pyobject.cs | 16 ++++- tests/test_conversion.py | 47 +++++-------- tests/test_delegate.py | 8 ++- tests/test_field.py | 8 +-- tests/test_indexer.py | 14 ++-- tests/test_module.py | 4 -- 12 files changed, 134 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b0aed48..35ef66882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,9 @@ 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: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to +`System.Single` and `System.Double`. An explicit conversion is required on Python or .NET side. +- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`. - BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions). - BREAKING: `PyObject` no longer implements `IEnumerable`. Instead, `PyIterable` does that. diff --git a/README.rst b/README.rst index c0e4229d3..18e15a7b2 100644 --- a/README.rst +++ b/README.rst @@ -77,7 +77,7 @@ Example dynamic sin = np.sin; Console.WriteLine(sin(5)); - double c = np.cos(5) + sin(5); + double c = (double)(np.cos(5) + sin(5)); Console.WriteLine(c); dynamic a = np.array(new List { 1, 2, 3 }); diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index f31f7b25b..8b76f4ca1 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -40,7 +40,7 @@ public void TestReadme() dynamic sin = np.sin; StringAssert.StartsWith("-0.95892", sin(5).ToString()); - double c = np.cos(5) + sin(5); + double c = (double)(np.cos(5) + sin(5)); Assert.AreEqual(-0.675262, c, 0.01); dynamic a = np.array(new List { 1, 2, 3 }); diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 1657aaf79..3a01763d3 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -116,6 +116,13 @@ public void ConvertOverflow() } } + [Test] + public void NoImplicitConversionToBool() + { + var pyObj = new PyList(items: new[] { 1.ToPython(), 2.ToPython() }).ToPython(); + Assert.Throws(() => pyObj.As()); + } + [Test] public void ToNullable() { diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index 36e8049d4..05298997b 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -33,8 +33,8 @@ public void Dispose() static IEnumerable PythonTestCases() { // Add the test that you want to debug here. - yield return new[] { "test_enum", "test_enum_standard_attrs" }; - yield return new[] { "test_generic", "test_missing_generic_type" }; + yield return new[] { "test_indexer", "test_boolean_indexer" }; + yield return new[] { "test_delegate", "test_bool_delegate" }; } /// diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 2b79caf39..94df2a484 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; @@ -501,6 +502,44 @@ internal static bool ToManagedValue(IntPtr value, Type obType, return ToPrimitive(value, obType, out result, setError); } + /// + /// Unlike , + /// this method does not have a setError parameter, because it should + /// only be called after . + /// + internal static bool ToManagedExplicit(BorrowedReference value, Type obType, + out object? result) + { + result = null; + + // this method would potentially clean any existing error resulting in information loss + Debug.Assert(Runtime.PyErr_Occurred() == null); + + string? converterName = + IsInteger(obType) ? "__int__" + : IsFloatingNumber(obType) ? "__float__" + : null; + + if (converterName is null) return false; + + Debug.Assert(obType.IsPrimitive); + + using var converter = Runtime.PyObject_GetAttrString(value, converterName); + if (converter.IsNull()) + { + Exceptions.Clear(); + return false; + } + + using var explicitlyCoerced = Runtime.PyObject_CallObject(converter, BorrowedReference.Null); + if (explicitlyCoerced.IsNull()) + { + Exceptions.Clear(); + return false; + } + return ToPrimitive(explicitlyCoerced, obType, out result, false); + } + static object? ToPyObjectSubclass(ConstructorInfo ctor, PyObject instance, bool setError) { try @@ -544,6 +583,8 @@ internal static int ToInt32(BorrowedReference value) return checked((int)num); } + private static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError) + => ToPrimitive(value.DangerousGetAddress(), obType, out result, setError); /// /// Convert a Python value to an instance of a primitive managed type. /// @@ -590,8 +631,21 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b } case TypeCode.Boolean: - result = Runtime.PyObject_IsTrue(value) != 0; - return true; + if (value == Runtime.PyTrue) + { + result = true; + return true; + } + if (value == Runtime.PyFalse) + { + result = false; + return true; + } + if (setError) + { + goto type_error; + } + return false; case TypeCode.Byte: { @@ -768,6 +822,10 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b case TypeCode.Single: { + if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value)) + { + goto type_error; + } double num = Runtime.PyFloat_AsDouble(value); if (num == -1.0 && Exceptions.ErrorOccurred()) { @@ -786,6 +844,10 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b case TypeCode.Double: { + if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value)) + { + goto type_error; + } double num = Runtime.PyFloat_AsDouble(value); if (num == -1.0 && Exceptions.ErrorOccurred()) { @@ -933,6 +995,13 @@ private static bool ToArray(IntPtr value, Type obType, out object? result, bool result = items; return true; } + + internal static bool IsFloatingNumber(Type type) => type == typeof(float) || type == typeof(double); + internal static bool IsInteger(Type type) + => type == typeof(Byte) || type == typeof(SByte) + || type == typeof(Int16) || type == typeof(UInt16) + || type == typeof(Int32) || type == typeof(UInt32) + || type == typeof(Int64) || type == typeof(UInt64); } public static class ConverterExtension diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 70461552c..bd767307b 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1293,7 +1293,21 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re public override bool TryConvert(ConvertBinder binder, out object result) { - return Converter.ToManaged(this.obj, binder.Type, out result, false); + // always try implicit conversion first + if (Converter.ToManaged(this.obj, binder.Type, out result, false)) + { + return true; + } + + if (binder.Explicit) + { + Runtime.PyErr_Fetch(out var errType, out var errValue, out var tb); + bool converted = Converter.ToManagedExplicit(Reference, binder.Type, out result); + Runtime.PyErr_Restore(errType.StealNullable(), errValue.StealNullable(), tb.StealNullable()); + return converted; + } + + return false; } public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index c895951e1..341b11b90 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -13,53 +13,36 @@ def test_bool_conversion(): """Test bool conversion.""" ob = ConversionTest() assert ob.BooleanField is False - assert ob.BooleanField is False assert ob.BooleanField == 0 ob.BooleanField = True assert ob.BooleanField is True - assert ob.BooleanField is True assert ob.BooleanField == 1 ob.BooleanField = False assert ob.BooleanField is False - assert ob.BooleanField is False assert ob.BooleanField == 0 - ob.BooleanField = 1 - assert ob.BooleanField is True - assert ob.BooleanField is True - assert ob.BooleanField == 1 - - ob.BooleanField = 0 - assert ob.BooleanField is False - assert ob.BooleanField is False - assert ob.BooleanField == 0 + with pytest.raises(TypeError): + ob.BooleanField = 1 + + with pytest.raises(TypeError): + ob.BooleanField = 0 - ob.BooleanField = System.Boolean(None) - assert ob.BooleanField is False - assert ob.BooleanField is False - assert ob.BooleanField == 0 + with pytest.raises(TypeError): + ob.BooleanField = None - ob.BooleanField = System.Boolean('') - assert ob.BooleanField is False - assert ob.BooleanField is False - assert ob.BooleanField == 0 + with pytest.raises(TypeError): + ob.BooleanField = '' - ob.BooleanField = System.Boolean(0) - assert ob.BooleanField is False - assert ob.BooleanField is False - assert ob.BooleanField == 0 + with pytest.raises(TypeError): + ob.BooleanField = System.Boolean(0) - ob.BooleanField = System.Boolean(1) - assert ob.BooleanField is True - assert ob.BooleanField is True - assert ob.BooleanField == 1 + with pytest.raises(TypeError): + ob.BooleanField = System.Boolean(1) - ob.BooleanField = System.Boolean('a') - assert ob.BooleanField is True - assert ob.BooleanField is True - assert ob.BooleanField == 1 + with pytest.raises(TypeError): + ob.BooleanField = System.Boolean('a') def test_sbyte_conversion(): diff --git a/tests/test_delegate.py b/tests/test_delegate.py index 52ac8226d..55115203c 100644 --- a/tests/test_delegate.py +++ b/tests/test_delegate.py @@ -247,7 +247,7 @@ def test_bool_delegate(): from Python.Test import BoolDelegate def always_so_negative(): - return 0 + return False d = BoolDelegate(always_so_negative) ob = DelegateTest() @@ -256,6 +256,12 @@ def always_so_negative(): assert not d() assert not ob.CallBoolDelegate(d) + def always_so_positive(): + return 1 + bad = BoolDelegate(always_so_positive) + with pytest.raises(TypeError): + ob.CallBoolDelegate(bad) + def test_object_delegate(): """Test object delegate.""" from Python.Test import ObjectDelegate diff --git a/tests/test_field.py b/tests/test_field.py index 0becd99e5..52fed54cb 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -200,11 +200,11 @@ def test_boolean_field(): ob.BooleanField = False assert ob.BooleanField is False - ob.BooleanField = 1 - assert ob.BooleanField is True + with pytest.raises(TypeError): + ob.BooleanField = 1 - ob.BooleanField = 0 - assert ob.BooleanField is False + with pytest.raises(TypeError): + ob.BooleanField = 0 def test_sbyte_field(): diff --git a/tests/test_indexer.py b/tests/test_indexer.py index 0af6e6c45..8cf3150ba 100644 --- a/tests/test_indexer.py +++ b/tests/test_indexer.py @@ -65,13 +65,15 @@ def test_boolean_indexer(): ob = Test.BooleanIndexerTest() assert ob[True] is None - assert ob[1] is None - - ob[0] = "false" - assert ob[0] == "false" - ob[1] = "true" - assert ob[1] == "true" + with pytest.raises(TypeError): + ob[1] + with pytest.raises(TypeError): + ob[0] + with pytest.raises(TypeError): + ob[1] = "true" + with pytest.raises(TypeError): + ob[0] = "false" ob[False] = "false" assert ob[False] == "false" diff --git a/tests/test_module.py b/tests/test_module.py index 6949f2712..4e1a1a1ef 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -41,10 +41,6 @@ def test_preload_var(): try: clr.setPreload(True) assert clr.getPreload() is True, clr.getPreload() - clr.setPreload(0) - assert clr.getPreload() is False, clr.getPreload() - clr.setPreload(1) - assert clr.getPreload() is True, clr.getPreload() import System.Configuration content = dir(System.Configuration) 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