| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f1da55e commit 1bcbeb5
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,6 +35,7 @@ | |||
| 35 | 35 | - Jeff Reback ([@jreback](https://github.com/jreback)) | |
| 36 | 36 | - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) | |
| 37 | 37 | - Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter)) | |
| 38 | + - Joe Savage ([@s4v4g3](https://github.com/s4v4g3)) | ||
| 38 | 39 | - John Burnett ([@johnburnett](https://github.com/johnburnett)) | |
| 39 | 40 | - John Wilkes ([@jbw3](https://github.com/jbw3)) | |
| 40 | 41 | - Luke Stratman ([@lstratman](https://github.com/lstratman)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 18 | 18 | - Removes PyLong_GetMax and PyClass_New when targetting Python3 | |
| 19 | 19 | - Added support for converting python iterators to C# arrays | |
| 20 | 20 | - Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer<TDelegate>(IntPtr) | |
| 21 | + - Added support for kwarg parameters when calling .NET methods from Python | ||
| 21 | 22 | ||
| 22 | 23 | ### Fixed | |
| 23 | 24 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,8 @@ | |||
| 2 | 2 | using System.Collections; | |
| 3 | 3 | using System.Reflection; | |
| 4 | 4 | using System.Text; | |
| 5 | + using System.Collections.Generic; | ||
| 6 | + using System.Linq; | ||
| 5 | 7 | ||
| 6 | 8 | namespace Python.Runtime | |
| 7 | 9 | { | |
@@ -280,6 +282,22 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth | |||
| 280 | 282 | { | |
| 281 | 283 | // loop to find match, return invoker w/ or /wo error | |
| 282 | 284 | 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 | + | ||
| 283 | 301 | var pynargs = (int)Runtime.PyTuple_Size(args); | |
| 284 | 302 | var isGeneric = false; | |
| 285 | 303 | if (info != null) | |
@@ -303,11 +321,12 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth | |||
| 303 | 321 | ArrayList defaultArgList; | |
| 304 | 322 | bool paramsArray; | |
| 305 | 323 | ||
| 306 | - if (!MatchesArgumentCount(pynargs, pi, out paramsArray, out defaultArgList)) { | ||
| 324 | + if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList)) | ||
| 325 | + { | ||
| 307 | 326 | continue; | |
| 308 | 327 | } | |
| 309 | 328 | var outs = 0; | |
| 310 | - var margs = TryConvertArguments(pi, paramsArray, args, pynargs, defaultArgList, | ||
| 329 | + var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, | ||
| 311 | 330 | needsResolution: _methods.Length > 1, | |
| 312 | 331 | outs: out outs); | |
| 313 | 332 | ||
@@ -351,19 +370,21 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth | |||
| 351 | 370 | } | |
| 352 | 371 | ||
| 353 | 372 | /// <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. | ||
| 356 | 375 | /// </summary> | |
| 357 | 376 | /// <param name="pi">Information about expected parameters</param> | |
| 358 | 377 | /// <param name="paramsArray"><c>true</c>, if the last parameter is a params array.</param> | |
| 359 | 378 | /// <param name="args">A pointer to the Python argument tuple</param> | |
| 360 | 379 | /// <param name="pyArgCount">Number of arguments, passed by Python</param> | |
| 380 | + /// <param name="kwargDict">Dictionary of keyword argument name to python object pointer</param> | ||
| 361 | 381 | /// <param name="defaultArgList">A list of default values for omitted parameters</param> | |
| 362 | 382 | /// <param name="needsResolution"><c>true</c>, if overloading resolution is required</param> | |
| 363 | 383 | /// <param name="outs">Returns number of output parameters</param> | |
| 364 | 384 | /// <returns>An array of .NET arguments, that can be passed to a method.</returns> | |
| 365 | 385 | static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, | |
| 366 | 386 | IntPtr args, int pyArgCount, | |
| 387 | + Dictionary<string, IntPtr> kwargDict, | ||
| 367 | 388 | ArrayList defaultArgList, | |
| 368 | 389 | bool needsResolution, | |
| 369 | 390 | out int outs) | |
@@ -374,7 +395,10 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, | |||
| 374 | 395 | ||
| 375 | 396 | for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++) | |
| 376 | 397 | { | |
| 377 | - if (paramIndex >= pyArgCount) | ||
| 398 | + var parameter = pi[paramIndex]; | ||
| 399 | + bool hasNamedParam = kwargDict.ContainsKey(parameter.Name); | ||
| 400 | + | ||
| 401 | + if (paramIndex >= pyArgCount && !hasNamedParam) | ||
| 378 | 402 | { | |
| 379 | 403 | if (defaultArgList != null) | |
| 380 | 404 | { | |
@@ -384,12 +408,19 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray, | |||
| 384 | 408 | continue; | |
| 385 | 409 | } | |
| 386 | 410 | ||
| 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 | + } | ||
| 393 | 424 | ||
| 394 | 425 | bool isOut; | |
| 395 | 426 | if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut)) | |
@@ -505,29 +536,49 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool | |||
| 505 | 536 | return clrtype; | |
| 506 | 537 | } | |
| 507 | 538 | ||
| 508 | - static bool MatchesArgumentCount(int argumentCount, ParameterInfo[] parameters, | ||
| 539 | + static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters, | ||
| 540 | + Dictionary<string, IntPtr> kwargDict, | ||
| 509 | 541 | out bool paramsArray, | |
| 510 | 542 | out ArrayList defaultArgList) | |
| 511 | 543 | { | |
| 512 | 544 | defaultArgList = null; | |
| 513 | 545 | var match = false; | |
| 514 | 546 | paramsArray = false; | |
| 515 | 547 | ||
| 516 | - if (argumentCount == parameters.Length) | ||
| 548 | + if (positionalArgumentCount == parameters.Length) | ||
| 517 | 549 | { | |
| 518 | 550 | match = true; | |
| 519 | - } else if (argumentCount < parameters.Length) | ||
| 551 | + } | ||
| 552 | + else if (positionalArgumentCount < parameters.Length) | ||
| 520 | 553 | { | |
| 554 | + // every parameter past 'positionalArgumentCount' must have either | ||
| 555 | + // a corresponding keyword argument or a default parameter | ||
| 521 | 556 | match = true; | |
| 522 | 557 | 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 | + { | ||
| 525 | 577 | match = false; | |
| 526 | - } else { | ||
| 527 | - defaultArgList.Add(parameters[v].DefaultValue); | ||
| 528 | 578 | } | |
| 529 | 579 | } | |
| 530 | - } else if (argumentCount > parameters.Length && parameters.Length > 0 && | ||
| 580 | + } | ||
| 581 | + else if (positionalArgumentCount > parameters.Length && parameters.Length > 0 && | ||
| 531 | 582 | Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute))) | |
| 532 | 583 | { | |
| 533 | 584 | // This is a `foo(params object[] bar)` style method | |
@@ -722,4 +773,33 @@ internal Binding(MethodBase info, object inst, object[] args, int outs) | |||
| 722 | 773 | this.outs = outs; | |
| 723 | 774 | } | |
| 724 | 775 | } | |
| 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 | + } | ||
| 725 | 805 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.IO; | |
| 3 | + using System.Runtime.InteropServices; | ||
| 3 | 4 | ||
| 4 | 5 | namespace Python.Test | |
| 5 | 6 | { | |
@@ -651,6 +652,38 @@ public static string Casesensitive() | |||
| 651 | 652 | { | |
| 652 | 653 | return "Casesensitive"; | |
| 653 | 654 | } | |
| 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 | + | ||
| 654 | 687 | } | |
| 655 | 688 | ||
| 656 | 689 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments