Rocksolid Light

Welcome to RetroBBS

mail  files  register  newsreader  groups  login

Message-ID:  

A feature is nothing more than a bug with seniority. -- Unknown source


devel / comp.lang.python / Defining a Python enum in a C extension - am I doing this right?

SubjectAuthor
o Defining a Python enum in a C extension - am I doing this right?Bartosz Golaszewski

1
Defining a Python enum in a C extension - am I doing this right?

<mailman.242.1627028419.4164.python-list@python.org>

  copy mid

https://www.rocksolidbbs.com/devel/article-flat.php?id=18674&group=comp.lang.python#18674

  copy link   Newsgroups: comp.lang.python
Path: i2pn2.org!i2pn.org!news.swapon.de!fu-berlin.de!uni-berlin.de!not-for-mail
From: brgl@bgdev.pl (Bartosz Golaszewski)
Newsgroups: comp.lang.python
Subject: Defining a Python enum in a C extension - am I doing this right?
Date: Fri, 23 Jul 2021 10:20:06 +0200
Lines: 245
Message-ID: <mailman.242.1627028419.4164.python-list@python.org>
References: <CAMRc=MehhhAGOt=u3qpYzHRhgqpeStew30qiLRNoA5mScgf=PA@mail.gmail.com>
Mime-Version: 1.0
Content-Type: text/plain; charset="UTF-8"
X-Trace: news.uni-berlin.de JYmgZSHtIn2JgEaOQVAbUw+VL5rODhFCJRKwEtP9rv/g==
Return-Path: <brgl@bgdev.pl>
X-Original-To: python-list@python.org
Delivered-To: python-list@mail.python.org
Authentication-Results: mail.python.org; dkim=pass
reason="2048-bit key; unprotected key"
header.d=bgdev-pl.20150623.gappssmtp.com
header.i=@bgdev-pl.20150623.gappssmtp.com
header.b=MESxPmn7; dkim-adsp=unknown (unprotected policy);
dkim-atps=neutral
X-Spam-Status: OK 0.041
X-Spam-Evidence: '*H*': 0.92; '*S*': 0.00; 'project,': 0.03; 'this:':
0.03; 'mechanism': 0.07; 'const': 0.09; 'int': 0.09; 'module:':
0.09; 'pulled': 0.09; 'trivial': 0.09; 'subject:Python': 0.12;
'import': 0.14; '#include': 0.16; 'buildable': 0.16; 'but...':
0.16; 'char': 0.16; 'expose': 0.16; 'key,': 0.16; 'namely': 0.16;
'right.': 0.16; 'static': 0.16; 'subject:extension': 0.16;
'python': 0.16; 'basically': 0.23; 'object': 0.23; 'to:addr
:python-list': 0.23; 'extension': 0.24; 'feedback': 0.24; 'skip:p
30': 0.26; '(as': 0.27; 'task': 0.27; 'module': 0.28; "doesn't":
0.32; "i'm": 0.32; 'to:name:python': 0.32; 'message-
id:@mail.gmail.com': 0.33; 'using': 0.33; 'appreciated.': 0.33;
'class': 0.33; 'received:google.com': 0.34; 'bar': 0.35; 'trying':
0.36; 'received:209.85': 0.38; "it's": 0.38; 'received:209': 0.38;
'use': 0.38; 'example': 0.40; 'skip:e 10': 0.60; 'reference':
0.61; 'best': 0.61; 'likely': 0.61; 'lot': 0.62; 'increase': 0.62;
'skip:* 20': 0.63; 'subject:this': 0.63; 'key': 0.63; 'skip:m 20':
0.64; 'in.': 0.65; '[1]': 0.68; 'exactly': 0.69; 'skip:* 10':
0.73; 'name,': 0.75; 'skip:f 20': 0.76; 'reference.': 0.81; '-1;':
0.84; 'bartosz': 0.84; 'inheritance': 0.84; 'null;': 0.84;
'stolen': 0.84; 'url-ip:136/8': 0.84; 'val': 0.84; 'val);': 0.84;
'turned': 0.95
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=bgdev-pl.20150623.gappssmtp.com; s=20150623;
h=mime-version:from:date:message-id:subject:to;
bh=4UR1GSkb3jaGMYHK+gsbPSf8mXdN3crL1K3pUGo6wxI=;
b=MESxPmn7Q+MHjaWeV4NN1/Uxcj0Ntc7W6NYBvQ/F5n/drEJz0SxAKCFoFm5q7t4zm4
SOIBb/M0JcYsrRVZ/5x0f58GZkEVSeSOskyhOYNA75dxz9ffqfXniBW30BeDe7Sv9V0W
zANWHrVHzAyAzSWNsiPl2LP9dqFfPZb1m6Txx/vJNn56RFvXklsTYgqoTekG6uYFLZNz
rHKfXjUJ7ZKjX6qrmy22Yxt+shmsI2HwCcyBOQVClEZCfsL+EpTPGgSy33x/H6CsONXu
krCqHzyW0AeQgZJ/6PVn1tqmp6AybzsKDp7OqiN3rYi2HIncCrjBKeqpRZv+1f7C8DJg
4iMw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20161025;
h=x-gm-message-state:mime-version:from:date:message-id:subject:to;
bh=4UR1GSkb3jaGMYHK+gsbPSf8mXdN3crL1K3pUGo6wxI=;
b=Pweqm5pZV50OjDv4x+/upuott7+kDdbKLDA/R7R0RyYh4oOKIHdksIZGfgWHEbL52j
sfxbWYnl41a5pHtOXjeRlyZCEC1KZZfVpnzDZzsOl5AmVgphO8HbjqiL2AexZWf9V3q5
OAV6yIqs0h9onGHl5W3WVM8qfvHMDXbVy4oJWjl7RpQ6w7isbIkMazbD5hLkrS1CBFlW
f3/paCQlEmaTiqetjyoB9uVH6y7ipLr6hpkjS9JSQl8Gw2hbBWUAZixuipMVBqr4C53T
p1dp4MUHmmZJVc2JEA3hZI970Kxlogvo2NOs6XmUF9uD97GDF/1HC5dtvFieMfbKhka8
k/SA==
X-Gm-Message-State: AOAM530hNHZMSc5zi6it7ePTXXjRw1xKJeODelTwBel9QmwHbL1PGH68
1NBMqHgrHxjgp/IjEbNxYInY1nX+7p8vvCmZJ5KRL/FZcyz/Lw==
X-Google-Smtp-Source: ABdhPJwfaZqWiZuerFzOxjQbxuGfbRzzSAkPDJZh4nEja+S6ay3KUYIHxtFZ7wGblDDHRkYROQE3eLqP7xEMPtgpxdw=
X-Received: by 2002:a17:907:11d0:: with SMTP id
va16mr3784417ejb.87.1627028416986;
Fri, 23 Jul 2021 01:20:16 -0700 (PDT)
X-BeenThere: python-list@python.org
X-Mailman-Version: 2.1.34
Precedence: list
List-Id: General discussion list for the Python programming language
<python-list.python.org>
List-Unsubscribe: <https://mail.python.org/mailman/options/python-list>,
<mailto:python-list-request@python.org?subject=unsubscribe>
List-Archive: <https://mail.python.org/pipermail/python-list/>
List-Post: <mailto:python-list@python.org>
List-Help: <mailto:python-list-request@python.org?subject=help>
List-Subscribe: <https://mail.python.org/mailman/listinfo/python-list>,
<mailto:python-list-request@python.org?subject=subscribe>
X-Mailman-Original-Message-ID: <CAMRc=MehhhAGOt=u3qpYzHRhgqpeStew30qiLRNoA5mScgf=PA@mail.gmail.com>
 by: Bartosz Golaszewski - Fri, 23 Jul 2021 08:20 UTC

Hi!

I'm working on a Python C extension and I would like to expose a
custom enum (as in: a class inheriting from enum.Enum) that would be
entirely defined in C.

It turned out to not be a trivial task and the regular mechanism for
inheritance using .tp_base doesn't work - most likely due to the
Enum's meta class not being pulled in.

Basically I'm trying to do this:

import enum

class FooBar(enum.Enum):
FOO = 1
BAR = 2

in C.

After a lot of digging into cpython's internals, this is what I came
up with, wrapped in an example buildable module:

#include <Python.h>

PyDoc_STRVAR(module_doc,
"C extension module defining a class inheriting from enum.Enum.");

static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "pycenum",
.m_doc = module_doc,
.m_size = -1,
};

