Skip to content

Commit bc8a992

Browse files
authored
Merge branch 'master' into losttech-perf-interop
2 parents 83e0539 + 1bcbeb5 commit bc8a992

File tree

5 files changed

+296
-19
lines changed

5 files changed

+296
-19
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
- Jeff Reback ([@jreback](https://github.com/jreback))
3636
- Joe Frayne ([@jfrayne](https://github.com/jfrayne))
3737
- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter))
38+
- Joe Savage ([@s4v4g3](https://github.com/s4v4g3))
3839
- John Burnett ([@johnburnett](https://github.com/johnburnett))
3940
- John Wilkes ([@jbw3](https://github.com/jbw3))
4041
- Luke Stratman ([@lstratman](https://github.com/lstratman))

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1919
- Improved performance of calls from Python to C#
2020
- Added support for converting python iterators to C# arrays
2121
- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer<TDelegate>(IntPtr)
22+
- Added support for kwarg parameters when calling .NET methods from Python
2223

2324
### Fixed
2425

src/runtime/methodbinder.cs

+99-19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections;
33
using System.Reflection;
44
using System.Text;
5+
using System.Collections.Generic;
6+
using System.Linq;
57

68
namespace Python.Runtime
79
{
@@ -280,6 +282,22 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
280282
{
281283
// loop to find match, return invoker w/ or /wo error
282284
MethodBase[] _methods = null;
285+
286+
var kwargDict = new Dictionary<string, IntPtr>();
287+
if (kw != IntPtr.Zero)
288+
{
289+
var pynkwargs = (int)Runtime.PyDict_Size(kw);
290+
IntPtr keylist = Runtime.PyDict_Keys(kw);
291+
IntPtr valueList = Runtime.PyDict_Values(kw);
292+
for (int i = 0; i < pynkwargs; ++i)
293+
{
294+
var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i));
295+
kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i);
296+
}
297+
Runtime.XDecref(keylist);
298+
Runtime.XDecref(valueList);
299+
}
300+
283301
var pynargs = (int)Runtime.PyTuple_Size(args);
284302
var isGeneric = false;
285303
if (info != null)
@@ -303,11 +321,12 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
303321
ArrayList defaultArgList;
304322
bool paramsArray;
305323

306-
if (!MatchesArgumentCount(pynargs, pi, out paramsArray, out defaultArgList)) {
324+
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList))
325+
{
307326
continue;
308327
}
309328
var outs = 0;
310-
var margs = TryConvertArguments(pi, paramsArray, args, pynargs, defaultArgList,
329+
var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList,
311330
needsResolution: _methods.Length > 1,
312331
outs: out outs);
313332

@@ -351,19 +370,21 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
351370
}
352371

353372
/// <summary>
354-
/// Attempts to convert Python argument tuple into an array of managed objects,
355-
/// that can be passed to a method.
373+
/// Attempts to convert Python positional argument tuple and keyword argument table
374+
/// into an array of managed objects, that can be passed to a method.
356375
/// </summary>
357376
/// <param name="pi">Information about expected parameters</param>
358377
/// <param name="paramsArray"><c>true</c>, if the last parameter is a params array.</param>
359378
/// <param name="args">A pointer to the Python argument tuple</param>
360379
/// <param name="pyArgCount">Number of arguments, passed by Python</param>
380+
/// <param name="kwargDict">Dictionary of keyword argument name to python object pointer</param>
361381
/// <param name="defaultArgList">A list of default values for omitted parameters</param>
362382
/// <param name="needsResolution"><c>true</c>, if overloading resolution is required</param>
363383
/// <param name="outs">Returns number of output parameters</param>
364384
/// <returns>An array of .NET arguments, that can be passed to a method.</returns>
365385
static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
366386
IntPtr args, int pyArgCount,
387+
Dictionary<string, IntPtr> kwargDict,
367388
ArrayList defaultArgList,
368389
bool needsResolution,
369390
out int outs)
@@ -374,7 +395,10 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
374395

375396
for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++)
376397
{
377-
if (paramIndex >= pyArgCount)
398+
var parameter = pi[paramIndex];
399+
bool hasNamedParam = kwargDict.ContainsKey(parameter.Name);
400+
401+
if (paramIndex >= pyArgCount && !hasNamedParam)
378402
{
379403
if (defaultArgList != null)
380404
{
@@ -384,12 +408,19 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
384408
continue;
385409
}
386410

387-
var parameter = pi[paramIndex];
388-
IntPtr op = (arrayStart == paramIndex)
389-
// map remaining Python arguments to a tuple since
390-
// the managed function accepts it - hopefully :]
391-
? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount)
392-
: Runtime.PyTuple_GetItem(args, paramIndex);
411+
IntPtr op;
412+
if (hasNamedParam)
413+
{
414+
op = kwargDict[parameter.Name];
415+
}
416+
else
417+
{
418+
op = (arrayStart == paramIndex)
419+
// map remaining Python arguments to a tuple since
420+
// the managed function accepts it - hopefully :]
421+
? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount)
422+
: Runtime.PyTuple_GetItem(args, paramIndex);
423+
}
393424

394425
bool isOut;
395426
if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut))
@@ -505,29 +536,49 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
505536
return clrtype;
506537
}
507538

508-
static bool MatchesArgumentCount(int argumentCount, ParameterInfo[] parameters,
539+
static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
540+
Dictionary<string, IntPtr> kwargDict,
509541
out bool paramsArray,
510542
out ArrayList defaultArgList)
511543
{
512544
defaultArgList = null;
513545
var match = false;
514546
paramsArray = false;
515547

516-
if (argumentCount == parameters.Length)
548+
if (positionalArgumentCount == parameters.Length)
517549
{
518550
match = true;
519-
} else if (argumentCount < parameters.Length)
551+
}
552+
else if (positionalArgumentCount < parameters.Length)
520553
{
554+
// every parameter past 'positionalArgumentCount' must have either
555+
// a corresponding keyword argument or a default parameter
521556
match = true;
522557
defaultArgList = new ArrayList();
523-
for (var v = argumentCount; v < parameters.Length; v++) {
524-
if (parameters[v].DefaultValue == DBNull.Value) {
558+
for (var v = positionalArgumentCount; v < parameters.Length; v++)
559+
{
560+
if (kwargDict.ContainsKey(parameters[v].Name))
561+
{
562+
// we have a keyword argument for this parameter,
563+
// no need to check for a default parameter, but put a null
564+
// placeholder in defaultArgList
565+
defaultArgList.Add(null);
566+
}
567+
else if (parameters[v].IsOptional)
568+
{
569+
// IsOptional will be true if the parameter has a default value,
570+
// or if the parameter has the [Optional] attribute specified.
571+
// The GetDefaultValue() extension method will return the value
572+
// to be passed in as the parameter value
573+
defaultArgList.Add(parameters[v].GetDefaultValue());
574+
}
575+
else
576+
{
525577
match = false;
526-
} else {
527-
defaultArgList.Add(parameters[v].DefaultValue);
528578
}
529579
}
530-
} else if (argumentCount > parameters.Length && parameters.Length > 0 &&
580+
}
581+
else if (positionalArgumentCount > parameters.Length && parameters.Length > 0 &&
531582
Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)))
532583
{
533584
// This is a `foo(params object[] bar)` style method
@@ -722,4 +773,33 @@ internal Binding(MethodBase info, object inst, object[] args, int outs)
722773
this.outs = outs;
723774
}
724775
}
776+
777+
778+
static internal class ParameterInfoExtensions
779+
{
780+
public static object GetDefaultValue(this ParameterInfo parameterInfo)
781+
{
782+
// parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0
783+
bool hasDefaultValue = (parameterInfo.Attributes & ParameterAttributes.HasDefault) ==
784+
ParameterAttributes.HasDefault;
785+
786+
if (hasDefaultValue)
787+
{
788+
return parameterInfo.DefaultValue;
789+
}
790+
else
791+
{
792+
// [OptionalAttribute] was specified for the parameter.
793+
// See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value
794+
// for rules on determining the value to pass to the parameter
795+
var type = parameterInfo.ParameterType;
796+
if (type == typeof(object))
797+
return Type.Missing;
798+
else if (type.IsValueType)
799+
return Activator.CreateInstance(type);
800+
else
801+
return null;
802+
}
803+
}
804+
}
725805
}

