This is a quick and dirty way to interface C++ code with Python, translating one or more C++ classes in Python objects.

First, we need some c++ sample code:

//myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H

#include <string>

using namespace std;

namespace pets {
    class Dog {
    public:
        Dog(string name, int age);
        virtual ~Dog();
        string talk();
    protected:
        string m_name;
        int m_age;
    };
}
//myclass.cpp
#include "myclass.h"

#include <string>

namespace pets {

    Dog::Dog(std::string name, int age): 
     m_name(name),m_age(age) { }

    Dog::~Dog() { }

    std::string Dog::talk() {
        return "BARK! I am a DOG and my name is "+m_name;
    }
}

now, we can try a little test program just to exercise our class:

#include <iostream>

#include "myclass.h"

using namespace std;

int main()
{
	pets::Dog dog("Charlie",3);
	
	cout << dog.talk() << endl;
}

compile and run:

g++ myprog.cpp myclass.cpp -o myprog  ; ./myprog

To use the Dog class from Python, we can create a wrapper using Cython.

Cython is a programming language that makes writing C extensions for the Python language as easy as Python itself. It aims to become a superset of the Python language which gives it high-level, object-oriented, functional, and dynamic programming. Its main feature on top of these is support for optional static type declarations as part of the language. The source code gets translated into optimized C/C++ code and compiled as Python extension modules. This allows for both very fast program execution and tight integration with external C libraries, while keeping up the high programmer productivity for which the Python language is well known.

So this is the “C++ to python” (Cython) wrapper glue code:

#pets.pyx
from libcpp.string cimport string

cdef extern from "myclass.h" namespace "pets":
  cppclass Dog:
    Dog(string, int)
    string talk()

cdef class PyDog:
  cdef Dog* c_dog #Cython class holds a c++ "Dog" instance
  def __cinit__(self, string name, int age):
    pyname=<bytes>name
    self.c_dog=new Dog(pyname,age)
  def __dealloc__(self):
    del self.c_dog
  def talk(self):
    return self.c_dog.talk()

we can also write a setup script in oreder to provide an easy and smooth compilation/install process:

#setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

extensions = [
    Extension("pets", ["pets.pyx","myclass.cpp"], language="c++"),
]

setup(
  name = 'test_pets',
  ext_modules = cythonize(extensions)
)

using this, to compile all the code in a single shared library, the user can run:

python3 setup.py build_ext --inplace

After this step, we have under current directory a new file with a .so extension, and we can finally use this shared library as a Python module:

import pets

dog1=pets.PyDog(b"Max",5)

print(dog1.talk())

notice that under the hood a lot of “automagic” type conversions does happen; Cython for example is able to translate between standard C++ enumerable classes and Python tuples. More information of course is available in the official documentation