8000 C# classes, which inherited __call__ from their Python base class, ar… · pythonnet/pythonnet@960457f · GitHub
[go: up one dir, main page]

Skip to content

Commit 960457f

Browse files
committed
C# classes, which inherited __call__ from their Python base class, are also callable now
A test case added to CallableObject.cs When tp_call is invoked for a custom .NET class, and __call__ is not implemented in .NET, walk the class hierarchy (as seen from Python) until Python class is found. Then try to invoke its __call__ method, if defined. #890
1 parent d46878c commit 960457f

File tree

4 files changed

+96
-25
lines changed

4 files changed

+96
-25
lines changed

src/embed_tests/CallableObject.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ class CallableObject {
99
[OneTimeSetUp]
1010
public void SetUp() {
1111
PythonEngine.Initialize();
12+
using (Py.GIL()) {
13+
var locals = new PyDict();
14+
PythonEngine.Exec(CallViaInheritance.BaseClassSource, locals: locals.Handle);
15+
CustomBaseTypeAttribute.BaseClass = locals[CallViaInheritance.BaseClassName];
16+
}
1217
}
1318

1419
[OneTimeTearDown]
@@ -25,8 +30,35 @@ public void CallMethodMakesObjectCallable() {
2530
}
2631
}
2732

33+
[Test]
34+
public void CallMethodCanBeInheritedFromPython() {
35+
var callViaInheritance = new CallViaInheritance();
36+
using (Py.GIL()) {
37+
dynamic applyObjectTo14 = PythonEngine.Eval("lambda o: o(14)");
38+
Assert.AreEqual(callViaInheritance.Call(14), (int)applyObjectTo14(callViaInheritance.ToPython()));
39+
}
40+
}
41+
2842
class Doubler {
2943
public int __call__(int arg) => 2 * arg;
3044
}
45+
46+
[CustomBaseType]
47+
class CallViaInheritance {
48+
public const string BaseClassName = "Forwarder";
49+
public static readonly string BaseClassSource = $@"
50+
class {BaseClassName}:
51+
def __call__(self, val):
52+
return self.Call(val)
53+
";
54+
public int Call(int arg) => 3 * arg;
55+
}
56+
57+
class CustomBaseTypeAttribute : BaseTypeAttributeBase {
58+
internal static PyObject BaseClass;
59+
60+
public override IntPtr BaseType(Type type)
61+
=> type != typeof(CallViaInheritance) ? IntPtr.Zero : BaseClass.Handle;
62+
}
3163
}
3264
}

src/runtime/classobject.cs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Reflection;
34
using System.Runtime.InteropServices;
45

@@ -299,17 +300,44 @@ public static IntPtr tp_call(IntPtr ob, IntPtr args, IntPtr kw)
299300
IntPtr methodObjectHandle = Runtime.PyDict_GetItemString(dict, "__call__");
300301
if (methodObjectHandle == IntPtr.Zero || methodObjectHandle == Runtime.PyNone)
301302
{
303+
Runtime.XDecrefIgnoreNull(methodObjectHandle);
302304
Exceptions.SetError(Exceptions.TypeError, "object is not callable");
303305
return IntPtr.Zero;
304306
}
305307

306-
if (GetManagedObject(methodObjectHandle) is MethodObject methodObject)
308+
try
307309
{
308-
return methodObject.Invoke(ob, args, kw);
310+
if (GetManagedObject(methodObjectHandle) is MethodObject methodObject)
311+
{
312+
return methodObject.Invoke(ob, args, kw);
313+
}
314+
315+
IntPtr pythonBase = GetPythonBase(tp);
316+
dict = Marshal.ReadIntPtr(pythonBase, TypeOffset.tp_dict);
317+
Runtime.XDecref(methodObjectHandle);
318+
methodObjectHandle = Runtime.PyDict_GetItemString(dict, "__call__");
319+
if (methodObjectHandle == IntPtr.Zero || methodObjectHandle == Runtime.PyNone)
320+
{
321+
Exceptions.SetError(Exceptions.TypeError, "object is not callable");
322+
return IntPtr.Zero;
323+
}
324+
325+
var boundMethod = Runtime.PyMethod_New(methodObjectHandle, ob);
326+
if (boundMethod == IntPtr.Zero) { return IntPtr.Zero; }
327+
328+
try
329+
{
330+
return Runtime.PyObject_Call(boundMethod, args, kw);
331+
}
332+
finally
333+
{
334+
Runtime.XDecref(boundMethod);
335+
}
336+
}
337+
finally
338+
{
339+
Runtime.XDecrefIgnoreNull(methodObjectHandle);
309340
}
310-
311-
Exceptions.SetError(Exceptions.TypeError, "instance has __call__, but it is not supported by Python.NET");
312-
return IntPtr.Zero;
313341
}
314342

