After reading the Apple documentation for Executing Mach-O Files, it says:
The two-level namespace function of OS X v10.1 and later adds the module name as part of the symbol name of the characters defined in It. This approach ensures that module symbol names do not conflict with names used in other modules .
So in my example, I am loading python2 and python3 into the same process. Both Python libraries (by default) are compiled with a two-level version of the namespace. Both libs are also loaded with the RTLD_GLOBAL flag via dlopen (..), so characters with the same name should not interfere with each other, since the two modules have different names (python27 and python36).
Example:
#include <{...}/include/python2.7/Python.h> int main(int argc, const char * argv[]) { auto* py3 = dlopen(".../python36", RTLD_GLOBAL | RTLD_NOW); if (py3 == nullptr) return 0; auto* py2 = dlopen(".../python27", RTLD_GLOBAL | RTLD_NOW); if (py2 == nullptr) return 0; auto* init = ((decltype(Py_Initialize)*)dlsym(py2, "Py_Initialize")); if (init) { init(); } return 0; }
The problem is that after importing python2 /path/to/python2/lib/lib-dynload/_locale.so , the PyModule_GetDict function is PyModule_GetDict from python3. Why is this? How can this happen? Shouldn't this hinder a two-level namespace?
PS lib-dynload is a directory with additional C modules for Python on macOS. I have verified that the correct _local.so lib is _local.so from the python2 environment.

Edit:
After some experimentation, I saw that the symbols of the first loaded python lib always get a higher priority, but are not sure, although if it is intended for the first loaded libs or still "undefined ground behavior".
Call Py_Initialize () from python27 - Success:
1. Loading python27 first 2. Loading python36 second 3. PYTHONHOME to python27 4. cal Py_Initialize() of python27
Py_Initialize () call python27 - Failure:
1. Loading python36 first 2. Loading python27 second 3. PYTHONHOME to python27 4. cal Py_Initialize() of python27
I get the same results differently.
Call Py_Initialize () for python36 - Success:
1. Loading python36 first 2. Loading python27 second 3. PYTHONHOME to python36 4. cal Py_Initialize() of python36
Py_Initialize () call python36 - Failure:
1. Loading python27 first 2. Loading python36 second 3. PYTHONHOME to python36 4. cal Py_Initialize() of python36