diff --git a/examples/cython/c_interface/setup.py b/examples/cython/c_interface/setup.py
index 2f0ce429cdcec75676a2408ee08f4855c40e314e..15a0b895ac5fd8a1ed57d7f3d3b249c297f49ce1 100644
--- a/examples/cython/c_interface/setup.py
+++ b/examples/cython/c_interface/setup.py
@@ -1,29 +1,9 @@
 import os
-from setuptools import setup, Extension, Command
+from setuptools import setup, Extension
 
 ext = Extension("foobar.hello",
                 sources=["foobar/hello.pyx", "foobar/c_hello.c"])
 
-class CleanCommand(Command):
-    """Enable `python setup.py clean` to tidy up properly."""
-    user_options = []
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        os.system('rm -vrf build')
-        os.system('rm -vrf dist')
-        os.system('rm -vrf foobar/hello.c')
-        os.system('rm -vrf foobar/__pycache__')
-        os.system('rm -vrf foobar.egg-info')
-        os.system("find foobar -name '*.so' -delete -print")
-        os.system("find foobar -name '*.pyc' -delete -print")
-
-
 setup(name="foobar",
       ext_modules=[ext],
-      cmdclass={'clean': CleanCommand})
+)
diff --git a/examples/cython/c_interface_shared_object/setup.py b/examples/cython/c_interface_shared_object/setup.py
index 4abb29edeec6df214189708beff19eeb848b5fe3..f19d9cf7f838d34a9529768f191c9026841cea34 100644
--- a/examples/cython/c_interface_shared_object/setup.py
+++ b/examples/cython/c_interface_shared_object/setup.py
@@ -1,5 +1,5 @@
 import os
-from setuptools import setup, Extension, Command
+from setuptools import setup, Extension
 
 
 libhello_dir = os.path.abspath("./libhello")
@@ -16,34 +16,11 @@ ld_flags.append("-L"+libhello_dir)
 ld_flags.append("-Wl,-rpath,"+libhello_dir)
 ld_flags.append("-lhello")  # link libhello
 
-
 ext = Extension("wrap_libhello.hello",
                 sources=["wrap_libhello/hello.pyx"],
                 extra_link_args=ld_flags,
                 include_dirs=include_dirs)
 
-
-class CleanCommand(Command):
-    """Enable `python setup.py clean` to tidy up properly."""
-    user_options = []
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        os.system('rm -vrf build')
-        os.system('rm -vrf dist')
-        os.system('rm -vrf wrap_libhello/__pycache__')
-        os.system('rm -vrf wrap_libhello/hello.c')
-        os.system('rm -vrf wrap_libhello.egg-info')
-        os.system("find wrap_libhello libhello -name '*.o' -delete -print")
-        os.system("find wrap_libhello libhello -name '*.so' -delete -print")
-        os.system("find wrap_libhello libhello -name '*.pyc' -delete -print")
-
-
 setup(name="wrap_libhello",
       ext_modules=[ext],
-      cmdclass={'clean': CleanCommand})
+)
diff --git a/examples/cython/c_numpy/setup.py b/examples/cython/c_numpy/setup.py
index ff6c7e895b7b26f8ee9d1f7e457ef029659e9809..57009d0eab05a6058dd7fd92544ad1e3486e327d 100644
--- a/examples/cython/c_numpy/setup.py
+++ b/examples/cython/c_numpy/setup.py
@@ -1,6 +1,6 @@
 import os
 import numpy as np
-from setuptools import setup, Extension, Command
+from setuptools import setup, Extension
 
 
 # assuming gcc, set (aggressive) optimization flags, to be appended to the default compile line
@@ -25,27 +25,7 @@ ext = Extension("ctonumpy.cube",
                 extra_link_args=ld_flags,
                 include_dirs=[np.get_include()])
 
-class CleanCommand(Command):
-    """Enable `python setup.py clean` to tidy up properly."""
-    user_options = []
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        # better: use plain Python instead of UNIX commands
-        os.system('rm -vrf build')
-        os.system('rm -vrf dist')
-        os.system('rm -vrf ctonumpy/cube.c')
-        os.system('rm -vrf ctonumpy/__pycache__')
-        os.system('rm -vrf ctonumpy.egg-info')
-        os.system("find ctonumpy -name '*.so' -delete -print")
-        os.system("find ctonumpy -name '*.pyc' -delete -print")
-
 setup(name="ctonumpy",
       ext_modules=[ext],
-      cmdclass={'clean': CleanCommand})
+)
 
