在从C中实现的另一个类派生的类上使用元类

问题描述 投票:2回答:1

我正在研究ctypes drop-in-replacement / extension并遇到了一个我不太了解的问题。

我正在尝试为类似于CFUNCTYPEWINFUNCTYPE的回调函数装饰器构建一个类工厂。两家工厂都生产来自ctypes._CFuncPtr的类。像每个ctypes函数接口一样,它们具有argtypesrestype等属性。我想扩展类允许一个名为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。)

python python-3.x ctypes metaclass
1个回答
1
投票

也许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

© www.soinside.com 2019 - 2024. All rights reserved.