Search code examples
pythonwindowspython-importpywin32pth

How does "import pythoncom" find the right files?


NB: I know the proper solution is just to do pip install pywin32 and let everything be done automatically, but here this question is about the internals of pythoncom.

When doing import pythoncom, it works, but:

  • in "C:\Python38\Lib\site-packages\pythoncom.py", there is import pywintypes, but no pywintypes.py or .pyd or .dll is present in the site-packages directory. How can it "magically" find pywintypes?

  • when doing print(pythoncom.__file__), we see:

    'C:\\Python38\\lib\\site-packages\\pywin32_system32\\pythoncom38.dll'
    

    How is this stunning behaviour possible internally? (i.e. pythoncom.py that we imported is now recognized as another file, a .dll)

  • Also, pythoncom.py contains:

    # Magic utility that "redirects" to pythoncomxx.dll
    import pywintypes
    
    pywintypes.__import_pywin32_system_module__("pythoncom", globals())
    

What and where is this (I quote) "magic utility" that redirects to pythoncomxx.dll?

I don't see where this utility is called when doing just import pythoncom.


Solution

  • I believe the magic utility is pywintypes.__import_pywin32_system_module__ combined with _win32sysloader.

    The DLL path is built here (where modname is 'pythoncom'):

        suffix = "_d" if "_d.pyd" in importlib.machinery.EXTENSION_SUFFIXES else ""
        filename = "%s%d%d%s.dll" % (
            modname,
            sys.version_info[0],
            sys.version_info[1],
            suffix,
        )
    

    which gets passed to _win32sysloader:

        found = _win32sysloader.LoadModule(filename)
    

    which is a C++ file that loads the DLL:

        HINSTANCE hinst = LoadLibraryEx(modName, NULL,
                                        LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
                                        LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
    

    The final result is the path of the loaded DLL, which gets registered as a Python module here:

        # Load the DLL.
        loader = importlib.machinery.ExtensionFileLoader(modname, found)
        spec = importlib.machinery.ModuleSpec(name=modname, loader=loader, origin=found)
        mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mod)
    

    As for why pywintypes, it should be under Lib/site-packages/win32/lib/pywintypes.py. But if you actually import it you get another DLL path:

    >>> import pywintypes
    >>> sys.modules['pywintypes']
    <module 'pywintypes' (C:\Python312\Lib\site-packages\pywin32_system32\pywintypes312.dll)>
    

    But that's because pywintypes uses its own __import_pywin32_system_module__ function to replace itself:

    __import_pywin32_system_module__("pywintypes", globals())
    

    And finally, the reason that importing pywintypes from Lib\site-packages\win32\lib works is due to the library's pywin32.pth file (per @Jeronimo's comment), a path configuration file. This file is automatically imported at interpreter start, and the paths there are added to the import paths.