我正在研究ctypes
drop-in-replacement / extension并遇到了一个我不太了解的问题。
我正在尝试为类似于CFUNCTYPE和WINFUNCTYPE的回调函数装饰器构建一个类工厂。两家工厂都生产来自ctypes._CFuncPtr
的类。像每个ctypes
函数接口一样,它们具有argtypes
和restype
等属性。我想扩展类允许一个名为some_param
的附加属性,我想,为什么不呢,让我们用“getter”和“setter”方法尝试这个 - 它有多难......
因为我试图在类的属性(不是对象的属性)上使用“getter”和“setter”方法(@property
),所以我最终编写了一个元类。因为我的班级来自ctypes._CFuncPtr
,我认为我的元类必须来自ctypes._CFuncPtr.__class__
(我可能在这里错了)。
以下示例有效:
import ctypes
class a_class:
def b_function(self, some_param_parg):
class c_class_meta(ctypes._CFuncPtr.__class__):
def __init__(cls, *args):
super().__init__(*args) # no idea if this is good ...
cls._some_param_ = some_param_parg
@property
def some_param(cls):
return cls._some_param_
@some_param.setter
def some_param(cls, value):
if not isinstance(value, list):
raise TypeError('some_param must be a list')
cls._some_param_ = value
class c_class(ctypes._CFuncPtr, metaclass = c_class_meta):
_argtypes_ = ()
_restype_ = None
_flags_ = ctypes._FUNCFLAG_STDCALL # change for CFUNCTYPE or WINFUNCTYPE etc ...
return c_class
d_class = a_class().b_function([1, 2, 3])
print(d_class.some_param)
d_class.some_param = [2, 6]
print(d_class.some_param)
d_class.some_param = {} # Raises an error - as expected
到目前为止一直很好 - 使用上面的任何进一步不再适用。以下伪代码(如果在DLL或共享对象的实际函数中使用)将失败 - 事实上,它将导致CPython解释器发生段错误...
some_routine = ctypes.windll.LoadLibrary('some.dll').some_routine
func_type = d_class(ctypes.c_int16, ctypes.c_int16) # similar to CFUNCTYPE/WINFUNCTYPE
func_type.some_param = [4, 5, 6] # my "special" property
some_routine.argtypes = (ctypes.c_int16, func_type)
@func_type
def demo(x):
return x - 1
some_routine(4, demo) # segfaults HERE!
我不完全确定出了什么问题。 ctypes._CFuncPtr
在C中实现,这可能是一个相关的限制......我也可能在元类的实现中犯了一个错误。有人可以开导我吗?
(有关其他背景,我正在研究this function。)
也许ctypes元类只是不能很好地成为子类 - 因为它本身是用C语言编写的,它可能会绕过继承对某些快捷方式强加的路由并最终导致失败。
理想情况下,这种“不良行为”必须正确记录,填写为反对CPython的ctypes的错误并修复 - 据我所知,没有多少人可以修复ctypes错误。
另一方面,仅仅因为在类级别需要类似属性的属性而拥有元类就太过分了。
Python的property
本身就是预制的,非常有用的内置类,它实现了descriptor protocol。您自己创建的任何类都可以实现正确的__get__
和__set__
方法,可以替换“属性”(通常,当跨属性属性共享逻辑时,会导致更短,非重复的代码)
不幸的是,在第二个,描述符设置器只适用于实例,而不适用于类(这是有道理的,因为执行cls.attr
已经获得了特殊的代码保护值,并且无法在其上调用__set__
方法)
因此,如果您可以使用“手动”设置cls.__dict__
中的值并将逻辑放在__get__
属性中,则可以执行以下操作:
PREFIX = "_cls_prop_"
class ClsProperty:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
value = owner.__dict__.get(PREFIX + self.name)
# Logic to transform/check value goes here:
if not isinstance(value, list):
raise TypeError('some_param must be a list')
return value
def b_function(some_param_arg):
class c_class(ctypes._CFuncPtr):
_argtypes_ = ()
_restype_ = None
_flags_ = 0 # ctypes._FUNCFLAG_STDCALL # change for CFUNCTYPE or WINFUNCTYPE etc ...
_some_param_ = ClsProperty()
setattr(c_class, PREFIX + "_some_param_", some_param_arg)
return c_class
d_class = b_function([1, 2, 3])
print(d_class._some_param_)
d_class._some_param_ = [1, 2]
print(d_class._some_param_)
如果这不起作用,我认为尝试扩展CTypes元类的其他方法无论如何都不会起作用,但是如果你想尝试,而不是“元属性”,你可能会尝试自定义元类'__setitem__
,而不是进行参数检查,而不是使用property
。