src/testing/methodtest.cs

+33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Runtime.InteropServices;
34

45
namespace Python.Test
56
{
@@ -651,6 +652,38 @@ public static string Casesensitive()
651652
{
652653
return "Casesensitive";
653654
}
655+
656+
public static string DefaultParams(int a=0, int b=0, int c=0, int d=0)
657+
{
658+
return string.Format("{0}{1}{2}{3}", a, b, c, d);
659+
}
660+
661+
public static string OptionalParams([Optional]int a, [Optional]int b, [Optional]int c, [Optional] int d)
662+
{
663+
return string.Format("{0}{1}{2}{3}", a, b, c, d);
664+
}
665+
666+
public static bool OptionalParams_TestMissing([Optional]object a)
667+
{
668+
return a == Type.Missing;
669+
}
670+
671+
public static bool OptionalParams_TestReferenceType([Optional]string a)
672+
{
673+
return a == null;
674+
}
675+
676+
public static string OptionalAndDefaultParams([Optional]int a, [Optional]int b, int c=0, int d=0)
677+
{
678+
return string.Format("{0}{1}{2}{3}", a, b, c, d);
679+
}
680+
681+
public static string OptionalAndDefaultParams2([Optional]int a, [Optional]int b, [Optional, DefaultParameterValue(1)]int c, int d = 2)
682+
{
683+
return string.Format("{0}{1}{2}{3}", a, b, c, d);
684+
}
685+
686+
654687
}
655688

656689

0 commit comments

Comments
 (0)