Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 26add1e

Browse files
committed
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 #1568
1 parent 8d93c39 commit 26add1e

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ See [Mixins/collections.py](src/runtime/Mixins/collections.py).
7171
- BREAKING: When trying to convert Python `int` to `System.Object`, result will
7272
be of type `PyInt` instead of `System.Int32` due to possible loss of information.
7373
Python `float` will continue to be converted to `System.Double`.
74+
- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to
75+
`System.Single` and `System.Double`. An explicit conversion is required on Python or .NET side.
76+
- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`.
7477
- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions).
7578
- BREAKING: `PyObject` no longer implements `IEnumerable<PyObject>`.
7679
Instead, `PyIterable` does that.

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Example
7777
dynamic sin = np.sin;
7878
Console.WriteLine(sin(5));
7979
80-
double c = np.cos(5) + sin(5);
80+
double c = (double)(np.cos(5) + sin(5));
8181
Console.WriteLine(c);
8282
8383
dynamic a = np.array(new List<float> { 1, 2, 3 });

src/embed_tests/NumPyTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void TestReadme()
4040
dynamic sin = np.sin;
4141
StringAssert.StartsWith("-0.95892", sin(5).ToString());
4242

43-
double c = np.cos(5) + sin(5);
43+
double c = (double)(np.cos(5) + sin(5));
4444
Assert.AreEqual(-0.675262, c, 0.01);
4545

4646
dynamic a = np.array(new List<float> { 1, 2, 3 });

src/embed_tests/TestConverter.cs

+7
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ public void ConvertOverflow()
116116
}
117117
}
118118

119+
[Test]
120+
public void NoImplicitConversionToBool()
121+
{
122+
var pyObj = new PyList(items: new[] { 1.ToPython(), 2.ToPython() }).ToPython();
123+
Assert.Throws<InvalidCastException>(() => pyObj.As<bool>());
124+
}
125+
119126
[Test]
120127
public void ToNullable()
121128
{

src/runtime/converter.cs

+68-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Collections;
44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.Globalization;
67
using System.Reflection;
78
using System.Runtime.InteropServices;
@@ -501,6 +502,44 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
501502
return ToPrimitive(value, obType, out result, setError);
502503
}
503504

505+
/// <remarks>
506+
/// Unlike <see cref="ToManaged(BorrowedReference, Type, out object?, bool)"/>,
507+
/// this method does not have a <c>setError</c> parameter, because it should
508+
/// only be called after <see cref="ToManaged(BorrowedReference, Type, out object?, bool)"/>.
509+
/// </remarks>
510+
internal static bool ToManagedExplicit(BorrowedReference value, Type obType,
511+
out object? result)
512+
{
513+
result = null;
514+
515+
// this method would potentially clean any existing error resulting in information loss
516+
Debug.Assert(Runtime.PyErr_Occurred() == null);
517+
518+
string? converterName =
519+
IsInteger(obType) ? "__int__"
520+
: IsFloatingNumber(obType) ? "__float__"
521+
: null;
522+
523+
if (converterName is null) return false;
524+
525+
Debug.Assert(obType.IsPrimitive);
526+
527+
using var converter = Runtime.PyObject_GetAttrString(value, converterName);
528+
if (converter.IsNull())
529+
{
530+
Exceptions.Clear();
531+
return false;
532+
}
533+
534+
using var explicitlyCoerced = Runtime.PyObject_CallObject(converter, BorrowedReference.Null);
535+
if (explicitlyCoerced.IsNull())
536+
{
537+
Exceptions.Clear();
538+
return false;
539+
}
540+
return ToPrimitive(explicitlyCoerced, obType, out result, false);
541+
}
542+
504543
static object? ToPyObjectSubclass(ConstructorInfo ctor, PyObject instance, bool setError)
505544
{
506545
try
@@ -544,6 +583,8 @@ internal static int ToInt32(BorrowedReference value)
544583
return checked((int)num);
545584
}
546585

586+
private static bool ToPrimitive(BorrowedReference value, Type obType, out object? result, bool setError)
587+
=> ToPrimitive(value.DangerousGetAddress(), obType, out result, setError);
547588
/// <summary>
548589
/// Convert a Python value to an instance of a primitive managed type.
549590
/// </summary>
@@ -590,7 +631,18 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b
590631
}
591632

592633
case TypeCode.Boolean:
593-
result = Runtime.PyObject_IsTrue(value) != 0;
634+
if (value == Runtime.PyTrue)
635+
{
636+
result = true;
637+
}
638+
else if (value == Runtime.PyFalse)
639+
{
640+
result = false;
641+
}
642+
else if (setError)
643+
{
644+
goto type_error;
645+
}
594646
return true;
595647

596648
case TypeCode.Byte:
@@ -768,6 +820,10 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b
768820

769821
case TypeCode.Single:
770822
{
823+
if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value))
824+
{
825+
goto type_error;
826+
}
771827
double num = Runtime.PyFloat_AsDouble(value);
772828
if (num == -1.0 && Exceptions.ErrorOccurred())
773829
{
@@ -786,6 +842,10 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object? result, b
786842

787843
case TypeCode.Double:
788844
{
845+
if (!Runtime.PyFloat_Check(value) && !Runtime.PyInt_Check(value))
846+
{
847+
goto type_error;
848+
}
789849
double num = Runtime.PyFloat_AsDouble(value);
790850
if (num == -1.0 && Exceptions.ErrorOccurred())
791851
{
@@ -933,6 +993,13 @@ private static bool ToArray(IntPtr value, Type obType, out object? result, bool
933993
result = items;
934994
return true;
935995
}
996+
997+
internal static bool IsFloatingNumber(Type type) => type == typeof(float) || type == typeof(double);
998+
internal static bool IsInteger(Type type)
999+
=> type == typeof(Byte) || type == typeof(SByte)
1000+
|| type == typeof(Int16) || type == typeof(UInt16)
1001+
|| type == typeof(Int32) || type == typeof(UInt32)
1002+
|| type == typeof(Int64) || type == typeof(UInt64);
9361003
}
9371004

9381005
public static class ConverterExtension

src/runtime/pyobject.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -1293,7 +1293,21 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re
12931293

12941294
public override bool TryConvert(ConvertBinder binder, out object result)
12951295
{
1296-
return Converter.ToManaged(this.obj, binder.Type, out result, false);
1296+
// always try implicit conversion first
1297+
if (Converter.ToManaged(this.obj, binder.Type, out result, false))
1298+
{
1299+
return true;
1300+
}
1301+
1302+
if (binder.Explicit)
1303+
{
1304+
Runtime.PyErr_Fetch(out var errType, out var errValue, out var tb);
1305+
bool converted = Converter.ToManagedExplicit(Reference, binder.Type, out result);
1306+
Runtime.PyErr_Restore(errType.StealNullable(), errValue.StealNullable(), tb.StealNullable());
1307+
return converted;
1308+
}
1309+
1310+
return false;
12971311
}
12981312

12991313
public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)

0 commit comments

Comments
 (0)