Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
ctypes pythonapi version
-
@omz
ctypes.pythonapi
always points to the C API of Python 3 regardless of the default interpreter setting. Is there anyway to access the Python 2 version ofpythonapi
object? It would be even more fantastic if both of them can be accessed without switch interpreter setting.I also tried to manually load the library with
ctypes.CDLL(os.path.join(os.path.dirname(sys.executable), 'Frameworks/PythonistaKit.framework/PythonistaKit'))
Although it seems to load the Python 2 API and
Py_GetVersion
does show the version to be 2.7. But it is somehow not really usable. Many API calls working with the Python 3 API would not work or even simply crash the app.Any help is appreciated.
-
@ywangd Perhaps the Python 3 interpreter is the "main" DLL that takes priority. The names of the Python 2 and 3 C functions conflict in most places, which means that because
ctypes.pythonapi
is really just actypes.PyDLL(None)
(i. e. accessing global symbols rather than a specific DLL) you can only really access one version with it and that will not change with the interpreter version of the console.If you want to call the active version's C API, you need to wrap its DLL in a
ctypes.PyDLL
instead of actypes.CDLL
so the GIL stays held while you call its functions. If you want to call the other version's C API, you can use a normalctypes.CDLL
, but you need to worry about managing the GIL yourself (thePyGILState
functions are probably the easiest way). -
Thanks @dgelessus
The use ofPyDLL
worked for some initial tests! -
@omz @dgelessus
The following simple code usingpythonapi
works well in Python 2 but errors out in Python 3.import ctypes p3 = ctypes.pythonapi state = p3.PyGILState_Ensure() p3.PyRun_SimpleString('print(42)') p3.PyGILState_Release(state)
The error is
name 'p' is not defined
which is very weird as it suggests that the API does not even parse the given string correctly. It somehow tries to get a variable namedp
which is in fact the first character ofprint
. -
@ywangd That's a
unicode
/str
/bytes
issue. Short answer, the arguments to Python's C API need to be byte strings (b"..."
) unless stated otherwise in the docs. Long explanation below.:)
In C, the type
char
represents a byte (which is generally agreed to be 8 bits nowadays). Most code useschar *
(a pointer to achar
, which is effectively used as an array of unknown size) as the data type for "strings". Because achar
is only 8 bit wide, it can't hold a full Unicode code point. There is thewchar_t
data type, which is not really standardized either, but it's wider thanchar
and can usually hold a Unicode code point, so APIs that support Unicode properly usewchar_t *
instead ofchar *
for strings.In Python 2, the situation is similar.
str
is like C'schar *
- it's made of 8-bit bytes and can't hold Unicode text properly, andunicode
is like C'swchar_t *
and supports full Unicode. That's whyctypes
convertsstr
tochar *
andunicode
towchar_t *
and vice versa.Now Python 3 comes along and cleans up a lot of Python 2's Unicode issues. In Python 3, you have the two data types
bytes
andstr
. Python 3'sbytes
is an 8-bit string like Python 2'sstr
, and Python 3'sstr
is a Unicode string like Python 2'sunicode
. And most importantly, in both Python versions the string"hello"
is of typestr
, which means that under Python 2 it's 8-bit (i. e.char *
) and under Python 3 it's Unicode (i. e.wchar_t *
).Python's C API functions, such as
PyRun_SimpleString
use normalchar *
for source code. So under Python 2, your code works fine -"print(42)"
is an 8-bit string, gets converted tochar *
, which is whatPyRun_SimpleString
wants. Perfect. Under Python 3,"print(42)"
is a Unicode string, which gets converted towchar_t *
, and then things go wrong. Becausewchar_t
is 32 bits wide under iOS, the textprint(42)
represented as awchar_t *
has three null bytes between each character (which would be used if the character had a higher code point in Unicode). Null bytes are also the "end of string" marker in C. Python reads the start of thewchar_t *
string, but expects achar *
- it sees ap
, then a null byte, and thinks "great, I'm done" and so it just runsp
instead ofprint(42)
. -
Thanks a lot @dgelessus ! One cannot ask for a better answer!