Skip to content

Disable implicit seq to array conversion when target type is object #1901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ details about the cause of the failure
able to access members that are part of the implementation class, but not the
interface. Use the new `__implementation__` or `__raw_implementation__` properties to
if you need to "downcast" to the implementation class.
- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison
(previously was equivalent to `object.ReferenceEquals(,)`)
- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition
to the regular method return value (unless they are passed with `ref` or `out` keyword).
- BREAKING: Drop support for the long-deprecated CLR.* prefix.
Expand All @@ -65,6 +67,9 @@ or the DLL must be loaded in advance. This must be done before calling any other
- BREAKING: disabled implicit conversion from C# enums to Python `int` and back.
One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor
(e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42).
- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to
.NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the
target type is a `System.Array`.
- Sign Runtime DLL with a strong name
- Implement loading through `clr_loader` instead of the included `ClrModule`, enables
support for .NET Core
Expand Down
5 changes: 3 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
recursive-include src/ *
graft src/runtime
prune src/runtime/obj
prune src/runtime/bin
include Directory.Build.*
include pythonnet.sln
include version.txt
global-exclude **/obj/* **/bin/*
18 changes: 9 additions & 9 deletions pythonnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Dict, Optional, Union
import clr_loader

__all__ = ["set_runtime", "set_default_runtime", "load"]
__all__ = ["set_runtime", "set_runtime_from_env", "load"]

_RUNTIME: Optional[clr_loader.Runtime] = None
_LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None
Expand All @@ -30,7 +30,7 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None:
def _get_params_from_env(prefix: str) -> Dict[str, str]:
from os import environ

full_prefix = f"PYTHONNET_{prefix.upper()}"
full_prefix = f"PYTHONNET_{prefix.upper()}_"
len_ = len(full_prefix)

env_vars = {
Expand Down Expand Up @@ -63,8 +63,8 @@ def _create_runtime_from_spec(
raise RuntimeError(f"Invalid runtime name: '{spec}'")


def set_default_runtime() -> None:
"""Set up the default runtime
def set_runtime_from_env() -> None:
"""Set up the runtime using the environment

This will use the environment variable PYTHONNET_RUNTIME to decide the
runtime to use, which may be one of netfx, coreclr or mono. The parameters
Expand All @@ -80,16 +80,13 @@ def set_default_runtime() -> None:
"""
from os import environ

print("Set default RUNTIME")
raise RuntimeError("Shouldn't be called here")

spec = environ.get("PYTHONNET_RUNTIME", "default")
runtime = _create_runtime_from_spec(spec)
set_runtime(runtime)


def load(
runtime: Union[clr_loader.Runtime, str] = "default", **params: Dict[str, str]
runtime: Union[clr_loader.Runtime, str, None] = None, **params: str
) -> None:
"""Load Python.NET in the specified runtime

Expand All @@ -102,7 +99,10 @@ def load(
return

if _RUNTIME is None:
set_runtime(runtime, **params)
if runtime is None:
set_runtime_from_env()
else:
set_runtime(runtime, **params)

if _RUNTIME is None:
raise RuntimeError("No valid runtime selected")
Expand Down
22 changes: 22 additions & 0 deletions src/embed_tests/dynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,28 @@ public void PassPyObjectInNet()
Assert.IsTrue(sys.testattr1.Equals(sys.testattr2));
}

// regression test for https://github.com/pythonnet/pythonnet/issues/1848
[Test]
public void EnumEquality()
{
using var scope = Py.CreateScope();
scope.Exec(@"
import enum

class MyEnum(enum.IntEnum):
OK = 1
ERROR = 2

def get_status():
return MyEnum.OK
"
);

dynamic MyEnum = scope.Get("MyEnum");
dynamic status = scope.Get("get_status").Invoke();
Assert.IsTrue(status == MyEnum.OK);
}

// regression test for https://github.com/pythonnet/pythonnet/issues/1680
[Test]
public void ForEach()
Expand Down
6 changes: 0 additions & 6 deletions src/embed_tests/packages.config

This file was deleted.

2 changes: 1 addition & 1 deletion src/runtime/AssemblyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public static bool IsValidNamespace(string name)
}

/// <summary>
/// Returns an IEnumerable<string> containing the namepsaces exported
/// Returns an enumerable collection containing the namepsaces exported
/// by loaded assemblies in the current app domain.
/// </summary>
public static IEnumerable<string> GetNamespaces ()
Expand Down
15 changes: 12 additions & 3 deletions src/runtime/ClassManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal static ClassManagerState SaveRuntimeData()
if ((Runtime.PyDict_DelItemString(dict.Borrow(), member) == -1) &&
(Exceptions.ExceptionMatches(Exceptions.KeyError)))
{
// Trying to remove a key that's not in the dictionary
// Trying to remove a key that's not in the dictionary
// raises an error. We don't care about it.
Runtime.PyErr_Clear();
}
Expand Down Expand Up @@ -215,7 +215,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
impl.indexer = info.indexer;
impl.richcompare.Clear();


// Finally, initialize the class __dict__ and return the object.
using var newDict = Runtime.PyObject_GenericGetDict(pyType.Reference);
BorrowedReference dict = newDict.Borrow();
Expand Down Expand Up @@ -271,6 +271,15 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc.Borrow());
}
}

if (Runtime.PySequence_Contains(dict, PyIdentifier.__doc__) != 1)
{
// Ensure that at least some doc string is set
using var fallbackDoc = Runtime.PyString_FromString(
$"Python wrapper for .NET type {type}"
);
Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, fallbackDoc.Borrow());
}
}
doc.Dispose();

Expand Down Expand Up @@ -562,7 +571,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)

return ci;
}

/// <summary>
/// This class owns references to PyObjects in the `members` member.
/// The caller has responsibility to DECREF them.
Expand Down
7 changes: 1 addition & 6 deletions src/runtime/Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
// conversions (Python string -> managed string).
if (obType == objectType)
{
if (Runtime.IsStringType(value))
if (Runtime.PyString_Check(value))
{
return ToPrimitive(value, stringType, out result, setError);
}
Expand Down Expand Up @@ -389,11 +389,6 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType,
return true;
}

if (Runtime.PySequence_Check(value))
{
return ToArray(value, typeof(object[]), out result, setError);
}

result = new PyObject(value);
return true;
}
Expand Down
1 change: 1 addition & 0 deletions src/runtime/Finalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ internal IncorrectRefCountException(IntPtr ptr)

#endregion

[ForbidPythonThreads]
public void Collect() => this.DisposeAll();

internal void ThrottledCollect()
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/Native/NewReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public PyObject MoveToPyObject()
/// </summary>
public NewReference Move()
{
var result = new NewReference(this);
var result = DangerousFromPointer(this.DangerousGetAddress());
this.pointer = default;
return result;
}
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>

<GenerateDocumentationFile>True</GenerateDocumentationFile>

<AssemblyOriginatorKeyFile>..\pythonnet.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>

Expand Down
15 changes: 12 additions & 3 deletions src/runtime/PythonEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
Assembly assembly = Assembly.GetExecutingAssembly();
// add the contents of clr.py to the module
string clr_py = assembly.ReadStringResource("clr.py");

Exec(clr_py, module_globals, locals.Reference);

LoadSubmodule(module_globals, "clr.interop", "interop.py");
Expand All @@ -237,14 +238,22 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
// add the imported module to the clr module, and copy the API functions
// and decorators into the main clr module.
Runtime.PyDict_SetItemString(clr_dict, "_extras", module);

// append version
var version = typeof(PythonEngine)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion;
using var versionObj = Runtime.PyString_FromString(version);
Runtime.PyDict_SetItemString(clr_dict, "__version__", versionObj.Borrow());

using var keys = locals.Keys();
foreach (PyObject key in keys)
{
if (!key.ToString()!.StartsWith("_") || key.ToString()!.Equals("__version__"))
if (!key.ToString()!.StartsWith("_"))
{
PyObject value = locals[key];
using PyObject value = locals[key];
Runtime.PyDict_SetItem(clr_dict, key.Reference, value.Reference);
value.Dispose();
}
key.Dispose();
}
Expand Down
75 changes: 59 additions & 16 deletions src/runtime/PythonTypes/PyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,12 +1075,9 @@ public virtual bool Equals(PyObject? other)
{
return true;
}
int r = Runtime.PyObject_Compare(this, other);
if (Exceptions.ErrorOccurred())
{
throw PythonException.ThrowLastAsClrException();
}
return r == 0;
int result = Runtime.PyObject_RichCompareBool(obj, other.obj, Runtime.Py_EQ);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}


Expand Down Expand Up @@ -1304,6 +1301,18 @@ public override bool TryConvert(ConvertBinder binder, out object? result)
return false;
}

private bool TryCompare(PyObject arg, int op, out object @out)
{
int result = Runtime.PyObject_RichCompareBool(this.obj, arg.obj, op);
@out = result != 0;
if (result < 0)
{
Exceptions.Clear();
return false;
}
return true;
}

public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
{
using var _ = Py.GIL();
Expand Down Expand Up @@ -1352,32 +1361,29 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
res = Runtime.PyNumber_InPlaceXor(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.GreaterThan:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) > 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_GT, out result);
case ExpressionType.GreaterThanOrEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) >= 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_GE, out result);
case ExpressionType.LeftShift:
res = Runtime.PyNumber_Lshift(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.LeftShiftAssign:
res = Runtime.PyNumber_InPlaceLshift(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.LessThan:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) < 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_LT, out result);
case ExpressionType.LessThanOrEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) <= 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_LE, out result);
case ExpressionType.Modulo:
res = Runtime.PyNumber_Remainder(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.ModuloAssign:
res = Runtime.PyNumber_InPlaceRemainder(this.obj, ((PyObject)arg).obj);
break;
case ExpressionType.NotEqual:
result = Runtime.PyObject_Compare(this.obj, ((PyObject)arg).obj) != 0;
return true;
return this.TryCompare((PyObject)arg, Runtime.Py_NE, out result);
case ExpressionType.Equal:
return this.TryCompare((PyObject)arg, Runtime.Py_EQ, out result);
case ExpressionType.Or:
res = Runtime.PyNumber_Or(this.obj, ((PyObject)arg).obj);
break;
Expand All @@ -1402,6 +1408,40 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg
return true;
}

public static bool operator ==(PyObject? a, PyObject? b)
{
if (a is null && b is null)
{
return true;
}
if (a is null || b is null)
{
return false;
}

using var _ = Py.GIL();
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_EQ);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}

public static bool operator !=(PyObject? a, PyObject? b)
{
if (a is null && b is null)
{
return false;
}
if (a is null || b is null)
{
return true;
}

using var _ = Py.GIL();
int result = Runtime.PyObject_RichCompareBool(a.obj, b.obj, Runtime.Py_NE);
if (result < 0) throw PythonException.ThrowLastAsClrException();
return result != 0;
}

// Workaround for https://bugzilla.xamarin.com/show_bug.cgi?id=41509
// See https://github.com/pythonnet/pythonnet/pull/219
internal static object? CheckNone(PyObject pyObj)
Expand Down Expand Up @@ -1436,14 +1476,17 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object?
case ExpressionType.Not:
r = Runtime.PyObject_Not(this.obj);
result = r == 1;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.IsFalse:
r = Runtime.PyObject_IsTrue(this.obj);
result = r == 0;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.IsTrue:
r = Runtime.PyObject_IsTrue(this.obj);
result = r == 1;
if (r == -1) Exceptions.Clear();
return r != -1;
case ExpressionType.Decrement:
case ExpressionType.Increment:
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/Resources/clr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
Code in this module gets loaded into the main clr module.
"""

__version__ = "3.0.0dev"


class clrproperty(object):
"""
Expand Down
Loading