The main question is: **do you want your configuration file to be in some Turing complete language** (like Python is)? If you do want that, you might also consider embedding some *other* (Turing complete) scripting language like [Guile][1] or [Lua][2] (because there could be perceived as "simpler" to use, or to embed, than Python is; read the chapter on [Extending & Embedding Python][3]). I won't discuss that further (because other answers -e.g. [by Amon][4]- discussed that in depth) but notice that **embedding a scripting language in your application is a *major* architectural choice**, that you should consider very *early*; I really don't recommend making that choice later! A well known example of a program configurable thru "scripts" is the [GNU emacs][5] editor (or probably [AutoCAD][6] in the proprietary realm); so be aware that if you accept scripting, some user would eventually use -and perhaps abuse, in your point of view- that facility extensively and make a multi-thousand lines script; hence the choice of a good enough scripting language is important. However (at least on POSIX systems), you might consider convenient to enable the **configuration "file" to be dynamically *computed* at initialization time** (of course, leaving the burden of a sane configuration to your system admin or user; actually it is a configuration *text* which comes from some file or from some command). For that, you could simply adopt the *convention* (and *document* it) that a configuration file path starting with e.g. a `!` or a `|` is actually a shell *command* that you would read as a [pipeline][7]. This leaves your user with the choice of using whatever "preprocessor" or "scripting language" he is the most familiar with. <sup>(you need to trust your user about security issues if you accept a dynamically computed configuration)</sup> So in your initialization code, your `main` would (for example) accept some `--config` [argument][8] `confarg` and get some `FILE*configf;` from it. If that argument starts with `!` (i.e. if `(confarg[0]=='!')` ....), you would use `configf = popen(confarg+1, "r");` and close that pipe with `pclose(configf);`. Otherwise you would use `configf=fopen(confarg, "r");` and close that file with `fclose(configf);` (don't forget the error checking). See [pipe(7)][9], [popen(3)][10], [fopen(3)][11]. <sup>(document also for the weird user wanting to pass a configuration file named `!foo.config` to pass `./!foo.config` to bypass the `popen` trick above)</sup> BTW, such a trick is only a convenience (to avoid requiring the advanced user to e.g. code some shell script to *generate* a configuration *file*). If the user want to report any bug, he should send you the *generated* configuration file... Notice that you could also **design your application with the ability to use and load [plugins][12]** at initialization time, e.g. with [dlopen(3)][13] (and you need to trust your user about that plugin). Again, this is a very important architectural decision (and you need to *define* and provide some rather stable [API][14] and convention about these plugins and your application). [1]: https://www.gnu.org/software/guile/ [2]: http://lua.org/ [3]: https://docs.python.org/3/extending/index.html [4]: https://softwareengineering.stackexchange.com/a/351128/40065 [5]: https://www.gnu.org/software/emacs/ [6]: https://en.wikipedia.org/wiki/AutoCAD [7]: https://en.wikipedia.org/wiki/Pipeline_(Unix) [8]: https://softwareengineering.stackexchange.com/a/307472/40065 [9]: http://man7.org/linux/man-pages/man7/pipe.7.html [10]: http://man7.org/linux/man-pages/man7/popen.7.html [11]: http://man7.org/linux/man-pages/man3/fopen.3.html [12]: https://en.wikipedia.org/wiki/Plug-in_(computing) [13]: http://man7.org/linux/man-pages/man3/dlopen.3.html [14]: https://en.wikipedia.org/wiki/Application_programming_interface