os.RTLD_LAZY

Python loads C extension modules with dlopen(); sys.setdlopenflags() chooses the flags for later imports, and ctypes.CDLL(name, mode=...) takes them per library. The usual reason to touch them: make one extension symbols visible to another with RTLD_GLOBAL. Unix only; the constants do not exist on Windows.

os constantsPython 3.3+
Common call
sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)
Returns
None (affects later imports)
Replaces
LD_PRELOAD hacks to share symbols between extensions
Watch out
Restore the old flags afterwards; RTLD_LOCAL is 0 on Linux, so OR-ing it changes nothing
os.RTLD_LAZY / RTLD_NOW / RTLD_GLOBAL / RTLD_LOCAL / RTLD_NODELETE / RTLD_NOLOAD / RTLD_DEEPBIND
→ int

Common patterns

Import an extension with global symbols, then restore
Only the imports inside the try see the changed flags.
import os, sys
old = sys.getdlopenflags()
sys.setdlopenflags(old | os.RTLD_GLOBAL)
try:
    import some_extension
finally:
    sys.setdlopenflags(old)
Load a library globally with ctypes
Later libraries can resolve symbols against it.
import ctypes, os
lib = ctypes.CDLL('libfoo.so', mode=os.RTLD_NOW | os.RTLD_GLOBAL)
Is the library already loaded? (glibc)
RTLD_NOLOAD does not load anything; it fails if the library is not loaded yet.
import ctypes, os
try:
    ctypes.CDLL('libssl.so.3', mode=os.RTLD_NOLOAD)
    loaded = True
except OSError:
    loaded = False

Examples

1. Unix only
import os all(hasattr(os, n) == (os.name == 'posix') for n in ('RTLD_LAZY', 'RTLD_NOW', 'RTLD_GLOBAL', 'RTLD_LOCAL'))
Returns
True
2. sys.getdlopenflags exists on Unix only
import sys hasattr(sys, 'getdlopenflags') == (sys.platform != 'win32')
Returns
True
3. Combine flags with | (Linux values)
RTLD_NOW, RTLD_GLOBAL = 2, 256 RTLD_NOW | RTLD_GLOBAL
Returns
258
4. Remove a flag with & ~
RTLD_NOW, RTLD_GLOBAL = 2, 256 flags = RTLD_NOW | RTLD_GLOBAL flags & ~RTLD_GLOBAL
Returns
2
5. All seven Linux values are distinct
linux = {'RTLD_LAZY': 1, 'RTLD_NOW': 2, 'RTLD_NOLOAD': 4, 'RTLD_DEEPBIND': 8, 'RTLD_GLOBAL': 256, 'RTLD_LOCAL': 0, 'RTLD_NODELETE': 4096} len(set(linux.values()))
Returns
7

Pitfalls

1. Expecting RTLD_LOCAL to switch GLOBAL off
On Linux RTLD_LOCAL is 0, so OR-ing it into flags that already contain RTLD_GLOBAL changes nothing. Clear the GLOBAL bit instead.
flags | RTLD_LOCAL
RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL = 2, 256, 0
flags = RTLD_NOW | RTLD_GLOBAL
flags | RTLD_LOCAL
258
flags & ~RTLD_GLOBAL
RTLD_NOW, RTLD_GLOBAL, RTLD_LOCAL = 2, 256, 0
flags = RTLD_NOW | RTLD_GLOBAL
(flags & ~RTLD_GLOBAL) | RTLD_LOCAL
2
2. Setting the flags and never restoring them
setdlopenflags is process-wide: every extension imported later is loaded the same way. Save the old value and restore it in finally, the same shape as saving any global setting.
set and forget
settings = {'dlopenflags': 2}
settings['dlopenflags'] = 2 | 256
settings['dlopenflags']
258
try / finally
settings = {'dlopenflags': 2}
old = settings['dlopenflags']
settings['dlopenflags'] = old | 256
try:
    pass  # import the extension here
finally:
    settings['dlopenflags'] = old
settings['dlopenflags']
2

When to use

Use it
  • Two C extensions that must share symbols (RTLD_GLOBAL)
  • Loading shared libraries with ctypes in a specific mode
  • Failing early on missing symbols with RTLD_NOW
Reach for something else
  • Windows → there is no dlopen; ctypes.WinDLL / CDLL use LoadLibrary (winmode=)
  • Ordinary Python code → the default flags are right

Notes

CPython impl
sys.setdlopenflags(flags) / sys.getdlopenflags() store the flags used by the import machinery when it dlopen()s an extension module; on our Linux check the default was 2 (RTLD_NOW)
Availability
Unix only; on our Linux check all seven constants exist, on Windows none do. See the dlopen(3) manual page for the exact meaning; NODELETE, NOLOAD and DEEPBIND are extensions beyond POSIX, so not every Unix defines them
Linux values
RTLD_LOCAL 0, RTLD_LAZY 1, RTLD_NOW 2, RTLD_NOLOAD 4, RTLD_DEEPBIND 8, RTLD_GLOBAL 256, RTLD_NODELETE 4096
ctypes
ctypes.RTLD_GLOBAL and ctypes.RTLD_LOCAL mirror these (256 and 0 on Linux; both 0 on Windows), so ctypes code can use them without importing os constants that Windows lacks

FAQ

The RTLD_* constants, sys.setdlopenflags and sys.getdlopenflags exist only on Unix, and changing the flags affects every later import in the process. The examples show the bit arithmetic with the values from Linux.