diff --git a/examples/pybind11/setup.py b/examples/pybind11/setup.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5ef0a12dcc7c3f782aef80548d090f19774d5e8f 100644
--- a/examples/pybind11/setup.py
+++ b/examples/pybind11/setup.py
@@ -0,0 +1,16 @@
+from setuptools import setup
+# run `pip install --user pybind11` if not yet installed
+from pybind11.setup_helpers import Pybind11Extension, build_ext
+
+ext_modules = [
+    Pybind11Extension(
+        "pybex",
+        ["src/pybex.cpp",],
+    ),
+]
+
+setup(
+    cmdclass={"build_ext": build_ext},
+    ext_modules=ext_modules
+)
+
diff --git a/examples/pybind11/src/cube.cpp b/examples/pybind11/src/cube.cpp
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/examples/pybind11/src/pybex.cpp b/examples/pybind11/src/pybex.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3700913d4ffa2fb975d3db274bfedaca5e2cdc89
--- /dev/null
+++ b/examples/pybind11/src/pybex.cpp
@@ -0,0 +1,32 @@
+// pybind11 example module 'pybex'
+
+#include <pybind11/pybind11.h>
+#include <pybind11/numpy.h>
+
+namespace py = pybind11;
+
+// C/C++ implementation of the function to be wrapped
+void c_cube(const double * v_in, double * v_out, size_t n_elem) {
+    for (size_t i=0; i<n_elem; ++i) {
+        v_out[i] = v_in[i] * v_in[i] * v_in[i];
+    }
+}
+
+// wrapper function, accepting a NumPy array as input and returning a NumPy array
+py::array_t<double> py_cube(py::array_t<double, py::array::c_style> v_in)
+{
+  // allocate buffer to be returned as a NumPy array
+  auto v_out = py::array_t<double>(v_in.size());
+  double * v_out_ptr = (double*) v_out.request().ptr;
+
+  c_cube(v_in.data(), v_out_ptr, v_in.size());
+
+  return v_out;
+}
+
+PYBIND11_MODULE(pybex, m) {
+    m.doc() = "pybind11 example module"; // module docstring
+
+    m.def("cube", &py_cube, "a function that cubes a double-precision numpy array");
+}
+
diff --git a/notebooks/2e--Interfacing_with_C_and_F.ipynb b/notebooks/2e--Interfacing_with_C_and_F.ipynb
index 09b51f5653463e12e6238b3ada9e89b99f8e2af1..39ac47ddd25a8e031024076a792082a74d1766f8 100644
--- a/notebooks/2e--Interfacing_with_C_and_F.ipynb
+++ b/notebooks/2e--Interfacing_with_C_and_F.ipynb
@@ -10,7 +10,7 @@
    "source": [
     "# Interfacing Python/NumPy with C/C++ and Fortran Code\n",
     "\n",
-    "**Python for HPC course**\n",
+    "**Python for HPC**\n",
     "\n",
     "2018-2021 Sebastian Ohlmann, Klaus Reuter\n",
     "\n",
@@ -29,14 +29,14 @@
     "\n",
     "* Call compiled C/C++ or Fortran code from the Python layer\n",
     "    * Existing (legacy) code\n",
-    "    * Newly developed and optimized code (hotspots, numerical kernels)\n",
+    "    * Newly developed and optimized code (numerical kernels)\n",
     "\n",
     "$\\rightarrow$ combine the convenience of using high-level Python with high-performance compiled code\n",
     "\n",
     "## Steps\n",
     "\n",
     "* create software interface between Python/NumPy and C/C++/Fortran\n",
-    "* compile and link to a Python module"
+    "* compile and link to become a Python-importable module"
    ]
   },
   {
@@ -49,6 +49,8 @@
    "source": [
     "## Interfacing Options\n",
     "\n",
+    "We're looking into the following options:\n",
+    "\n",
     "* Fortran $\\longrightarrow$ **f2py**\n",
     "* C/C++/CUDA $\\longrightarrow$ **Cython**\n",
     "* C++ $\\longrightarrow$ **pybind11**\n",
@@ -57,7 +59,7 @@
     "\n",
     "* Swig, an interface generator\n",
     "* Boost.Python for C++ codes\n",
-    "* Python's low-level C API (different for Python 2 and 3!)"
+    "* Python's low-level C API"
    ]
   },
   {
@@ -74,9 +76,9 @@
     "    * is part of NumPy\n",
     "    * scans the Fortran code and generates signature files (.pyf)\n",
     "    * automatically takes care of type casting and non-contiguous arrays\n",
-    "* 2 ways of usage\n",
-    "    * direct calling of `f2py`\n",
-    "    * extension in `setup.py` using `numpy.distutils`\n",
+    "* two ways of usage\n",
+    "    * direct calling of the `f2py` executable\n",
+    "    * define an extension in `setup.py` using `numpy.distutils`\n",
     "* see examples in './f2py' (cf. the [f2py user guide](https://sysbio.ioc.ee/projects/f2py2e/usersguide/index.html))"
    ]
   },
