From b88be08ce1c2a35f24316005607a83520780dbae Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Tue, 25 Jun 2019 11:50:19 -0700 Subject: [PATCH] implemented __signature__ and __name__ on methodbinding --- CHANGELOG.md | 5 ++- src/embed_tests/Inspect.cs | 25 +++++++++++++ src/runtime/exceptions.cs | 1 + src/runtime/methodbinding.cs | 71 ++++++++++++++++++++++++++++++++++++ src/runtime/methodobject.cs | 12 +++++- src/runtime/runtime.cs | 3 ++ 6 files changed, 114 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3075e2e2..5cb3fc14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) - `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` - Improved exception handling: -- exceptions can now be converted with codecs -- `InnerException` and `__cause__` are propagated properly + * exceptions can now be converted with codecs + * `InnerException` and `__cause__` are propagated properly +- `__name__` and `__signature__` to reflected .NET methods - .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). - .NET arrays implement Python buffer protocol diff --git a/src/embed_tests/Inspect.cs b/src/embed_tests/Inspect.cs index 823a0169a..8ff94e02c 100644 --- a/src/embed_tests/Inspect.cs +++ b/src/embed_tests/Inspect.cs @@ -30,5 +30,30 @@ public void InstancePropertiesVisibleOnClass() var pyProp = (PropertyObject)ManagedType.GetManagedObject(property.Reference); Assert.AreEqual(nameof(Uri.AbsoluteUri), pyProp.info.Value.Name); } + + [Test] + public void BoundMethodsAreInspectable() + { + using var scope = Py.CreateScope(); + try + { + scope.Import("inspect"); + } + catch (PythonException) + { + Assert.Inconclusive("Python build does not include inspect module"); + return; + } + + var obj = new Class(); + scope.Set(nameof(obj), obj); + using var spec = scope.Eval($"inspect.getfullargspec({nameof(obj)}.{nameof(Class.Method)})"); + } + + class Class + { + public void Method(int a, int b = 10) { } + public void Method(int a, object b) { } + } } } diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index f1a06c328..8c09cd608 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -420,6 +420,7 @@ public static variables on the Exceptions class filled in from public static IntPtr IOError; public static IntPtr OSError; public static IntPtr ImportError; + public static IntPtr ModuleNotFoundError; public static IntPtr IndexError; public static IntPtr KeyError; public static IntPtr KeyboardInterrupt; diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs index c1e729f9e..dcd2175b0 100644 --- a/src/runtime/methodbinding.cs +++ b/src/runtime/methodbinding.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace Python.Runtime @@ -65,6 +66,67 @@ public static IntPtr mp_subscript(IntPtr tp, IntPtr idx) return mb.pyHandle; } + PyObject Signature + { + get + { + var infos = this.info.Valid ? new[] { this.info.Value } : this.m.info; + Type type = infos.Select(i => i.DeclaringType) + .OrderByDescending(t => t, new TypeSpecificityComparer()) + .First(); + infos = infos.Where(info => info.DeclaringType == type).ToArray(); + // this is a primitive version + // the overload with the maximum number of parameters should be used + MethodInfo primary = infos.OrderByDescending(i => i.GetParameters().Length).First(); + var primaryParameters = primary.GetParameters(); + PyObject signatureClass = Runtime.InspectModule.GetAttr("Signature"); + var primaryReturn = primary.ReturnParameter; + + using var parameters = new PyList(); + using var parameterClass = primaryParameters.Length > 0 ? Runtime.InspectModule.GetAttr("Parameter") : null; + using var positionalOrKeyword = parameterClass?.GetAttr("POSITIONAL_OR_KEYWORD"); + for (int i = 0; i < primaryParameters.Length; i++) + { + var parameter = primaryParameters[i]; + var alternatives = infos.Select(info => + { + ParameterInfo[] altParamters = info.GetParameters(); + return i < altParamters.Length ? altParamters[i] : null; + }).Where(p => p != null); + using var defaultValue = alternatives + .Select(alternative => alternative.DefaultValue != DBNull.Value ? alternative.DefaultValue.ToPython() : null) + .FirstOrDefault(v => v != null) ?? parameterClass.GetAttr("empty"); + + if (alternatives.Any(alternative => alternative.Name != parameter.Name)) + { + return signatureClass.Invoke(); + } + + using var args = new PyTuple(new[] { parameter.Name.ToPython(), positionalOrKeyword }); + using var kw = new PyDict(); + if (defaultValue is not null) + { + kw["default"] = defaultValue; + } + using var parameterInfo = parameterClass.Invoke(args: args, kw: kw); + parameters.Append(parameterInfo); + } + + // TODO: add return annotation + return signatureClass.Invoke(parameters); + } + } + + struct TypeSpecificityComparer : IComparer + { + public int Compare(Type a, Type b) + { + if (a == b) return 0; + if (a.IsSubclassOf(b)) return 1; + if (b.IsSubclassOf(a)) return -1; + throw new NotSupportedException(); + } + } /// /// MethodBinding __getattribute__ implementation. @@ -91,6 +153,15 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key) case "Overloads": var om = new OverloadMapper(self.m, self.target); return om.pyHandle; + case "__signature__" when Runtime.InspectModule is not null: + var sig = self.Signature; + if (sig is null) + { + return Runtime.PyObject_GenericGetAttr(ob, key); + } + return sig.NewReferenceOrNull().DangerousMoveToPointerOrNull(); + case "__name__": + return self.m.GetName().DangerousMoveToPointerOrNull(); default: return Runtime.PyObject_GenericGetAttr(ob, key); } diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index 2787ec999..655ac4b43 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Linq; +using System.Reflection; namespace Python.Runtime { @@ -101,6 +101,16 @@ internal IntPtr GetDocString() return doc; } + internal NewReference GetName() + { + var names = new HashSet(binder.GetMethods().Select(m => m.Name)); + if (names.Count != 1) { + Exceptions.SetError(Exceptions.AttributeError, "a method has no name"); + return default; + } + return NewReference.DangerousFromPointer(Runtime.PyString_FromString(names.First())); + } + /// /// This is a little tricky: a class can actually have a static method diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 8cdd6eb70..d2653a510 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -178,6 +178,7 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd AssemblyManager.UpdatePath(); clrInterop = GetModuleLazy("clr.interop"); + inspect = GetModuleLazy("inspect"); } private static void InitPyMembers() @@ -573,6 +574,8 @@ private static void MoveClrInstancesOnwershipToPython() internal static IntPtr PyNone; internal static IntPtr Error; + private static Lazy inspect; + internal static PyObject InspectModule => inspect.Value; private static Lazy clrInterop; internal static PyObject InteropModule => clrInterop.Value; 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