-
Notifications
You must be signed in to change notification settings - Fork 756
Enable user classes to override how Python.NET processes parameters of their functions #835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c355bd6
b5cae6c
bea9e0d
11a3ee6
9c81661
b6421e9
6cd7bd4
56ca4c9
7f894fa
aaafea8
3804438
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…g PyArgConverter attribute
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System; | ||
using NUnit.Framework; | ||
using Python.Runtime; | ||
|
||
namespace Python.EmbeddingTest | ||
{ | ||
class TestCustomArgMarshal | ||
{ | ||
[OneTimeSetUp] | ||
public void SetUp() | ||
{ | ||
PythonEngine.Initialize(); | ||
} | ||
|
||
[OneTimeTearDown] | ||
public void Dispose() | ||
{ | ||
PythonEngine.Shutdown(); | ||
} | ||
|
||
[Test] | ||
public void CustomArgMarshaller() | ||
{ | ||
var obj = new CustomArgMarshaling(); | ||
using (Py.GIL()) { | ||
dynamic callWithInt = PythonEngine.Eval("lambda o: o.CallWithInt('42')"); | ||
callWithInt(obj.ToPython()); | ||
} | ||
Assert.AreEqual(expected: 42, actual: obj.LastArgument); | ||
} | ||
} | ||
|
||
[PyArgConverter(typeof(CustomArgConverter))] | ||
class CustomArgMarshaling { | ||
public object LastArgument { get; private set; } | ||
public void CallWithInt(int value) => this.LastArgument = value; | ||
} | ||
|
||
class CustomArgConverter : DefaultPyArgumentConverter { | ||
public override bool TryConvertArgument(IntPtr pyarg, Type parameterType, bool needsResolution, | ||
out object arg, out bool isOut) { | ||
if (parameterType != typeof(int)) | ||
return base.TryConvertArgument(pyarg, parameterType, needsResolution, out arg, out isOut); | ||
|
||
bool isString = base.TryConvertArgument(pyarg, typeof(string), needsResolution, | ||
out arg, out isOut); | ||
if (!isString) return false; | ||
|
||
int number; | ||
if (!int.TryParse((string)arg, out number)) return false; | ||
arg = number; | ||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Diagnostics; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Python.Runtime | ||
{ | ||
using System.Linq; | ||
|
||
/// <summary> | ||
/// A MethodBinder encapsulates information about a (possibly overloaded) | ||
/// managed method, and is responsible for selecting the right method given | ||
|
@@ -19,16 +22,16 @@ internal class MethodBinder | |
public MethodBase[] methods; | ||
public bool init = false; | ||
public bool allow_threads = true; | ||
readonly IPyArgumentConverter pyArgumentConverter = DefaultPyArgumentConverter.Instance; | ||
IPyArgumentConverter pyArgumentConverter; | ||
|
||
internal MethodBinder() | ||
{ | ||
list = new ArrayList(); | ||
} | ||
|
||
internal MethodBinder(MethodInfo mi) | ||
internal MethodBinder(MethodInfo mi): this() | ||
{ | ||
list = new ArrayList { mi }; | ||
this.AddMethod(mi); | ||
} | ||
|
||
public int Count | ||
|
@@ -38,6 +41,7 @@ public int Count | |
|
||
internal void AddMethod(MethodBase m) | ||
{ | ||
Debug.Assert(!init); | ||
list.Add(m); | ||
} | ||
|
||
|
@@ -165,11 +169,40 @@ internal MethodBase[] GetMethods() | |
// I'm sure this could be made more efficient. | ||
list.Sort(new MethodSorter()); | ||
methods = (MethodBase[])list.ToArray(typeof(MethodBase)); | ||
pyArgumentConverter = this.GetArgumentConverter(); | ||
init = true; | ||
} | ||
return methods; | ||
} | ||
|
||
IPyArgumentConverter GetArgumentConverter() { | ||
IPyArgumentConverter converter = null; | ||
Type converterType = null; | ||
foreach (MethodBase method in this.methods) | ||
{ | ||
var attribute = method.DeclaringType? | ||
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) | ||
.OfType<PyArgConverterAttribute>() | ||
.SingleOrDefault() | ||
?? method.DeclaringType?.Assembly | ||
.GetCustomAttributes(typeof(PyArgConverterAttribute), inherit: false) | ||
.OfType<PyArgConverterAttribute>() | ||
.SingleOrDefault(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here you could additionally introduce a "global" (maybe per engine) lookup table ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did not want to overoptimize at first. Weren't there some issues with AppDomains for global caches? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, there is plenty of brokenness in that general direction, that's why I put the "(maybe per engine)" there. We have other global data though already, and for the one usecase of multiple AppDomains that I know 8000 of (reloading code in Unity) it works because they take down the PythonEngine and restart it, s.t. always exactly one is running. The comment was also more of a "fyi", not a requirement for merging :) |
||
if (converterType == null) | ||
{ | ||
if (attribute == null) continue; | ||
|
||
converterType = attribute.ConverterType; | ||
converter = attribute.Converter; | ||
} else if (converterType != attribute?.ConverterType) | ||
{ | ||
throw new NotSupportedException("All methods must have the same IPyArgumentConverter"); | ||
} | ||
} | ||
|
||
return converter ?? DefaultPyArgumentConverter.Instance; | ||
} | ||
|
||
/// <summary> | ||
/// Precedence algorithm largely lifted from Jython - the concerns are | ||
/// generally the same so we'll start with this and tweak as necessary. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to the top.