python 可以从 C 头文件加载定义吗?
Can python load definitions from a C header file?
我正在写一个 python 围绕 C API 的包装器。我有一个广泛的 API 描述,现在我正在努力实现头文件中定义的枚举。
假设我在 myAPI.dll
中有一个 C API 函数,它接受一个枚举作为参数,例如:
void SomeFunction(SomeEnum data)
从头文件中,我可以看到SomeEnum
看起来像:
enum SomeEnum{
SomeValue = 1,
SomeOtherValue = 2,
SomeVeryStupidValue = -1
};
在 python 中,我加载 .dll
如下:
myAPI = ctypes.cdll.LoadLibrary('myAPI.dll')
现在我希望能够打电话给:
myAPI.SomeFunction(SomeValue)
我知道,我可以在 python 中定义 SomeValue
,但直接从头文件加载它的定义或直接将其作为 [=19= 的属性会很方便].这可能吗?
有可能。几年前我写了一个工具来使用 pyparsing. It's now a pyparsing example 扫描文件的 C++ enum
语法,我在此处重现以防 link 发生变化。如您所见,该文件甚至不必是完全有效的 C++。它定义 enum
语法并扫描文件以查找与语法匹配的文本,生成 Python 个变量。
#
# cpp_enum_parser.py
#
# Posted by Mark Tolonen on comp.lang.python in August, 2009,
# Used with permission.
#
# Parser that scans through C or C++ code for enum definitions, and
# generates corresponding Python constant definitions.
#
#
from pyparsing import *
# sample string with enums and other stuff
sample = """
stuff before
enum hello {
Zero,
One,
Two,
Three,
Five=5,
Six,
Ten=10
};
in the middle
enum blah
{
alpha,
beta,
gamma = 10 ,
zeta = 50
};
at the end
"""
# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
# find instances of enums ignoring other syntax
for item, start, stop in enum.scanString(sample):
id = 0
for entry in item.names:
if entry.value != "":
id = int(entry.value)
print("%s_%s = %d" % (item.enum.upper(), entry.name.upper(), id))
id += 1
输出:
HELLO_ZERO = 0
HELLO_ONE = 1
HELLO_TWO = 2
HELLO_THREE = 3
HELLO_FIVE = 5
HELLO_SIX = 6
HELLO_TEN = 10
BLAH_ALPHA = 0
BLAH_BETA = 1
BLAH_GAMMA = 10
BLAH_ZETA = 50
调整 Mark Tolonen 的代码以创建实际的 Python 枚举:
# import the dependencies
from enum import EnumMeta, IntEnum
from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore
from pyparsing import alphas, alphanums, nums
第一步是创建一个新的EnumMeta
类型(注意这是一次:
CPPEnum = None
class CPPEnumType(EnumMeta):
#
@classmethod
def __prepare__(metacls, clsname, bases, **kwds):
# return a standard dictionary for the initial processing
return {}
#
def __init__(clsname, *args , **kwds):
super(CPPEnumType, clsname).__init__(*args)
#
def __new__(metacls, clsname, bases, clsdict, **kwds):
if CPPEnum is None:
# first time through, ignore the rest
enum_dict = super(CPPEnumType, metacls).__prepare__(
clsname, bases, **kwds
)
enum_dict.update(clsdict)
return super(CPPEnumType, metacls).__new__(
metacls, clsname, bases, enum_dict, **kwds,
)
members = []
#
# remove _file and _name using `pop()` as they will
# cause problems in EnumMeta
try:
file = clsdict.pop('_file')
except KeyError:
raise TypeError('_file not specified')
cpp_enum_name = clsdict.pop('_name', clsname.lower())
with open(file) as fh:
file_contents = fh.read()
#
# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
#
# find the cpp_enum_name ignoring other syntax and other enums
for item, start, stop in enum.scanString(file_contents):
if item.enum != cpp_enum_name:
continue
id = 0
for entry in item.names:
if entry.value != "":
id = int(entry.value)
members.append((entry.name.upper(), id))
id += 1
#
# get the real EnumDict
enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds)
# transfer the original dict content, names starting with '_' first
items = list(clsdict.items())
items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p))
for name, value in items:
enum_dict[name] = value
# add the members
for name, value in members:
enum_dict[name] = value
return super(CPPEnumType, metacls).__new__(
metacls, clsname, bases, enum_dict, **kwds,
)
创建新类型后,我们可以创建新的基class:
class CPPEnum(IntEnum, metaclass=CPPEnumType):
pass
一旦你有了新的 CPPEnum
基础 class,使用它就很简单:
class Hello(CPPEnum):
_file = 'some_header.h'
class Blah(CPPEnum):
_file = 'some_header.h'
_name = 'blah' # in case the name in the file is not the lower-cased
# version of the Enum class name (so not needed in
# in this case)
并在使用中:
>>> list(Hello)
[
<Hello.ZERO: 0>, <Hello.ONE: 1>, <Hello.TWO: 2>, <Hello.THREE: 3>,
<Hello.FIVE: 5>, <Hello.SIX: 6>, <Hello.TEN: 10>,
]
披露:我是 Python stdlib Enum
, the enum34
backport, and the Advanced Enumeration (aenum
) 库的作者。
我正在写一个 python 围绕 C API 的包装器。我有一个广泛的 API 描述,现在我正在努力实现头文件中定义的枚举。
假设我在 myAPI.dll
中有一个 C API 函数,它接受一个枚举作为参数,例如:
void SomeFunction(SomeEnum data)
从头文件中,我可以看到SomeEnum
看起来像:
enum SomeEnum{
SomeValue = 1,
SomeOtherValue = 2,
SomeVeryStupidValue = -1
};
在 python 中,我加载 .dll
如下:
myAPI = ctypes.cdll.LoadLibrary('myAPI.dll')
现在我希望能够打电话给:
myAPI.SomeFunction(SomeValue)
我知道,我可以在 python 中定义 SomeValue
,但直接从头文件加载它的定义或直接将其作为 [=19= 的属性会很方便].这可能吗?
有可能。几年前我写了一个工具来使用 pyparsing. It's now a pyparsing example 扫描文件的 C++ enum
语法,我在此处重现以防 link 发生变化。如您所见,该文件甚至不必是完全有效的 C++。它定义 enum
语法并扫描文件以查找与语法匹配的文本,生成 Python 个变量。
#
# cpp_enum_parser.py
#
# Posted by Mark Tolonen on comp.lang.python in August, 2009,
# Used with permission.
#
# Parser that scans through C or C++ code for enum definitions, and
# generates corresponding Python constant definitions.
#
#
from pyparsing import *
# sample string with enums and other stuff
sample = """
stuff before
enum hello {
Zero,
One,
Two,
Three,
Five=5,
Six,
Ten=10
};
in the middle
enum blah
{
alpha,
beta,
gamma = 10 ,
zeta = 50
};
at the end
"""
# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
# find instances of enums ignoring other syntax
for item, start, stop in enum.scanString(sample):
id = 0
for entry in item.names:
if entry.value != "":
id = int(entry.value)
print("%s_%s = %d" % (item.enum.upper(), entry.name.upper(), id))
id += 1
输出:
HELLO_ZERO = 0
HELLO_ONE = 1
HELLO_TWO = 2
HELLO_THREE = 3
HELLO_FIVE = 5
HELLO_SIX = 6
HELLO_TEN = 10
BLAH_ALPHA = 0
BLAH_BETA = 1
BLAH_GAMMA = 10
BLAH_ZETA = 50
调整 Mark Tolonen 的代码以创建实际的 Python 枚举:
# import the dependencies
from enum import EnumMeta, IntEnum
from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore
from pyparsing import alphas, alphanums, nums
第一步是创建一个新的EnumMeta
类型(注意这是一次
CPPEnum = None
class CPPEnumType(EnumMeta):
#
@classmethod
def __prepare__(metacls, clsname, bases, **kwds):
# return a standard dictionary for the initial processing
return {}
#
def __init__(clsname, *args , **kwds):
super(CPPEnumType, clsname).__init__(*args)
#
def __new__(metacls, clsname, bases, clsdict, **kwds):
if CPPEnum is None:
# first time through, ignore the rest
enum_dict = super(CPPEnumType, metacls).__prepare__(
clsname, bases, **kwds
)
enum_dict.update(clsdict)
return super(CPPEnumType, metacls).__new__(
metacls, clsname, bases, enum_dict, **kwds,
)
members = []
#
# remove _file and _name using `pop()` as they will
# cause problems in EnumMeta
try:
file = clsdict.pop('_file')
except KeyError:
raise TypeError('_file not specified')
cpp_enum_name = clsdict.pop('_name', clsname.lower())
with open(file) as fh:
file_contents = fh.read()
#
# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
#
# find the cpp_enum_name ignoring other syntax and other enums
for item, start, stop in enum.scanString(file_contents):
if item.enum != cpp_enum_name:
continue
id = 0
for entry in item.names:
if entry.value != "":
id = int(entry.value)
members.append((entry.name.upper(), id))
id += 1
#
# get the real EnumDict
enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds)
# transfer the original dict content, names starting with '_' first
items = list(clsdict.items())
items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p))
for name, value in items:
enum_dict[name] = value
# add the members
for name, value in members:
enum_dict[name] = value
return super(CPPEnumType, metacls).__new__(
metacls, clsname, bases, enum_dict, **kwds,
)
创建新类型后,我们可以创建新的基class:
class CPPEnum(IntEnum, metaclass=CPPEnumType):
pass
一旦你有了新的 CPPEnum
基础 class,使用它就很简单:
class Hello(CPPEnum):
_file = 'some_header.h'
class Blah(CPPEnum):
_file = 'some_header.h'
_name = 'blah' # in case the name in the file is not the lower-cased
# version of the Enum class name (so not needed in
# in this case)
并在使用中:
>>> list(Hello)
[
<Hello.ZERO: 0>, <Hello.ONE: 1>, <Hello.TWO: 2>, <Hello.THREE: 3>,
<Hello.FIVE: 5>, <Hello.SIX: 6>, <Hello.TEN: 10>,
]
披露:我是 Python stdlib Enum
, the enum34
backport, and the Advanced Enumeration (aenum
) 库的作者。