struct enum_descr {
const char *name;
long value;
};

static const struct enum_descr foobar_descr[] = {
{
.name = "FOO",
.value = 1,
},
{
.name = "BAR",
.value = 2,
},
{ }
};

static PyObject *make_bases(PyObject *enum_mod)
{ PyObject *enum_type, *bases;

enum_type = PyObject_GetAttrString(enum_mod, "Enum");
if (!enum_type)
return NULL;

bases = PyTuple_Pack(1, enum_type); /* Steals reference. */
if (!bases)
Py_DECREF(enum_type);

return bases;
}

static PyObject *make_classdict(PyObject *enum_mod, PyObject *bases)
{ PyObject *enum_meta_type, *classdict;

enum_meta_type = PyObject_GetAttrString(enum_mod, "EnumMeta");
if (!enum_meta_type)
return NULL;

classdict = PyObject_CallMethod(enum_meta_type, "__prepare__",
"sO", "FooBarEnum", bases);
Py_DECREF(enum_meta_type);
return classdict;
}

static int fill_classdict(PyObject *classdict, PyObject *modname,
const struct enum_descr *descr)
{ const struct enum_descr *entry;
PyObject *key, *val;
int ret;

key = PyUnicode_FromString("__module__");
if (!key)
return -1;

ret = PyObject_SetItem(classdict, key, modname);
Py_DECREF(key);
if (ret < 0)
return -1;

for (entry = descr; entry->name; entry++) {
key = PyUnicode_FromString(entry->name);
if (!key)
return -1;

val = PyLong_FromLong(entry->value);
if (!val) {
Py_DECREF(key);
return -1;
}

ret = PyObject_SetItem(classdict, key, val);
Py_DECREF(key);
Py_DECREF(val);
if (ret < 0)
return -1;
}

return 0;
}

