From 6bfef31fc176159da5898d7c1b06246d7765b54e Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 09:42:32 +0200 Subject: [PATCH 1/8] Add Runtime.PyObject_IsIterable and fix PyIter_Check for Python 2 --- src/runtime/pyobject.cs | 2 +- src/runtime/runtime.cs | 39 +++++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index b7df2a924..1b41b0893 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -802,7 +802,7 @@ public bool IsCallable() /// public bool IsIterable() { - return Runtime.PyIter_Check(obj); + return Runtime.PyObject_IsIterable(obj); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a24b6f6d4..a0703c988 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; using System.Security; using System.Text; @@ -274,11 +274,10 @@ internal static void Initialize() Error = new IntPtr(-1); -#if PYTHON3 IntPtr dllLocal = IntPtr.Zero; if (PythonDll != "__Internal") { - NativeMethods.LoadLibrary(PythonDll); + dllLocal = NativeMethods.LoadLibrary(PythonDll); } _PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dllLocal, "_PyObject_NextNotImplemented"); #if !(MONO_LINUX || MONO_OSX) @@ -286,7 +285,6 @@ internal static void Initialize() { NativeMethods.FreeLibrary(dllLocal); } -#endif #endif // Initialize modules that depend on the runtime class. @@ -349,8 +347,8 @@ internal static int AtExit() #if PYTHON3 internal static IntPtr PyBytesType; - internal static IntPtr _PyObject_NextNotImplemented; #endif + internal static IntPtr _PyObject_NextNotImplemented; internal static IntPtr PyNotImplemented; internal const int Py_LT = 0; @@ -780,6 +778,22 @@ internal static string PyObject_GetTypeName(IntPtr op) return Marshal.PtrToStringAnsi(ppName); } + /// + /// Test whether the Python object is an iterable. + /// + internal static bool PyObject_IsIterable(IntPtr pointer) + { + var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); +#if PYTHON2 + int Py_TPFLAGS_HAVE_ITER = 1 << 7; + long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); + if ((tp_flags & Py_TPFLAGS_HAVE_ITER) == 0) + return false; +#endif + IntPtr tp_iter = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iter); + return tp_iter != null; + } + [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int PyObject_HasAttrString(IntPtr pointer, string name); @@ -1425,17 +1439,18 @@ internal static bool PyTuple_Check(IntPtr ob) // Python iterator API //==================================================================== -#if PYTHON2 - [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern bool PyIter_Check(IntPtr pointer); -#elif PYTHON3 internal static bool PyIter_Check(IntPtr pointer) { - var ob_type = (IntPtr)Marshal.PtrToStructure(pointer + ObjectOffset.ob_type, typeof(IntPtr)); - IntPtr tp_iternext = ob_type + TypeOffset.tp_iternext; + var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); +#if PYTHON2 + int Py_TPFLAGS_HAVE_ITER = 1 << 7; + long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); + if ((tp_flags & Py_TPFLAGS_HAVE_ITER) == 0) + return false; +#endif + IntPtr tp_iternext = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iternext); return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented; } -#endif [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyIter_Next(IntPtr pointer); From d620babe3354d33b8b29ddf505e7ffe8b7087cf8 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 10:30:03 +0200 Subject: [PATCH 2/8] Add test_clrmethod.py and update AUTHORS and CHANGELOG --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/tests/test_clrmethod.py | 71 +++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/tests/test_clrmethod.py diff --git a/AUTHORS.md b/AUTHORS.md index 434c57801..78bb25f9e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -31,6 +31,7 @@ - Matthias Dittrich ([@matthid](https://github.com/matthid)) - Patrick Stewart ([@patstew](https://github.com/patstew)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) +- Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) - Sam Winstanley ([@swinstanley](https://github.com/swinstanley)) - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3bcdfe16..38fa56a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. derived from a .NET class has a `__namespace__` or `__assembly__` attribute (#481) - Fixed conversion of 'float' and 'double' values (#486) +- Fixed 'clrmethod' for python 2 (#492) ## [2.3.0][] - 2017-03-11 diff --git a/src/tests/test_clrmethod.py b/src/tests/test_clrmethod.py new file mode 100644 index 000000000..2a294f5f2 --- /dev/null +++ b/src/tests/test_clrmethod.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +"""Test clrmethod and clrproperty support for calling methods and getting/setting python properties from CLR.""" + +import Python.Test as Test +import System +import pytest +import clr + +class ExampleClrClass(System.Object): + __namespace__ = "PyTest" + def __init__(self): + self._x = 3 + @clr.clrmethod(int, [int]) + def test(self, x): + return x*2 + + def get_X(self): + return self._x + def set_X(self, value): + self._x = value + X = clr.clrproperty(int, get_X, set_X) + + @clr.clrproperty(int) + def Y(self): + return self._x * 2 + +def test_set_and_get_property_from_py(): + """Test usage of CLR defined reference types.""" + t = ExampleClrClass() + assert t.X == 3 + assert t.Y == 3 * 2 + t.X = 4 + assert t.X == 4 + assert t.Y == 4 * 2 + +def test_set_and_get_property_from_clr(): + """Test usage of CLR defined reference types.""" + t = ExampleClrClass() + assert t.GetType().GetProperty("X").GetValue(t) == 3 + assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 + t.GetType().GetProperty("X").SetValue(t, 4) + assert t.GetType().GetProperty("X").GetValue(t) == 4 + assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2 + + +def test_set_and_get_property_from_clr_and_py(): + """Test usage of CLR defined reference types.""" + t = ExampleClrClass() + assert t.GetType().GetProperty("X").GetValue(t) == 3 + assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 + assert t.X == 3 + assert t.Y == 3 * 2 + t.GetType().GetProperty("X").SetValue(t, 4) + assert t.GetType().GetProperty("X").GetValue(t) == 4 + assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2 + assert t.X == 4 + assert t.Y == 4 * 2 + t.X = 5 + assert t.GetType().GetProperty("X").GetValue(t) == 5 + assert t.GetType().GetProperty("Y").GetValue(t) == 5 * 2 + assert t.X == 5 + assert t.Y == 5 * 2 + +def test_method_invocation_from_py(): + t = ExampleClrClass() + assert t.test(41) == 41*2 + +def test_method_invocation_from_clr(): + t = ExampleClrClass() + assert t.GetType().GetMethod("test").Invoke(t, [37]) == 37*2 From 6d736dfb5986b515e6ba9f1f66514712265af425 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 10:42:14 +0200 Subject: [PATCH 3/8] Fix test docstrings --- src/tests/test_clrmethod.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tests/test_clrmethod.py b/src/tests/test_clrmethod.py index 2a294f5f2..a6078bece 100644 --- a/src/tests/test_clrmethod.py +++ b/src/tests/test_clrmethod.py @@ -26,7 +26,7 @@ def Y(self): return self._x * 2 def test_set_and_get_property_from_py(): - """Test usage of CLR defined reference types.""" + """Test setting and getting clr-accessible properties from python.""" t = ExampleClrClass() assert t.X == 3 assert t.Y == 3 * 2 @@ -35,7 +35,7 @@ def test_set_and_get_property_from_py(): assert t.Y == 4 * 2 def test_set_and_get_property_from_clr(): - """Test usage of CLR defined reference types.""" + """Test setting and getting clr-accessible properties from the clr.""" t = ExampleClrClass() assert t.GetType().GetProperty("X").GetValue(t) == 3 assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 @@ -45,7 +45,7 @@ def test_set_and_get_property_from_clr(): def test_set_and_get_property_from_clr_and_py(): - """Test usage of CLR defined reference types.""" + """Test setting and getting clr-accessible properties alternatingly from the clr and from python.""" t = ExampleClrClass() assert t.GetType().GetProperty("X").GetValue(t) == 3 assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2 @@ -63,9 +63,11 @@ def test_set_and_get_property_from_clr_and_py(): assert t.Y == 5 * 2 def test_method_invocation_from_py(): + """Test calling a clr-accessible method from python.""" t = ExampleClrClass() assert t.test(41) == 41*2 def test_method_invocation_from_clr(): + """Test calling a clr-accessible method from the clr.""" t = ExampleClrClass() assert t.GetType().GetMethod("test").Invoke(t, [37]) == 37*2 From a21f5cff0d154c861430b976cc7ef1a0b86f2d89 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 14:09:53 +0200 Subject: [PATCH 4/8] Use IntPtr.Zero instead of null for comparisons with IntPtrs --- src/runtime/runtime.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index a0703c988..bcaa23eb1 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -791,7 +791,7 @@ internal static bool PyObject_IsIterable(IntPtr pointer) return false; #endif IntPtr tp_iter = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iter); - return tp_iter != null; + return tp_iter != IntPtr.Zero; } [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] @@ -1449,7 +1449,7 @@ internal static bool PyIter_Check(IntPtr pointer) return false; #endif IntPtr tp_iternext = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iternext); - return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented; + return tp_iternext != IntPtr.Zero && tp_iternext != _PyObject_NextNotImplemented; } [DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)] From 1d3328494ea33851f0136ce8e2c5879fbe729ea2 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 14:14:05 +0200 Subject: [PATCH 5/8] Add test on Runtime.PyObject_IsIterable and Runtime.PyIter_Check --- src/tests/test_runtime.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/tests/test_runtime.py diff --git a/src/tests/test_runtime.py b/src/tests/test_runtime.py new file mode 100644 index 000000000..c3c95c110 --- /dev/null +++ b/src/tests/test_runtime.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +"""Test clrmethod and clrproperty support for calling methods and getting/setting python properties from CLR.""" + +import Python.Test as Test +import System +import pytest +import clr +import Python.Runtime + +def test_pyobject_isiterable_on_list(): + """Tests that Runtime.PyObject_IsIterable is true for lists .""" + assy=clr.AddReference("Python.Runtime") + x = [] + ip = System.IntPtr.op_Explicit(System.Int64(id(x))) + m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyObject_IsIterable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) + assert m.Invoke(None, [ip]) == True + +def test_pyiter_check_on_list(): + """Tests that Runtime.PyIter_Check is false for lists.""" + assy=clr.AddReference("Python.Runtime") + x = [] + ip = System.IntPtr.op_Explicit(System.Int64(id(x))) + m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) + assert m.Invoke(None, [ip]) == False + +def test_pyiter_check_on_listiterator(): + """Tests that Runtime.PyIter_Check is true for list iterators.""" + assy=clr.AddReference("Python.Runtime") + x = [].__iter__() + ip = System.IntPtr.op_Explicit(System.Int64(id(x))) + m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) + assert m.Invoke(None, [ip]) == True From c538e9f0803cb2179381e75f106ea6b61ade392d Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Thu, 15 Jun 2017 23:26:26 +0200 Subject: [PATCH 6/8] Use the already defined TypeFlags.HaveIter instead of redefining it --- src/runtime/runtime.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index bcaa23eb1..73caeb854 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -785,9 +785,8 @@ internal static bool PyObject_IsIterable(IntPtr pointer) { var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); #if PYTHON2 - int Py_TPFLAGS_HAVE_ITER = 1 << 7; long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); - if ((tp_flags & Py_TPFLAGS_HAVE_ITER) == 0) + if ((tp_flags & TypeFlags.HaveIter) == 0) return false; #endif IntPtr tp_iter = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iter); @@ -1443,9 +1442,8 @@ internal static bool PyIter_Check(IntPtr pointer) { var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); #if PYTHON2 - int Py_TPFLAGS_HAVE_ITER = 1 << 7; long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags); - if ((tp_flags & Py_TPFLAGS_HAVE_ITER) == 0) + if ((tp_flags & TypeFlags.HaveIter) == 0) return false; #endif IntPtr tp_iternext = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iternext); From 1a85eef6465b635deb9a5dfee9aeb4bc4be6bc62 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Fri, 16 Jun 2017 00:55:18 +0200 Subject: [PATCH 7/8] Add PyIter_Check and PyObject_IsIterable tests on threading.Lock, which does not have type feature iter --- src/tests/test_runtime.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tests/test_runtime.py b/src/tests/test_runtime.py index c3c95c110..cc10dd45b 100644 --- a/src/tests/test_runtime.py +++ b/src/tests/test_runtime.py @@ -7,6 +7,7 @@ import pytest import clr import Python.Runtime +import threading def test_pyobject_isiterable_on_list(): """Tests that Runtime.PyObject_IsIterable is true for lists .""" @@ -31,3 +32,19 @@ def test_pyiter_check_on_listiterator(): ip = System.IntPtr.op_Explicit(System.Int64(id(x))) m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) assert m.Invoke(None, [ip]) == True + +def test_pyiter_check_on_threadlock(): + """Tests that Runtime.PyIter_Check is false for threading.Lock, which uses a different code path in PyIter_Check.""" + assy=clr.AddReference("Python.Runtime") + x = threading.Lock() + ip = System.IntPtr.op_Explicit(System.Int64(id(x))) + m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) + assert m.Invoke(None, [ip]) == False + +def test_pyobject_isiterable_on_threadlock(): + """Tests that Runtime.PyObject_IsIterable is false for threading.Lock, which uses a different code path in PyObject_IsIterable.""" + assy=clr.AddReference("Python.Runtime") + x = threading.Lock() + ip = System.IntPtr.op_Explicit(System.Int64(id(x))) + m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyObject_IsIterable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) + assert m.Invoke(None, [ip]) == False From 4be925f974d066be3089429b8574dd3acc9e9656 Mon Sep 17 00:00:00 2001 From: Rickard Holmberg Date: Fri, 16 Jun 2017 13:52:08 +0200 Subject: [PATCH 8/8] Move the tests from test_runtime.py to TestRuntime.py --- src/embed_tests/TestRuntime.cs | 43 ++++++++++++++++++++++++++++- src/tests/test_runtime.py | 50 ---------------------------------- 2 files changed, 42 insertions(+), 51 deletions(-) delete mode 100644 src/tests/test_runtime.py diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 22e6db0a9..2e0598da7 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using Python.Runtime; @@ -47,5 +47,46 @@ public static void RefCountTest() Runtime.Runtime.Py_Finalize(); } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_Test() + { + Runtime.Runtime.Py_Initialize(); + + // Tests that a python list is an iterable, but not an iterator + var pyList = Runtime.Runtime.PyList_New(0); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyList)); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyList)); + + // Tests that a python list iterator is both an iterable and an iterator + var pyListIter = Runtime.Runtime.PyObject_GetIter(pyList); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyListIter)); + Assert.IsTrue(Runtime.Runtime.PyIter_Check(pyListIter)); + + // Tests that a python float is neither an iterable nor an iterator + var pyFloat = Runtime.Runtime.PyFloat_FromDouble(2.73); + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(pyFloat)); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyFloat)); + + Runtime.Runtime.Py_Finalize(); + } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() + { + Runtime.Runtime.Py_Initialize(); + + // Create an instance of threading.Lock, which is one of the very few types that does not have the + // TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check. + var threading = Runtime.Runtime.PyImport_ImportModule("threading"); + var threadingDict = Runtime.Runtime.PyModule_GetDict(threading); + var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); + var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, Runtime.Runtime.PyTuple_New(0)); + + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance)); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance)); + + Runtime.Runtime.Py_Finalize(); + } } } diff --git a/src/tests/test_runtime.py b/src/tests/test_runtime.py deleted file mode 100644 index cc10dd45b..000000000 --- a/src/tests/test_runtime.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Test clrmethod and clrproperty support for calling methods and getting/setting python properties from CLR.""" - -import Python.Test as Test -import System -import pytest -import clr -import Python.Runtime -import threading - -def test_pyobject_isiterable_on_list(): - """Tests that Runtime.PyObject_IsIterable is true for lists .""" - assy=clr.AddReference("Python.Runtime") - x = [] - ip = System.IntPtr.op_Explicit(System.Int64(id(x))) - m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyObject_IsIterable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - assert m.Invoke(None, [ip]) == True - -def test_pyiter_check_on_list(): - """Tests that Runtime.PyIter_Check is false for lists.""" - assy=clr.AddReference("Python.Runtime") - x = [] - ip = System.IntPtr.op_Explicit(System.Int64(id(x))) - m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - assert m.Invoke(None, [ip]) == False - -def test_pyiter_check_on_listiterator(): - """Tests that Runtime.PyIter_Check is true for list iterators.""" - assy=clr.AddReference("Python.Runtime") - x = [].__iter__() - ip = System.IntPtr.op_Explicit(System.Int64(id(x))) - m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - assert m.Invoke(None, [ip]) == True - -def test_pyiter_check_on_threadlock(): - """Tests that Runtime.PyIter_Check is false for threading.Lock, which uses a different code path in PyIter_Check.""" - assy=clr.AddReference("Python.Runtime") - x = threading.Lock() - ip = System.IntPtr.op_Explicit(System.Int64(id(x))) - m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyIter_Check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - assert m.Invoke(None, [ip]) == False - -def test_pyobject_isiterable_on_threadlock(): - """Tests that Runtime.PyObject_IsIterable is false for threading.Lock, which uses a different code path in PyObject_IsIterable.""" - assy=clr.AddReference("Python.Runtime") - x = threading.Lock() - ip = System.IntPtr.op_Explicit(System.Int64(id(x))) - m=assy.GetType("Python.Runtime.Runtime").GetMethod("PyObject_IsIterable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) - assert m.Invoke(None, [ip]) == False