@@ -88,7 +90,7 @@
     }
    },
    "source": [
-    "### Compile and link Fortran code to Python module using `f2py`\n",
+    "### Compile and link Fortran code to a Python module using `f2py`\n",
     "\n",
     "* Given the Fortran file `fib.f90`\n",
     "\n",
@@ -108,7 +110,7 @@
     "end\n",
     "```\n",
     "\n",
-    "* Generate '.so' module file by calling `f2py -c fib.f90 -m fib` (second parameter is module name)"
+    "* Generate 'fib' Python module file by calling `f2py -c fib.f90 -m fib` (second parameter is module name)"
    ]
   },
   {
@@ -263,7 +265,7 @@
     }
    },
    "source": [
-    "## Interfacing with C from NumPy-Code using Cython\n",
+    "## Interfacing with C code from NumPy code using Cython\n",
     "\n",
     "* Goals\n",
     "  * Pass NumPy arrays down to C code to perform computation,  \n",
@@ -294,7 +296,7 @@
     "        * OpenMP threading (not affected by the Python GIL)\n",
     "        * vectorization\n",
     "        * cache optimization, etc.\n",
-    "    * tweak the compiler flags in `setup.py`"
+    "    * use optimizing compiler flags in `setup.py`"
    ]
   },
   {
@@ -305,9 +307,9 @@
     }
    },
    "source": [
-    "## Interfacing with a C library (shared object) using Cython\n",
+    "## Interfacing with a C library ('.so', shared object) using Cython\n",
     "* Goal: Use a C library from Python\n",
-    "    * third-party library for which no Python bindings exist, yet\n",
+    "    * third-party library for which no Python bindings exist\n",
     "    * legacy code you want to wrap into Python\n",
     "    * CUDA code (compile into `.so` file independently using `nvcc`, first)\n",
     "* see the comprehensive example in `cython/c_interface_shared_object`"
@@ -329,7 +331,7 @@
     "        * installation location passed as arguments to `setup.py`\n",
     "        * installed at sytem location\n",
     "    * deployment: add the RPATH to the link line pointing to library location, to avoid `LD_LIBRARY_PATH`\n",
-    "* for CUDA code, it is much easier to go via a library than using `setup.py` with `nvcc`"
+    "* hint: for CUDA code, it is often easier to go via a library than injecting `nvcc` into `setup.py`"
    ]
   },
   {
@@ -342,7 +344,7 @@
    "source": [
     "## Cython and C++\n",
     "\n",
-    "* Cython supports most of the C++ features: classes, templates, overloading\n",
+    "* Cython supports many object-oriented/advanced C++ features: classes, templates, overloading\n",
     "* typical use case: write a Python wrapper class for a C++ class\n",
     "* not covered here, please see for further details  \n",
     "  http://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html"
@@ -356,7 +358,7 @@
     }
    },
    "source": [
-    "### Cython provides interfaces to libc and c++ STL"
+    "### Cython provides interfaces to libc and C++ STL (!)"
    ]
   },
   {
@@ -430,15 +432,13 @@
     }
    },
    "source": [
-    "## pybind11"
+    "## pybind11\n",
+    "\n",
+    "* lightweight header-only library to create interfaces to modern C++ code\n",
+    "* highlights: STL, iterators, classes and inheritance, smart pointers, move semantics, NumPy $\\leftrightarrow$ Eigen, etc.\n",
+    "* cf. the simple NumPy example in `pybind11`\n",
+    "* documentation: https://pybind11.readthedocs.io/en/latest/"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {