Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

#2240 Recursion error when doing reversed python operations on C# types#2327

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
lostmsu merged 5 commits intopythonnet:masterfromgertdreyer:bugfix/recursionError
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes fromall 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
1 change: 1 addition & 0 deletionsAUTHORS.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,3 +86,4 @@
- ([@gpetrou](https://github.com/gpetrou))
- Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad))
- ([@legomanww](https://github.com/legomanww))
- ([@gertdreyer](https://github.com/gertdreyer))
1 change: 1 addition & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].

### Fixed

- Fixed RecursionError for reverse operators on C# operable types from python. See #2240

## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11

Expand Down
235 changes: 235 additions & 0 deletionssrc/embed_tests/TestOperator.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@ public class TestOperator
public void SetUp()
{
PythonEngine.Initialize();
OwnIntCodec.Setup();
}

[OneTimeTearDown]
Expand All@@ -23,6 +24,125 @@ public void Dispose()
PythonEngine.Shutdown();
}

// Mock Integer class to test math ops on non-native dotnet types
public readonly struct OwnInt
{
private readonly int _value;

public int Num => _value;

public OwnInt()
{
_value = 0;
}

public OwnInt(int value)
{
_value = value;
}

public override int GetHashCode()
{
return unchecked(65535 + _value.GetHashCode());
}

public override bool Equals(object obj)
{
return obj is OwnInt @object &&
Num == @object.Num;
}

public static OwnInt operator -(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value - p2._value);
}

public static OwnInt operator +(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value + p2._value);
}

public static OwnInt operator *(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value * p2._value);
}

public static OwnInt operator /(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value / p2._value);
}

public static OwnInt operator %(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value % p2._value);
}

public static OwnInt operator ^(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value ^ p2._value);
}

public static bool operator <(OwnInt p1, OwnInt p2)
{
return p1._value < p2._value;
}

public static bool operator >(OwnInt p1, OwnInt p2)
{
return p1._value > p2._value;
}

public static bool operator ==(OwnInt p1, OwnInt p2)
{
return p1._value == p2._value;
}

public static bool operator !=(OwnInt p1, OwnInt p2)
{
return p1._value != p2._value;
}

public static OwnInt operator |(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value | p2._value);
}

public static OwnInt operator &(OwnInt p1, OwnInt p2)
{
return new OwnInt(p1._value & p2._value);
}

public static bool operator <=(OwnInt p1, OwnInt p2)
{
return p1._value <= p2._value;
}

public static bool operator >=(OwnInt p1, OwnInt p2)
{
return p1._value >= p2._value;
}
}

// Codec for mock class above.
public class OwnIntCodec : IPyObjectDecoder
{
public static void Setup()
{
PyObjectConversions.RegisterDecoder(new OwnIntCodec());
}

public bool CanDecode(PyType objectType, Type targetType)
{
return objectType.Name == "int" && targetType == typeof(OwnInt);
}

public bool TryDecode<T>(PyObject pyObj, out T value)
{
value = (T)(object)new OwnInt(pyObj.As<int>());
return true;
}
}

public class OperableObject
{
public int Num { get; set; }
Expand DownExpand Up@@ -524,6 +644,121 @@ public void ShiftOperatorOverloads()

c = a >> b.Num
assert c.Num == a.Num >> b.Num
");
}

[Test]
public void ReverseOperatorWithCodec()
{
string name = string.Format("{0}.{1}",
typeof(OwnInt).DeclaringType.Name,
typeof(OwnInt).Name);
string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace;

PythonEngine.Exec($@"
from {module} import *
cls = {name}
a = 2
b = cls(10)

c = a + b
assert c.Num == a + b.Num

c = a - b
assert c.Num == a - b.Num

c = a * b
assert c.Num == a * b.Num

c = a / b
assert c.Num == a // b.Num

c = a % b
assert c.Num == a % b.Num

c = a & b
assert c.Num == a & b.Num

c = a | b
assert c.Num == a | b.Num

c = a ^ b
assert c.Num == a ^ b.Num

c = a == b
assert c == (a == b.Num)

c = a != b
assert c == (a != b.Num)

c = a <= b
assert c == (a <= b.Num)

c = a >= b
assert c == (a >= b.Num)

c = a < b
assert c == (a < b.Num)

c = a > b
assert c == (a > b.Num)
");
}

[Test]
public void ForwardOperatorWithCodec()
{
string name = string.Format("{0}.{1}",
typeof(OwnInt).DeclaringType.Name,
typeof(OwnInt).Name);
string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace;

PythonEngine.Exec($@"
from {module} import *
cls = {name}
a = cls(2)
b = 10
c = a + b
assert c.Num == a.Num + b

c = a - b
assert c.Num == a.Num - b

c = a * b
assert c.Num == a.Num * b

c = a / b
assert c.Num == a.Num // b

c = a % b
assert c.Num == a.Num % b

c = a & b
assert c.Num == a.Num & b

c = a | b
assert c.Num == a.Num | b

c = a ^ b
assert c.Num == a.Num ^ b

c = a == b
assert c == (a.Num == b)

c = a != b
assert c == (a.Num != b)

c = a <= b
assert c == (a.Num <= b)

c = a >= b
assert c == (a.Num >= b)

c = a < b
assert c == (a.Num < b)

c = a > b
assert c == (a.Num > b)
");
}
}
Expand Down
2 changes: 1 addition & 1 deletionsrc/runtime/ClassManager.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -546,7 +546,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
ci.members[pyName] = new MethodObject(type, name, forwardMethods).AllocObject();
// Only methods where only the right operand is the declaring type.
if (reverseMethods.Length > 0)
ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods).AllocObject();
ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, argsReversed: true).AllocObject();
}
}

Expand Down
15 changes: 10 additions & 5 deletionssrc/runtime/MethodBinder.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,12 @@ internal class MethodBinder

[NonSerialized]
public bool init = false;

public const bool DefaultAllowThreads = true;
public bool allow_threads = DefaultAllowThreads;

public bool argsReversed = false;

internal MethodBinder()
{
list = new List<MaybeMethodBase>();
Expand DownExpand Up@@ -363,10 +366,10 @@ public MismatchedMethod(Exception exception, MethodBase mb)
_methods = GetMethods();
}

return Bind(inst, args, kwargDict, _methods, matchGenerics: true);
return Bind(inst, args, kwargDict, _methods, matchGenerics: true, argsReversed);
}

static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary<string, PyObject> kwargDict, MethodBase[] methods, bool matchGenerics)
privatestatic Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary<string, PyObject> kwargDict, MethodBase[] methods, bool matchGenerics, bool argsReversed = false)
{
var pynargs = (int)Runtime.PyTuple_Size(args);
var isGeneric = false;
Expand All@@ -386,20 +389,22 @@ public MismatchedMethod(Exception exception, MethodBase mb)
// Binary operator methods will have 2 CLR args but only one Python arg
// (unary operators will have 1 less each), since Python operator methods are bound.
isOperator = isOperator && pynargs == pi.Length - 1;
bool isReverse = isOperator &&OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator.
bool isReverse = isOperator &&argsReversed; // Only cast if isOperator.
if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi))
continue; // Comparison operators in Python have no reverse mode.
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator)
{
continue;
}
// Preprocessing pi to remove either the first or second argument.
if (isOperator && !isReverse) {
if (isOperator && !isReverse)
{
// The first Python arg is the right operand, while the bound instance is the left.
// We need to skip the first (left operand) CLR argument.
pi = pi.Skip(1).ToArray();
}
else if (isOperator && isReverse) {
else if (isOperator && isReverse)
{
// The first Python arg is the left operand.
// We need to take the first CLR argument.
pi = pi.Take(1).ToArray();
Expand Down
1 change: 1 addition & 0 deletionssrc/runtime/Types/MethodBinding.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -237,6 +237,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args,
}
}
}

return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue);
}
finally
Expand Down
10 changes: 5 additions & 5 deletionssrc/runtime/Types/MethodObject.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,20 +19,20 @@ internal class MethodObject : ExtensionType
{
[NonSerialized]
private MethodBase[]? _info = null;

private readonly List<MaybeMethodInfo> infoList;
internal string name;
internal readonly MethodBinder binder;
internal bool is_static = false;

internal PyString? doc;
internal MaybeType type;

public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads)
public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool argsReversed = false)
{
this.type = type;
this.name = name;
this.infoList = new List<MaybeMethodInfo>();
binder = new MethodBinder();
binder = new MethodBinder() { argsReversed = argsReversed };
foreach (MethodBase item in info)
{
this.infoList.Add(item);
Expand All@@ -45,8 +45,8 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t
binder.allow_threads = allow_threads;
}

public MethodObject(MaybeType type, string name, MethodBase[] info)
: this(type, name, info, allow_threads: AllowThreads(info))
public MethodObject(MaybeType type, string name, MethodBase[] info, bool argsReversed = false)
: this(type, name, info, allow_threads: AllowThreads(info), argsReversed)
{
}

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp