Package: copperhead
copperheadpython --> snake --> copperhead -->copperhead -->cpp-->C++Introductioncopperheadis a dynamic code generator that allows C++ code blocks to be written and executed within Python. Ultimately the code is wrapped and built into a module usingsetuptools.copperheadis great for prototyping and "what-if" exploration. I would not necessarily recommend using it directly for production code and environments, but it is something that can aid in developing production/release grade modules and libraries.InstallationFor the latest release fromPyPI:pipinstallcopperheadFor the latest dev, clone this repository and:pythonsetup.pyinstallcopperheadwill use the compiler that was used to build your Python installation, and so on older systems you may run into isues until I implement a way to pass compiler flags in (to perhaps revert the C++ standard to something older; right now defaults to C++14).MotivationFirst and foremost I wanted to learn more about the Python C API (something I have only really hit the surface of over the years). I also was watching CppCon videos(this one in particular)and I was fascinated when he (Chandler Carruth) started writing assmebly inline with his C++. I have never worked with C++ on that low a level, and it blew my mind a little. I thought to myself and wondered "what if I could write my C++ inline with my Python?". So here we are today.copperheadgenerates vanilla C++ code without depending on 3rd party C++ libraries. This is donewithin Python, not from a native library or set of headers. This is important, because it means that you can focus on writing the C++ and not on all of the boilerplate that comes with a set of native bindings, build systems, etc. You simply write your C++ and keep going.That is it.When you are done usingcopperheadyou are left with the rawC++ and setup.pyfiles used to generate the extension in the first place. This means that you have something concrete, reusable, tweakable, etc. that is purely standard C++ and standard Python. Say what you will, but this is probably the best part aboutcopperhead.How It WorksCode Generationcopperheadworks by automatically wrapping your C++ code in a new Python API enabled function and pairing it with the necessary code to pack it into a module that is compatible with Python. There are three primary steps to this process:Generate the function wrapperGenerate the moduleGenerate the setup scriptThe function wrapper is the most complicated component.copperheadwill examine the return type and parameter types of the function type supplied and will generate all of the variables needed to convert from Python inputs to C++ types, call your function, and convert the return to a Python type.The module is fairly cookie cutter at the moment and simply includes your C++ function, the wrapper, and all the boilerplate for making a module.The setup script is just asetup.pyscript that we populate with a few bits of data, nothing special here.Installing & Importing C++ ModulesThe newly createdsetup.pyscript is used for generating the module. It will dump the new modules locally in a directory named.copperhead_cache. Your path will be adjusted to include the newly generated egg. If the egg already exists, this process is skipped and you will simply import what is already available. Note that thefunctionis returned, not the module.Development EnvironmentI am coding this to work with latest Python and with modern C++ techniques. I haveabsolutely nolittle interest in backwards compatibility. While earlier versions and standards may work right now, I do not guarantee any of that moving forward. I will not hinder development for the sake of supporting something older. Given the range of development environments currently at my disposal there will be some compatibility for a bit.I have a few primary development environments at the moment and so you can for expect support for at least the following:Python 3.6.7and GCC 7.3.0 (Ubuntu 18.04.2)Python 3.6.8and MSVC v1916 (Windows 10, Visual C++ 2017 (15.9))Python 3.7.3and MSVC v1916 (Windows 10, Visual C++ 2017 (15.9))Python 3.7.3and GCC 6.3.0 (Raspbian)Basic UsageHello WorldHere is a simple example showing how to create your typicalHello World!program:importcopperheadascpphello_world_cpp='''#include <iostream>void hello_world(){std::cout << "Hello World!" << std::endl;}'''hello_world=cpp.generate('hello_world','void()',hello_world_cpp)hello_world()STL SupportBasic STL support is provided now, with more planned. Current support is provided for:sequence containersstd::vectorstd::dequestd::forward_liststd::listcontainer adaptersstd::queuestd::stackYou can pass and return these structues, as well as nest them. Much more is planned, namely associative containers (and their unordered counterparts!).Mandelbrot: A Practical ExampleWant to generate the Mandelbrot Set? Awesome! You get to it and write a simple implementation to generate a file with the escape times:defmandlebrot(c):max_iter=100i=0z=0.0jwhileabs(z)<2.0andi<max_iter:z=z*z+ci+=1returnmax_iter-ix=-2.0y=1.25h=0.0005n=int(2.5/h)withopen('fractal.dat','w')asf:foryidxinrange(n):forxidxinrange(n):f.write('{}'.format(mandlebrot(complex(x,y))))x+=hf.write('\n')y-=hx-=2.5If this is the implementation you want, then fine. On my system it takes ~1 minutes 40 seconds to run. However, I do not want to wait upwards 2 minutes for this to run, and at scale this setup getsworse. There are potentially problems with buffering the output and maybe there are some other micro-optimizations we could make, but the improvements are not substantial and they really do not solve our performance problem.Queue entercopperheadLots of iterating and lots of output. Sounds like something C++ would be pretty good at, right? Let's make an implementation that does something comparable:importcopperheadascppmandelbrot_cpp=r'''#include <cmath>#include <complex>#include <fstream>inline int mandelbrot(const std::complex<double> &c){const int max_iter{100};int i{0};std::complex<double> z {0.0, 0.0};while (std::abs(z) < 2.0 && i < max_iter){z  = z * z + c;++i;}return max_iter - i;}void compute(std::string filename, double x, double y, double h){const auto n {std::lround(2.5 / h)};std::ofstream f(filename);for (long yidx{0}; yidx < n; ++yidx){for (long xidx{0}; xidx < n; ++xidx){f << mandelbrot(std::complex<double>(x, y)) << " ";x  += h;}f << "\n";y -= h;x -= 2.5;}}'''compute=cpp.generate('compute','void(std::string, double, double, double)',mandelbrot_cpp)compute('fractal.dat',-2.0,1.25,0.0005)Before diving into evaluating this against our Python, do note that I decided to make this input driven. We can specify the return and parameter types to the function so that we may pass data in. here we specify the output filename and some parameters that dictate the region of the set we are concerned with and the granularity of our iteration.Running this you will see a good bit of jargon dumped to stdout: this is the creation of the C++ extension (running again, the cache is accessed and you will not see it). On my system this runs in ~9 seconds. 1:40 to 0:09. The improvements here become even greater as the problem space scales. While timing is hardly a valuble benchmark, the savings here are too great and too obvious to ignore.Note that theexample codein this repository usesh = 0.005to reduce the runtime during testing.LicenseSeeLICENSE.mdfor the specifics, but it is an MIT license.