Skip to content

Commit c6ae6a4

Browse files
authored
Merge branch 'master' into repr
2 parents eb4004a + 1bcbeb5 commit c6ae6a4

File tree

9 files changed

+387
-83
lines changed

9 files changed

+387
-83
lines changed

AUTHORS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
- Jan Krivanek ([@jakrivan](https://github.com/jakrivan))
3535
- Jeff Reback ([@jreback](https://github.com/jreback))
3636
- Joe Frayne ([@jfrayne](https://github.com/jfrayne))
37+
- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter))
38+
- Joe Savage ([@s4v4g3](https://github.com/s4v4g3))
3739
- John Burnett ([@johnburnett](https://github.com/johnburnett))
3840
- John Wilkes ([@jbw3](https://github.com/jbw3))
3941
- Luke Stratman ([@lstratman](https://github.com/lstratman))

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1616
- Added argument types information to "No method matches given arguments" message
1717
- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures
1818
- Removes PyLong_GetMax and PyClass_New when targetting Python3
19+
- Added support for converting python iterators to C# arrays
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
1922

2023
### Fixed
2124

2225
- Fixed runtime that fails loading when using pythonnet in an environment
2326
together with Nuitka
27+
- Fixes bug where delegates get casts (dotnetcore)
2428

2529
## [2.4.0][]
2630

src/runtime/converter.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -837,50 +837,53 @@ private static void SetConversionError(IntPtr value, Type target)
837837

838838
/// <summary>
839839
/// Convert a Python value to a correctly typed managed array instance.
840-
/// The Python value must support the Python sequence protocol and the
840+
/// The Python value must support the Python iterator protocol or and the
841841
/// items in the sequence must be convertible to the target array type.
842842
/// </summary>
843843
private static bool ToArray(IntPtr value, Type obType, out object result, bool setError)
844844
{
845845
Type elementType = obType.GetElementType();
846-
var size = Runtime.PySequence_Size(value);
847846
result = null;
848847

849-
if (size < 0)
850-
{
848+
bool IsSeqObj = Runtime.PySequence_Check(value);
849+
var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1;
850+
851+
IntPtr IterObject = Runtime.PyObject_GetIter(value);
852+
853+
if(IterObject==IntPtr.Zero) {
851854
if (setError)
852855
{
853856
SetConversionError(value, obType);
854857
}
855858
return false;
856859
}
857860

858-
Array items = Array.CreateInstance(elementType, size);
861+
Array items;
862+
863+
var listType = typeof(List<>);
864+
var constructedListType = listType.MakeGenericType(elementType);
865+
IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) :
866+
(IList) Activator.CreateInstance(constructedListType);
867+
IntPtr item;
859868

860-
// XXX - is there a better way to unwrap this if it is a real array?
861-
for (var i = 0; i < size; i++)
869+
while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero)
862870
{
863871
object obj = null;
864-
IntPtr item = Runtime.PySequence_GetItem(value, i);
865-
if (item == IntPtr.Zero)
866-
{
867-
if (setError)
868-
{
869-
SetConversionError(value, obType);
870-
return false;
871-
}
872-
}
873872

874873
if (!Converter.ToManaged(item, elementType, out obj, true))
875874
{
876875
Runtime.XDecref(item);
877876
return false;
878877
}
879878

880-
items.SetValue(obj, i);
879+
list.Add(obj);
881880
Runtime.XDecref(item);
882881
}
882+
Runtime.XDecref(IterObject);
883883

884+
items = Array.CreateInstance(elementType, list.Count);
885+
list.CopyTo(items, 0);
886+
884887
result = items;
885888
return true;
886889
}

src/runtime/methodbinder.cs

Lines changed: 99 additions & 19 deletions
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/runtime/nativecall.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,21 @@ internal class NativeCall
3232

3333
public static void Void_Call_1(IntPtr fp, IntPtr a1)
3434
{
35-
((Void_1_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Void_1_Delegate)))(a1);
35+
var d = Marshal.GetDelegateForFunctionPointer<Interop.DestructorFunc>(fp);
36+
d(a1);
3637
}
3738

3839
public static IntPtr Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3)
3940
{
40-
var d = (Interop.TernaryFunc)Marshal.GetDelegateForFunctionPointer(fp, typeof(Interop.TernaryFunc));
41+
var d = Marshal.GetDelegateForFunctionPointer<Interop.TernaryFunc>(fp);
4142
return d(a1, a2, a3);
4243
}
4344

4445

4546
public static int Int_Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3)
4647
{
47-
return ((Int_3_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Int_3_Delegate)))(a1, a2, a3);
48+
var d = Marshal.GetDelegateForFunctionPointer<Interop.ObjObjArgFunc>(fp);
49+
return d(a1, a2, a3);
4850
}
4951
#else
5052
private static AssemblyBuilder aBuilder;

0 commit comments

Comments
 (0)