Использование метакласса для класса, взятого из другого класса, реализованного в C - PullRequest
3 голосов
/ 24 марта 2019

Я работаю над ctypes дроп-заменой / расширением и столкнулся с проблемой, которую я не до конца понимаю.

Я пытаюсь создать фабрику классов для декораторов функций обратного вызова, подобных 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, что может быть существенным ограничением ... Я мог также ошибиться в реализации метакласса.Может кто-то просветить меня?

(Для дополнительного контекста я работаю над этой функцией .)

1 Ответ

1 голос
/ 25 марта 2019

Может быть, метаклассы ctypes просто не будут хорошо работать, будучи подклассами - поскольку он сам написан на C, он может обойти маршруты наследования для некоторых сочетаний клавиш и привести к сбоям.

В идеале это «плохое поведение» должно быть надлежащим образом документировано, заполнено ошибками в ctypes CPython и исправлено - насколько мне известно, не так много людей могут исправить ошибки ctypes.

С другой стороны, иметь метакласс только потому, что вы хотите подобный атрибуту свойства на уровне класса, излишне.

Python property сам по себе является просто готовым, очень полезным встроенным классом, который реализует протокол дескриптора .Любой класс, который вы создаете самостоятельно и который реализует надлежащие методы __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.

...