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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy