Skip to content

Commit 825c647

Browse files
committed
implemented __signature__ and __name__ on methodbinding
1 parent 1e32d8c commit 825c647

File tree

7 files changed

+134
-7
lines changed

7 files changed

+134
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1414
- Add GetPythonThreadID and Interrupt methods in PythonEngine
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`
17-
- Improved exception handling:
18-
- exceptions can now be converted with codecs
19-
- `InnerException` and `__cause__` are propagated properly
17+
- Improved exception handling:
18+
- exceptions can now be converted with codecs
19+
- `__name__` and `__signature__` to reflected .NET methods
20+
- `InnerException` and `__cause__` are propagated properly
2021

2122
### Changed
2223
- Drop support for Python 2, 3.4, and 3.5

src/embed_tests/Inspect.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,21 @@ 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+
var obj = new Class();
38+
using var scope = Py.CreateScope();
39+
scope.Import("inspect");
40+
scope.Set(nameof(obj), obj);
41+
var spec = scope.Eval($"inspect.getfullargspec({nameof(obj)}.{nameof(Class.Method)})");
42+
}
43+
44+
class Class
45+
{
46+
public void Method(int a, int b = 10) { }
47+
public void Method(int a, object b) { }
48+
}
3349
}
3450
}

src/runtime/exceptions.cs

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

src/runtime/methodbinding.cs

Lines changed: 68 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,59 @@ public static IntPtr mp_subscript(IntPtr tp, IntPtr idx)
6566
return mb.pyHandle;
6667
}
6768

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

69123
/// <summary>
70124
/// MethodBinding __getattribute__ implementation.
@@ -91,6 +145,20 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
91145
case "Overloads":
92146
var om = new OverloadMapper(self.m, self.target);
93147
return om.pyHandle;
148+
case "__signature__" when Runtime.InspectModule is not null:
149+
var sig = self.Signature;
150+
if (sig is null)
151+
{
152+
return Runtime.PyObject_GenericGetAttr(ob, key);
153+
}
154+
return sig.Reference.IsNull
155+
? IntPtr.Zero
156+
: sig.NewReferenceOrNull().DangerousGetAddress();
157+
case "__name__":
158+
var pyName = self.m.GetName();
159+
return pyName == IntPtr.Zero
160+
? IntPtr.Zero
161+
: Runtime.SelfIncRef(pyName);
94162
default:
95163
return Runtime.PyObject_GenericGetAttr(ob, key);
96164
}

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 IntPtr 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 IntPtr.Zero;
110+
}
111+
return 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/pymodule.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using System;
23

34
namespace Python.Runtime
@@ -6,7 +7,7 @@ public class PyModule : PyScope
67
{
78
internal PyModule(ref NewReference reference) : base(ref reference, PyScopeManager.Global) { }
89
public PyModule(PyObject o) : base(o.Reference, PyScopeManager.Global) { }
9-
public PyModule(string name, string filename = null) : this(Create(name, filename)) { }
10+
public PyModule(string name, string? filename = null) : this(Create(name, filename)) { }
1011

1112
/// <summary>
1213
/// Given a module or package name, import the module and return the resulting object.
@@ -19,6 +20,28 @@ public static PyObject Import(string name)
1920
return IsModule(op) ? new PyModule(ref op) : op.MoveToPyObject();
2021
}
2122

23+
/// <summary>
24+
/// Given a module or package name, import the module and return the resulting object.
25+
/// Returns <c>null</c> when module was not found (Python 3.6+).
26+
/// </summary>
27+
/// <param name="name">Fully-qualified module or package name</param>
28+
public static PyObject? TryImport(string name)
29+
{
30+
NewReference op = Runtime.PyImport_ImportModule(name);
31+
if (op.IsNull())
32+
{
33+
if (Exceptions.ModuleNotFoundError == IntPtr.Zero
34+
|| !PythonException.CurrentMatches(Exceptions.ModuleNotFoundError))
35+
{
36+
throw PythonException.ThrowLastAsClrException();
37+
}
38+
39+
Exceptions.Clear();
40+
return null;
41+
}
42+
return IsModule(op) ? new PyModule(ref op) : op.MoveToPyObject();
43+
}
44+
2245
/// <summary>
2346
/// Reloads the module, and returns the updated object
2447
/// </summary>
@@ -38,7 +61,7 @@ public static PyModule FromString(string name, string code)
3861
return new PyModule(ref m);
3962
}
4063

41-
private static PyModule Create(string name, string filename=null)
64+
private static PyModule Create(string name, string? filename = null)
4265
{
4366
if(string.IsNullOrWhiteSpace(name))
4467
{

src/runtime/runtime.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
178178
}
179179
XDecref(item);
180180
AssemblyManager.UpdatePath();
181+
182+
inspect = GetInspectModuleLazy();
181183
}
182184

183185
private static void InitPyMembers()
@@ -291,6 +293,9 @@ private static IntPtr Get_PyObject_NextNotImplemented()
291293
return iternext;
292294
}
293295

296+
private static Lazy<PyObject?> GetInspectModuleLazy()
297+
=> new Lazy<PyObject?>(() => PyModule.TryImport("inspect"), isThreadSafe: false);
298+
294299
/// <summary>
295300
/// Tries to downgrade the shutdown mode, if possible.
296301
/// The only possibles downgrades are:
@@ -586,6 +591,9 @@ public static PyObject None
586591
}
587592
}
588593

594+
private static Lazy<PyObject?> inspect;
595+
internal static PyObject? InspectModule => inspect.Value;
596+
589597
/// <summary>
590598
/// Check if any Python Exceptions occurred.
591599
/// If any exist throw new PythonException.
@@ -2038,7 +2046,7 @@ internal static bool PyType_IsSameAsOrSubtype(BorrowedReference type, BorrowedRe
20382046
internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases);
20392047

20402048
/// <summary>
2041-
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a types base class. Return 0 on success, or return -1 and sets an exception on error.
2049+
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type's base class. Return 0 on success, or return -1 and sets an exception on error.
20422050
/// </summary>
20432051

20442052
internal static int PyType_Ready(IntPtr type) => Delegates.PyType_Ready(type);

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