Skip to content

Commit 748d3d7

Browse files
committed
implemented __signature__ and __name__ on methodbinding
1 parent 66716db commit 748d3d7

File tree

6 files changed

+114
-3
lines changed

6 files changed

+114
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1515
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
1616
- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`
1717
- Improved exception handling:
18-
- exceptions can now be converted with codecs
19-
- `InnerException` and `__cause__` are propagated properly
18+
* exceptions can now be converted with codecs
19+
* `InnerException` and `__cause__` are propagated properly
20+
- `__name__` and `__signature__` to reflected .NET methods
2021
- .NET collection types now implement standard Python collection interfaces from `collections.abc`.
2122
See [Mixins/collections.py](src/runtime/Mixins/collections.py).
2223
- .NET arrays implement Python buffer protocol

src/embed_tests/Inspect.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,30 @@ public void InstancePropertiesVisibleOnClass()
3030
var pyProp = (PropertyObject)ManagedType.GetManagedObject(property.Reference);
3131
Assert.AreEqual(nameof(Uri.AbsoluteUri), pyProp.info.Value.Name);
3232
}
33+
34+
[Test]
35+
public void BoundMethodsAreInspectable()
36+
{
37+
using var scope = Py.CreateScope();
38+
try
39+
{
40+
scope.Import("inspect");
41+
}
42+
catch (PythonException)
43+
{
44+
Assert.Inconclusive("Python build does not include inspect module");
45+
return;
46+
}
47+
48+
var obj = new Class();
49+
scope.Set(nameof(obj), obj);
50+
using var spec = scope.Eval($"inspect.getfullargspec({nameof(obj)}.{nameof(Class.Method)})");
51+
}
52+
53+
class Class
54+
{
55+
public void Method(int a, int b = 10) { }
56+
public void Method(int a, object b) { }
57+
}
3358
}
3459
}

src/runtime/exceptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ public static variables on the Exceptions class filled in from
420420
public static IntPtr IOError;
421421
public static IntPtr OSError;
422422
public static IntPtr ImportError;
423+
public static IntPtr ModuleNotFoundError;
423424
public static IntPtr IndexError;
424425
public static IntPtr KeyError;
425426
public static IntPtr KeyboardInterrupt;

src/runtime/methodbinding.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Reflection;
45

56
namespace Python.Runtime
@@ -65,6 +66,67 @@ public static IntPtr mp_subscript(IntPtr tp, IntPtr idx)
6566
return mb.pyHandle;
6667
}
6768

69+
PyObject Signature
70+
{
71+
get
72+
{
73+
var infos = this.info.Valid ? new[] { this.info.Value } : this.m.info;
74+
Type type = infos.Select(i => i.DeclaringType)
75+
.OrderByDescending(t => t, new TypeSpecificityComparer())
76+
.First();
77+
infos = infos.Where(info => info.DeclaringType == type).ToArray();
78+
// this is a primitive version
79+
// the overload with the maximum number of parameters should be used
80+
MethodInfo primary = infos.OrderByDescending(i => i.GetParameters().Length).First();
81+
var primaryParameters = primary.GetParameters();
82+
PyObject signatureClass = Runtime.InspectModule.GetAttr("Signature");
83+
var primaryReturn = primary.ReturnParameter;
84+
85+
using var parameters = new PyList();
86+
using var parameterClass = primaryParameters.Length > 0 ? Runtime.InspectModule.GetAttr("Parameter") : null;
87+
using var positionalOrKeyword = parameterClass?.GetAttr("POSITIONAL_OR_KEYWORD");
88+
for (int i = 0; i < primaryParameters.Length; i++)
89+
{
90+
var parameter = primaryParameters[i];
91+
var alternatives = infos.Select(info =>
92+
{
93+
ParameterInfo[] altParamters = info.GetParameters();
94+
return i < altParamters.Length ? altParamters[i] : null;
95+
}).Where(p => p != null);
96+
using var defaultValue = alternatives
97+
.Select(alternative => alternative.DefaultValue != DBNull.Value ? alternative.DefaultValue.ToPython() : null)
98+
.FirstOrDefault(v => v != null) ?? parameterClass.GetAttr("empty");
99+
100+
if (alternatives.Any(alternative => alternative.Name != parameter.Name))
101+
{
102+
return signatureClass.Invoke();
103+
}
104+
105+
using var args = new PyTuple(new[] { parameter.Name.ToPython(), positionalOrKeyword });
106+
using var kw = new PyDict();
107+
if (defaultValue is not null)
108+
{
109+
kw["default"] = defaultValue;
110+
}
111+
using var parameterInfo = parameterClass.Invoke(args: args, kw: kw);
112+
parameters.Append(parameterInfo);
113+
}
114+
115+
// TODO: add return annotation
116+
return signatureClass.Invoke(parameters);
117+
}
118+
}
119+
120+
struct TypeSpecificityComparer : IComparer<Type>
121+
{
122+
public int Compare(Type a, Type b)
123+
{
124+
if (a == b) return 0;
125+
if (a.IsSubclassOf(b)) return 1;
126+
if (b.IsSubclassOf(a)) return -1;
127+
throw new NotSupportedException();
128+
}
129+
}
68130

69131
/// <summary>
70132
/// MethodBinding __getattribute__ implementation.
@@ -91,6 +153,15 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
91153
case "Overloads":
92154
var om = new OverloadMapper(self.m, self.target);
93155
return om.pyHandle;
156+
case "__signature__" when Runtime.InspectModule is not null:
157+
var sig = self.Signature;
158+
if (sig is null)
159+
{
160+
return Runtime.PyObject_GenericGetAttr(ob, key);
161+
}
162+
return sig.NewReferenceOrNull().DangerousMoveToPointerOrNull();
163+
case "__name__":
164+
return self.m.GetName().DangerousMoveToPointerOrNull();
94165
default:
95166
return Runtime.PyObject_GenericGetAttr(ob, key);
96167
}

src/runtime/methodobject.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Reflection;
43
using System.Linq;
4+
using System.Reflection;
55

66
namespace Python.Runtime
77
{
@@ -101,6 +101,16 @@ internal IntPtr GetDocString()
101101
return doc;
102102
}
103103

104+
internal NewReference GetName()
105+
{
106+
var names = new HashSet<string>(binder.GetMethods().Select(m => m.Name));
107+
if (names.Count != 1) {
108+
Exceptions.SetError(Exceptions.AttributeError, "a method has no name");
109+
return default;
110+
}
111+
return NewReference.DangerousFromPointer(Runtime.PyString_FromString(names.First()));
112+
}
113+
104114

105115
/// <summary>
106116
/// This is a little tricky: a class can actually have a static method

src/runtime/runtime.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
178178
AssemblyManager.UpdatePath();
179179

180180
clrInterop = GetModuleLazy("clr.interop");
181+
inspect = GetModuleLazy("inspect");
181182
}
182183

183184
private static void InitPyMembers()
@@ -573,6 +574,8 @@ private static void MoveClrInstancesOnwershipToPython()
573574
internal static IntPtr PyNone;
574575
internal static IntPtr Error;
575576

577+
private static Lazy<PyObject> inspect;
578+
internal static PyObject InspectModule => inspect.Value;
576579
private static Lazy<PyObject> clrInterop;
577580
internal static PyObject InteropModule => clrInterop.Value;
578581

0 commit comments

Comments
 (0)