315343
var co = (CLRObject)GetManagedObject(ob);
@@ -323,5 +351,24 @@ public static IntPtr tp_call(IntPtr ob, IntPtr args, IntPtr kw)
323351
var binder = new MethodBinder(method);
324352
return binder.Invoke(ob, args, kw);
325353
}
354+
355+
/// <summary>
356+
/// Get the first base class in the class hierarchy
357+
/// of the specified .NET type, that is defined in Python.
358+
/// </summary>
359+
static IntPtr GetPythonBase(IntPtr tp) {
360+
Debug.Assert(IsManagedType(tp));
361+
do {
362+
tp = Marshal.ReadIntPtr(tp, TypeOffset.tp_base);
363+
} while (IsManagedType(tp));
364+
365+
return tp;
366+
}
367+
368+
internal static bool IsManagedType(IntPtr tp)
369+
{
370+
var flags = Util.ReadCLong(tp, TypeOffset.tp_flags);
371+
return (flags & TypeFlags.Managed) != 0;
372+
}
326373
}
327374
}

src/runtime/managedtype.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,5 @@ internal static ManagedType GetManagedObjectErr(IntPtr ob)
5555
}
5656
return result;
5757
}
58-
59-
60-
internal static bool IsManagedType(IntPtr ob)
61-
{
62-
if (ob != IntPtr.Zero)
63-
{
64-
IntPtr tp = Runtime.PyObject_TYPE(ob);
65-
if (tp == Runtime.PyTypeType || tp == Runtime.PyCLRMetaType)
66-
{
67-
tp = ob;
68-
}
69-
70-
var flags = Util.ReadCLong(tp, TypeOffset.tp_flags);
71-
if ((flags & TypeFlags.Managed) != 0)
72-
{
73-
return true;
74-
}
75-
}
76-
return false;
77-
}
7858
}
7959
}

src/runtime/runtime.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,10 @@ internal static unsafe void XDecref(IntPtr op)
715715
#endif
716716
}
717717

718+
internal static void XDecrefIgnoreNull(IntPtr op) {
719+
if (op != IntPtr.Zero) { XDecref(op); }
720+
}
721+
718722
internal static unsafe long Refcount(IntPtr op)
719723
{
720724
var p = (void*)op;
@@ -1773,6 +1777,8 @@ internal static IntPtr PyMem_Realloc(IntPtr ptr, long size)
17731777

17741778
internal static IntPtr PyMethod_Function(IntPtr ob) => Delegates.PyMethod_Function(ob);
17751779

1780+
internal static IntPtr PyMethod_New(IntPtr func, IntPtr self) => Delegates.PyMethod_New(func, self);
1781+
17761782
public static class Delegates
17771783
{
17781784
static Delegates()
@@ -1993,6 +1999,7 @@ static Delegates()
19931999
PyErr_Print = GetDelegateForFunctionPointer<PyErr_PrintDelegate>(GetFunctionByName(nameof(PyErr_Print), GetUnmanagedDll(PythonDLL)));
19942000
PyMethod_Self = GetDelegateForFunctionPointer<PyMethod_SelfDelegate>(GetFunctionByName(nameof(PyMethod_Self), GetUnmanagedDll(PythonDLL)));
19952001
PyMethod_Function = GetDelegateForFunctionPointer<PyMethod_FunctionDelegate>(GetFunctionByName(nameof(PyMethod_Function), GetUnmanagedDll(PythonDLL)));
2002+
PyMethod_New = GetDelegateForFunctionPointer<PyMethod_NewDelegate>(GetFunctionByName(nameof(PyMethod_New), GetUnmanagedDll(PythonDLL)));
19962003
}
19972004

19982005
static T GetDelegateForFunctionPointer<T>(IntPtr functionPointer) {
@@ -3117,6 +3124,11 @@ int updatepath
31173124

31183125
[global::System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
31193126
public delegate IntPtr PyMethod_FunctionDelegate(IntPtr ob);
3127+
3128+
internal static PyMethod_NewDelegate PyMethod_New { get; }
3129+
3130+
[global::System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
3131+
public delegate IntPtr PyMethod_NewDelegate(IntPtr func, IntPtr self);
31203132
// end of PY3
31213133

31223134
enum Py2 { }

0 commit comments

Comments
 (0)
0