We are trying to expose a python method in .Net, in a class that inherits from System.Object.
We are following the example code in clr.py in the pythonnet repository except that we are inheriting from System.Object and that we are using a namespace. When we run
Traceback (most recent call last):
File "test.py", line 80, in
class X(System.Object):
TypeError: Error when calling the metaclass bases
Unable to find an entry point named 'PyIter_Check' in DLL 'python27'.
The error seems to be due to the fact that PyIter_Check is a #define statement and not a function in python. Therefore the import of the function fails in runtime.cs. The error does not occur in python 3 because PyIter_Check is not imported when python 3 is used.
We have tried to implement PyIter_Check for python 2, similarly to the way it is done for python 3, but we have not gotten it to work. Here is what we tried
I got a bit further, and I believe that I have understood what is happening. ClassDerivedObject.AddPythonMethod calls PyObject.IsIterable() to find out whether it makes sense to call GetEnumerator on that pyobject. PyObject.IsIterable does a PyIter_Check to find out whether the PyObject implements the iterator interface. However, e.g. list is not an iterator, and PyObject.GetEnumerator does not expect it to be - only that there is a valid iter method on it.
So, the test in ClassDerivedObject.AddPythonMethod should probably not be IsIterable, but rather something like HasEnumerator, which would check for the presence of a iter method, not an iternext method as PyIter_Check does.
Update: Calling IsIterable() in ClassDerivedObject.AddPythonMethod is probably correct - it's the implementation which returns wheter the object is an iterator, not an iterable...
Activity
klasma commented on Jun 14, 2017
The definition of PyIterCheck in runtime.cs is
#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; return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented; } #endifWe have tried to implement PyIter_Check for python 2, similarly to the way it is done for python 3, but we have not gotten it to work. Here is what we tried
internal static bool PyIter_Check(IntPtr pointer) { IntPtr pyType = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type); long flags = Marshal.ReadInt64(pyType, TypeOffset.tp_flags); IntPtr iternext = Marshal.ReadIntPtr(pyType, TypeOffset.tp_iternext); return (flags & Py_TPFLAGS_HAVE_ITER) != 0 && (Int64)iternext != 0; }Maybe you can tell us what we are doing wrong.
rickardraysearch commented on Jun 14, 2017
I got a bit further, and I believe that I have understood what is happening. ClassDerivedObject.AddPythonMethod calls PyObject.IsIterable() to find out whether it makes sense to call GetEnumerator on that pyobject. PyObject.IsIterable does a PyIter_Check to find out whether the PyObject implements the iterator interface. However, e.g. list is not an iterator, and PyObject.GetEnumerator does not expect it to be - only that there is a valid iter method on it.
So, the test in ClassDerivedObject.AddPythonMethod should probably not be IsIterable, but rather something like HasEnumerator, which would check for the presence of a iter method, not an iternext method as PyIter_Check does.
den-run-ai commented on Jun 14, 2017
Here is comparison of macros from CPython C-API:
https://github.com/python/cpython/blob/2.7/Include/abstract.h#L637
#define PyIter_Check(obj) \ (PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \ (obj)->ob_type->tp_iternext != NULL && \ (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)https://github.com/python/cpython/blob/3.6/Include/abstract.h#L711
#define PyIter_Check(obj) \ ((obj)->ob_type->tp_iternext != NULL && \ (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)rickardraysearch commented on Jun 14, 2017
Yes, and the only difference there is that in python 3.6, the iter and iternext fields are always present.
rickardraysearch commented on Jun 14, 2017
Update: Calling IsIterable() in ClassDerivedObject.AddPythonMethod is probably correct - it's the implementation which returns wheter the object is an iterator, not an iterable...
rickardraysearch commented on Jun 14, 2017
Perhaps it's easier to discuss in code. I put up a prospective fix at rickardraysearch@5d052ca
den-run-ai commented on Jun 14, 2017
@rickardraysearch feel free to submit a pull request!
rickardraysearch commented on Jun 15, 2017
Hi @denfromufa , now there's a PR that passes all tests and might be possible to merge.
den-run-ai commented on Jun 17, 2017
Thanks for finding this issue and fixing it!