{"id":68892,"date":"2022-11-24T09:00:43","date_gmt":"2022-11-24T09:00:43","guid":{"rendered":"https:\/\/www.cryptocabaret.com\/?p=68892"},"modified":"2022-11-24T09:00:43","modified_gmt":"2022-11-24T09:00:43","slug":"write-a-c-extension-module-for-python","status":"publish","type":"post","link":"https:\/\/www.cryptocabaret.com\/?p=68892","title":{"rendered":"Write a C++ extension module for Python"},"content":{"rendered":"<p><span class=\"field field--name-title field--type-string field--label-hidden\">Write a C++ extension module for Python<\/span><br \/>\n<span class=\"field field--name-uid field--type-entity-reference field--label-hidden\"><a title=\"View user profile.\" href=\"https:\/\/opensource.com\/users\/hansic99\" class=\"username\">Stephan Avenwedde<\/a><\/span><br \/>\n<span class=\"field field--name-created field--type-created field--label-hidden\">Thu, 11\/24\/2022 &#8211; 03:00<\/span><\/p>\n<div class=\"clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item\">\n<p>In a previous article, I gave an overview of <a href=\"https:\/\/opensource.com\/article\/22\/9\/python-interpreters-2022\">six Python interpreters<\/a>. On most systems, the CPython interpreter is the default, and also the poll in my last article showed that CPython is the most popular one. Specific to CPython is the ability to write Python modules in C using CPythons extensions API. Writing Python modules in C allows you to move computation-intensive code to C while preserving the ease of access of Python.<\/p>\n<p>In this article, I\u2019ll show you how to write an extension module. Instead of plain C, I use C++ because most compilers usually understand both. I have to mention one major drawback in advance: Python modules built this way are not portable to other interpreters. They only work in conjunction with the CPython interpreter. So if you are looking for a more portable way of interacting with C libraries, consider using the <a href=\"https:\/\/docs.python.org\/3\/library\/ctypes.html#module-ctypes\">ctypes<\/a> module.<\/p>\n<h2>Source code<\/h2>\n<p>As usual, you can find the related source code on <a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\">GitHub<\/a>. The C++ files in the repository have the following purpose:<\/p>\n<ul>\n<li><code>my_py_module.cpp<\/code>: Definition of the Python module <em>MyModule<\/em><\/li>\n<li><code>my_cpp_class.h<\/code>: A header-only C++ class which gets exposed to Python<\/li>\n<li><code>my_class_py_type.h\/cpp<\/code>: Python representation of our C++ class<\/li>\n<li><code>pydbg.cpp<\/code>: Separate application for debugging purpose<\/li>\n<\/ul>\n<p>The Python module you build in this article won\u2019t have any meaningful use, but is a good example.<\/p>\n<h2>Build the module<\/h2>\n<p>Before looking into the source code, you can check whether the module compiles on your system. <a href=\"https:\/\/opensource.com\/article\/21\/5\/cmake\">I use CMake<\/a> for creating the build configuration, so CMake must be installed on your system. In order to configure and build the module, you can either let Python run the process:<\/p>\n<pre>\n<code>$ python3 setup.py build<\/code><\/pre>\n<p>Or run the process manually:<\/p>\n<pre>\n<code>$ cmake -B build\n$ cmake --build build<\/code><\/pre>\n<p>After that, you have a file called <code>MyModule.so<\/code> in the <code>\/build<\/code> subdirectory.<\/p>\n<h2>Defining an extension module<\/h2>\n<p>First, take a look on <code>my_py_module.cpp<\/code>, in particular, the function <code>PyInit_MyModule<\/code>:<\/p>\n<pre>\n<code>PyMODINIT_FUNC\nPyInit_MyModule(void) {\n    PyObject* module = PyModule_Create(&amp;my_module);\n    \n    PyObject *myclass = PyType_FromSpec(&amp;spec_myclass);\n    if (myclass == NULL){\n        return NULL;\n    }\n    Py_INCREF(myclass);\n    \n    if(PyModule_AddObject(module, \"MyClass\", myclass) <\/code><\/pre>\n<p>This is the most important code in this example because it acts as the entry point for CPython. In general, when a Python C extension is compiled and made available as a shared object binary, CPython searches for the function <code>PyInit_<modulename><\/modulename><\/code> in the eponymous binary (<code><modulename>.so<\/modulename><\/code>) and executes it when attempting to import it.<\/p>\n<p>All Python types, whether declarations or instances, are exposed as pointers to <a href=\"https:\/\/docs.python.org\/release\/3.9.1\/c-api\/structures.html?highlight=pyobject#c.PyObject\">PyObject<\/a>. In the first part of this function, the root definition of the module is created by running <code>PyModule_Create(...)<\/code>. As you can see in the module specification (<code>my_module<\/code>, same file), it doesn\u2019t have any special functionality.<\/p>\n<p>Afterward, <a href=\"https:\/\/docs.python.org\/3\/c-api\/type.html#c.PyType_FromSpec\">PyType_FromSpec<\/a> is called to create a Python <a href=\"https:\/\/docs.python.org\/3\/c-api\/typeobj.html#heap-types\">heap type<\/a> definition for the custom type MyClass. A heap type corresponds to a Python class. The type definition is then assigned to the module MyModule.<\/p>\n<p><em>Note that if one of the functions fails, the reference count of previously created PyObjects must be decremented so that they get deleted by the interpreter.<\/em><\/p>\n<\/p>\n<div class=\"embedded-resource-list callout-float-right\">\n<div class=\"field field--name-title field--type-string field--label-hidden field__item\">More Python resources<\/div>\n<div class=\"field field--name-links field--type-link field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/topics\/middleware\/what-is-ide?intcmp=7016000000127cYAAQ\">What is an IDE?<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/downloads\/cheat-sheet-python-37-beginners?intcmp=7016000000127cYAAQ\">Cheat sheet: Python 3.7 for beginners<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/resources\/python\/gui-frameworks?intcmp=7016000000127cYAAQ\">Top Python GUI frameworks<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/downloads\/7-essential-pypi-libraries?intcmp=7016000000127cYAAQ\">Download: 7 essential PyPI libraries<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/developers.redhat.com\/?intcmp=7016000000127cYAAQ\">Red Hat Developers<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/python?intcmp=7016000000127cYAAQ\">Latest Python articles<\/a><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<h2>Specifying a Python type<\/h2>\n<p>The specification for the type MyClass is found inside <a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/my_class_py_type.h\">my_class_py_type.h<\/a> as an instance of <a href=\"https:\/\/docs.python.org\/3\/c-api\/type.html#c.PyType_Spec\">PyType_Spec<\/a>:<\/p>\n<pre>\n<code>static PyType_Spec spec_myclass = {\n    \"MyClass\",                                  \/\/ name\n    sizeof(MyClassObject) + sizeof(MyClass),    \/\/ basicsize\n    0,                                          \/\/ itemsize\n    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   \/\/ flags\n    MyClass_slots                               \/\/ slots\n};<\/code><\/pre>\n<p>This structure defines some basic type information for the class. The value passed for the size consists of the size of the Python representation (<code>MyClassObject<\/code>) and the size of the plain C++ class (<code>MyClass<\/code>). The <code>MyClassObject<\/code> is defined as follows:<\/p>\n<pre>\n<code>typedef struct {\n    PyObject_HEAD\n    int         m_value;\n    MyClass*    m_myclass;\n} MyClassObject;<\/code><\/pre>\n<p>The Python representation is basically of type <a href=\"https:\/\/docs.python.org\/release\/3.9.1\/c-api\/structures.html?highlight=pyobject#c.PyObject\">PyObject<\/a>, defined by the macro <code>PyObject_HEAD<\/code>, and some additional members. The member <code>m_value<\/code> is exposed as ordinary class member while the member <code>m_myclass<\/code> is only accessible from inside C++ code.<\/p>\n<p>The <a href=\"https:\/\/docs.python.org\/release\/3.9.1\/c-api\/type.html?highlight=pytype_slot#c.PyType_Slot\">PyType_Slot<\/a> defines some additional functionality:<\/p>\n<pre>\n<code>static PyType_Slot MyClass_slots[] = {\n    {Py_tp_new,     (void*)MyClass_new},\n    {Py_tp_init,    (void*)MyClass_init},\n    {Py_tp_dealloc, (void*)MyClass_Dealloc},\n    {Py_tp_members, MyClass_members},\n    {Py_tp_methods, MyClass_methods},\n    {0, 0} \/* Sentinel *\/\n};<\/code><\/pre>\n<p>Here, the jump addressed for some initialization and de-initialization functions are set as well as ordinary class methods and members. Additional functionality, like assigning an initial attribute dictionary, could also be set, but this is optional. Those definitions usually end with a sentinel, consisting of <code>NULL<\/code> values.<\/p>\n<p>To complete the type specification, here is the method and member table:<\/p>\n<pre>\n<code>static PyMethodDef MyClass_methods[] = {\n    {\"addOne\", (PyCFunction)MyClass_addOne, METH_NOARGS,  PyDoc_STR(\"Return an incrmented integer\")},\n    {NULL, NULL} \/* Sentinel *\/\n};\n\nstatic struct PyMemberDef MyClass_members[] = {\n    {\"value\", T_INT, offsetof(MyClassObject, m_value)},\n    {NULL} \/* Sentinel *\/\n};<\/code><\/pre>\n<p>In the method table, the Python method <code>addOne<\/code> is defined, and it points to the related function <code>MyClass_addOne<\/code>. This function acts as a wrapper. It invokes the <code>addOne()<\/code> method in the C++ class.<\/p>\n<p>In the member table, there is just one member defined for demonstration purposes. Unfortunately, the use of <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/types\/offsetof\">offsetof<\/a> in <a href=\"https:\/\/docs.python.org\/release\/3.9.1\/c-api\/structures.html?highlight=pymemberdef#c.PyMemberDef\">PyMemberDef<\/a> doesn\u2019t allow C++ specific types to be added to <code>MyClassObject<\/code>. If you try to place some C++ type container (such as <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/optional\">std::optional<\/a>), the compiler complains about it in the form of warnings related to memory layout.<\/p>\n<h2>Initialization and de-initialization<\/h2>\n<p>The method <code>MyClass_new<\/code> acts only to provide initial values for <code>MyClassObject<\/code> and allocates memory for the base type:<\/p>\n<pre>\n<code>PyObject *MyClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds){\n    std::cout tp_alloc(type, 0);\n    if(self != NULL){ \/\/ -&gt; allocation successfull\n        \/\/ assign initial values\n        self-&gt;m_value   = 0;\n        self-&gt;m_myclass = NULL; \n    }\n    return (PyObject*) self;\n}<\/code><\/pre>\n<p>Actual initialization takes place in <code>MyClass_init<\/code>, which corresponds to the <a href=\"https:\/\/docs.python.org\/3\/library\/dataclasses.html?highlight=__init__\">__init__()<\/a> method in Python:<\/p>\n<pre>\n<code>int MyClass_init(PyObject *self, PyObject *args, PyObject *kwds){\n    \n    ((MyClassObject *)self)-&gt;m_value = 123;\n    \n    MyClassObject* m = (MyClassObject*)self;\n    m-&gt;m_myclass = (MyClass*)PyObject_Malloc(sizeof(MyClass));\n\n    if(!m-&gt;m_myclass){\n        PyErr_SetString(PyExc_RuntimeError, \"Memory allocation failed\");\n        return -1;\n    }\n\n    try {\n        new (m-&gt;m_myclass) MyClass();\n    } catch (const std::exception&amp; ex) {\n        PyObject_Free(m-&gt;m_myclass);\n        m-&gt;m_myclass = NULL;\n        m-&gt;m_value   = 0;\n        PyErr_SetString(PyExc_RuntimeError, ex.what());\n        return -1;\n    } catch(...) {\n        PyObject_Free(m-&gt;m_myclass);\n        m-&gt;m_myclass = NULL;\n        m-&gt;m_value   = 0;\n        PyErr_SetString(PyExc_RuntimeError, \"Initialization failed\");\n        return -1;\n    }\n\n    return 0;\n}<\/code><\/pre>\n<p>If you want to have arguments passed during initialization, you must call <a href=\"https:\/\/docs.python.org\/3\/c-api\/arg.html#c.PyArg_ParseTuple\">PyArg_ParseTuple<\/a> at this point. For the sake of simplicity, all arguments passed during initialization are ignored in this example. In the first part of the function, the <code>PyObject<\/code> pointer (<code>self<\/code>) is reinterpreted to a pointer to <code>MyClassObject<\/code> in order to get access to our additional members. Additionally, the memory for the C++ class is allocated and its constructor is executed.<\/p>\n<p>Note that exception handling and memory allocation (and de-allocation) must be carefully done in order to prevent memory leaks. When the reference count drops to zero, the <code>MyClass_dealloc<\/code> function takes care of freeing all related heap memory. There\u2019s a <a href=\"https:\/\/docs.python.org\/3\/c-api\/memory.html\">dedicated chapter<\/a> in the documentation about memory management for C and C++ extensions.<\/p>\n<h2>Method wrapper<\/h2>\n<p>Calling a related C++ class method from the Python class is easy:<\/p>\n<pre>\n<code>PyObject* MyClass_addOne(PyObject *self, PyObject *args){\n    assert(self);\n\n    MyClassObject* _self = reinterpret_cast<myclassobject>(self);\n    unsigned long val = _self-&gt;m_myclass-&gt;addOne();\n    return PyLong_FromUnsignedLong(val);\n}<\/myclassobject><\/code><\/pre>\n<p>Again, the <code>PyObject*<\/code> argument (<code>self<\/code>) is casted to <code>MyClassObject*<\/code> in order to get access to <code>m_myclass<\/code>, a pointer to the C++ class instance. With this information, the classes method <code>addOne()<\/code> is called and the result is returned in form of a <a href=\"https:\/\/docs.python.org\/3\/c-api\/long.html\">Python integer object<\/a>.<\/p>\n<h2>3 ways to debug<\/h2>\n<p>For debugging purposes, it can be valuable to compile the CPython interpreter in debugging configuration. A detailed description can be found in the <a href=\"https:\/\/docs.python.org\/3\/c-api\/intro.html#debugging-builds\">official documentation<\/a>. It\u2019s possible to follow the next steps, as long as additional debug symbols for the pre-installed interpreter are downloaded.<\/p>\n<h3>GNU Debugger<\/h3>\n<p>Good old <a href=\"https:\/\/opensource.com\/article\/21\/3\/debug-code-gdb\" target=\"_blank\" rel=\"noopener\">GNU Debugger (GDB)<\/a> is, of course, also useful here. I include a <a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/gdbinit\">gdbinit<\/a> file, defining some options and breakpoints. There\u2019s also the\u00a0<a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/gdb.sh\">gdb.sh<\/a> script, which creates a debug build and initiates a GDB session:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/11\/gdb_session_b_0.png\" width=\"1144\" height=\"574\" alt=\"Gnu Debugger (GDB) is useful for your Python C and C++ extensions.\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(Stephan Avenwedde, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>GDB invokes the CPython interpreter with the script file <a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/main.py\">main.py<\/a>. The script file allows you to easily define all the actions you want to perform with the Python extension module.<\/p>\n<h3>C++ application<\/h3>\n<p>Another approach is to embed the CPython interpreter in a separate C++ application. In the repository, this can be found in the file <a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/pydbg.cpp\">pydbg.cpp<\/a>:<\/p>\n<pre>\n<code>int main(int argc, char *argv[], char *envp[])\n{\n    Py_SetProgramName(L\"DbgPythonCppExtension\");\n    Py_Initialize();\n\n    PyObject *pmodule = PyImport_ImportModule(\"MyModule\");\n    if (!pmodule) {\n        PyErr_Print();\n        std::cerr <\/code><\/pre>\n<p>Using the <a href=\"https:\/\/docs.python.org\/3\/extending\/embedding.html#very-high-level-embedding\">high level interface<\/a>, it\u2019s possible to include the extension module and perform actions on it. This allows you to debug in the native IDE environment. It also gives you finer control of the variables passed from and to the extension module.<\/p>\n<p>The drawback is the high expense of creating an extra application.<\/p>\n<h3>VSCode and VSCodium LLDB extension<\/h3>\n<p>Using a debugger extension like <a href=\"https:\/\/github.com\/vadimcn\/vscode-lldb\">CodeLLDB<\/a> is probably the most convenient debugging option. The repository includes the VSCode or VSCodium configuration files for building the extension (<a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/.vscode\/tasks.json\">task.json<\/a>, <a href=\"https:\/\/github.com\/microsoft\/vscode-cmake-tools\">CMake Tools<\/a>) and invoking the debugger (<a href=\"https:\/\/github.com\/hANSIc99\/PythonCppExtension\/blob\/main\/.vscode\/launch.json\">launch.json<\/a>). This approach combines the advantages of the previous ones: Debugging in an graphical IDE, defining actions in a Python script file or even dynamically in the interpreter prompt.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/11\/vscodium_debug_session.png\" width=\"1389\" height=\"908\" alt=\"VSCodium features an integrated debugger.\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(Stephan Avenwedde, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<h2>Extend C++ with Python<\/h2>\n<p>All functionality available from Python code is also available from within a C or C++ extension. While coding in Python is often considered as an easy win, extending Python in C or C++ can also be a pain. On the other hand, while native Python code is slower than C++, a C or C++ extension makes it possible to elevate a computation-intensive task to the speed of native machine code.<\/p>\n<p>You must also consider the usage of an ABI. The stable ABI provides a way to maintain backwards compatibility to older versions of CPython as described in <a href=\"https:\/\/docs.python.org\/3\/c-api\/stable.html\">the documentation<\/a>.<\/p>\n<p>In the end, you must weigh the advantages and disadvantages yourself. Should you decide to use C extensions to make certain functionality available to you in Python, you\u2019ve seen how it can be done.<\/p>\n<\/div>\n<div class=\"clearfix text-formatted field field--name-field-article-subhead field--type-text-long field--label-hidden field__item\">\n<p>Use C extensions to make certain functionality available to you in Python.<\/p>\n<\/div>\n<div class=\"field field--name-field-lead-image field--type-entity-reference field--label-hidden field__item\">\n<article class=\"media media--type-image media--view-mode-caption\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/11\/code_computer_laptop_hack_work.png\" width=\"1041\" height=\"584\" alt=\"Coding on a computer\" title=\"Coding on a computer\"><\/div>\n<\/article>\n<\/div>\n<div class=\"field field--name-field-tags field--type-entity-reference field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/python\" hreflang=\"en\">Python<\/a><\/div>\n<\/p><\/div>\n<div class=\"hidden field field--name-field-listicle-title field--type-string field--label-hidden field__item\">What to read next<\/div>\n<div class=\"field field--name-field-default-license field--type-list-string field--label-hidden field__item\"><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/\"><br \/>\n        <img decoding=\"async\" alt=\"Creative Commons License\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2022\/11\/cc-by-sa--37.png\" title=\"This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.\"><\/a>This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.<\/div>\n<section class=\"field field--name-field-comments field--type-comment field--label-hidden comment-wrapper\">\n<div class=\"comments__count\">\n<div class=\"login\"><a href=\"https:\/\/opensource.com\/user\/register?absolute=1\">Register<\/a> or <a href=\"https:\/\/opensource.com\/user\/login?destination=\/feed&amp;absolute=1\">Login<\/a> to post a comment.<\/div>\n<\/p><\/div>\n<\/section>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\" rel=\"noopener\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Write a C++ extension module for Python Stephan Avenwedde Thu, 11\/24\/2022 &#8211; 03:00 In a previous article, I gave an overview of six Python interpreters. On most systems, the CPython interpreter is the default, and also the poll in my last article showed that CPython is the most popular one. Specific to CPython is the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":68893,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[307],"tags":[],"class_list":["post-68892","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-open-source"],"_links":{"self":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/68892","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=68892"}],"version-history":[{"count":0,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/68892\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/media\/68893"}],"wp:attachment":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=68892"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=68892"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=68892"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}