IMHO it is better to write an application in C++ using Qt or WxWidgets and link it statically to have a fighting chance of being able to install & run an application next year compared to using Python.
It was so nice to find a program backthen written with delphi, that just did run without any dll frameworks nor heavy installers .. most of the time they still run as back then.
I wonder if it would be useful to show how to create a dynamically linked application that can use either system libraries, or specific versions of the dynamic libraries provided with the software itself. (I can only show an example on Linux, and maybe BSDs and Macs, since they too use ELF binaries.)
Let me sketch the basic idea, since it is rather simple.
Let's say your binary is at
/path/bin/appname. It is actually a shell script that sets up the environment variables for the dynamic linker, and then executes (replaces itself with) the actual application binary,
/path/bin/appname.bin. This is already very common in Unix/Linux/POSIX/BSD environment: for example,
/usr/bin/firefox is a shell script that does
exec /usr/lib/firefox/firefox.
You have a full set of dynamic libraries provided with the app installed in say
/path/lib/appname/app.libs/, and a full set of symlinks to either system libraries or provided versions in say
/path/lib/appname/lib/. The dynamic linker is instructed to only use that directory,
/path/lib/appname.lib/, for the dynamic libraries.
The shell script also verifies that none of the dynamic libraries is dangling. (They shouldn't be, as they should be symlinked to the backwards-compatible version of the library, typically the major version.) If it finds any, it just reports that the operating system environment has changed; one way is to launch a helper app that can report (and fix) the dangling symlinks (by pointing them to the provided versions of the dynamic libraries – changing the symlinks is usually a privileged operation on these OSes). That helper app would run exclusively on top of the app-provided libraries, of course.
In essence, each application has a set of symlinks to the dynamic libraries it uses, and a launcher script that tells the dynamic library to use that directory for the dynamic libraries.
At install time, you do need logic that looks at the standard dynamic libraries already installed on the OS, and either heuristically determines (and lets the user override) for each library file whether the OS-provided one or the app-provided one is used; typically just based on the library minor version number.
In Linux, one can use
ldd to list the dynamic libraries needed by an application (although you may need to run it recursively to find all the dependencies, since it is perfectly normal for a dynamic library to have other dynamic libraries as dependencies), so you don't actually need to do anything in the C/C++ source code. It is just a matter of determining the full set of dynamic libraries needed.
With Python it is slightly more involved, because the interpreter itself has one set of dependencies, and the modules and scripts another set that are only accessed if actually used.
This kind of app should be self-contained if necessary, but also able to use (bug-fixed) versions of libraries when desired without recompiling.