static PyObject *make_new_type(PyObject *classdict, PyObject *bases,
const char *enum_name)
{ PyObject *name, *args, *new_type;
int ret;

name = PyUnicode_FromString(enum_name);
if (!name)
return NULL;

args = PyTuple_Pack(3, name, bases, classdict);
if (!args) {
Py_DECREF(name);
return NULL;
}

Py_INCREF(bases);
Py_INCREF(classdict);
/*
* Reference to name was stolen by PyTuple_Pack(), no need to
* increase it here.
*/

new_type = PyObject_CallObject((PyObject *)&PyType_Type, args);
Py_DECREF(args);
if (!new_type)
return NULL;

ret = PyType_Ready((PyTypeObject *)new_type);
if (ret < 0) {
Py_DECREF(new_type);
return NULL;
}

return new_type;
}

static PyObject *make_enum_type(PyObject *modname, const char *enum_name,
const struct enum_descr *descr)
{ PyObject *enum_mod, *bases, *classdict, *new_type;
int ret;

enum_mod = PyImport_ImportModule("enum");
if (!enum_mod)
return NULL;

bases = make_bases(enum_mod);
if (!bases) {
Py_DECREF(enum_mod);
return NULL;
}

classdict = make_classdict(enum_mod, bases);
if (!classdict) {
Py_DECREF(bases);
Py_DECREF(enum_mod);
return NULL;
}

ret = fill_classdict(classdict, modname, descr);
if (ret < 0) {
Py_DECREF(bases);
Py_DECREF(enum_mod);
Py_DECREF(classdict);
return NULL;
}

new_type = make_new_type(classdict, bases, enum_name);
Py_DECREF(bases);
Py_DECREF(enum_mod);
Py_DECREF(classdict);
return new_type;
}

PyMODINIT_FUNC PyInit_pycenum(void)
{ PyObject *module, *modname, *sub_enum_type;
int ret;

module = PyModule_Create(&module_def);
if (!module)
return NULL;

ret = PyModule_AddStringConstant(module, "__version__", "0.0.1");
if (ret < 0) {
Py_DECREF(module);
return NULL;
}

modname = PyModule_GetNameObject(module);
if (!modname) {
Py_DECREF(module);
return NULL;
}

sub_enum_type = make_enum_type(modname, "FooBar", foobar_descr);
Py_DECREF(modname);
if (!sub_enum_type) {
Py_DECREF(module);
return NULL;
}

ret = PyModule_AddObject(module, "FooBar", sub_enum_type);
if (ret < 0) {
Py_DECREF(sub_enum_type);
Py_DECREF(module);
return NULL;
}

return module;
}

Basically I'm calling the EnumMeta's __prepare__ method directly to
create a correct classdict and then I call the PyType_Type object too
to create the sub-type.

This works and AFAICT results in a class that behaves exactly as
expected, but... am I doing this right? Any feedback is appreciated.

I want to use this in a real project, namely the v2 of libgpiod python
bindings[1] so it's important to me to get this right.

Best regards,
Bartosz Golaszewski

[1] https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/


devel / comp.lang.python / Defining a Python enum in a C extension - am I doing this right?

1
server_pubkey.txt

rocksolid light 0.9.81
clearnet tor