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
}
0 commit comments