【问题标题】:Is there any special method on python class definition or metaclass about "After class is defined"关于“定义类后”的python类定义或元类是否有任何特殊方法
【发布时间】:2022-11-19 11:34:13
【问题描述】:

我正在努力实现一个带有类型提示的 ctypes.Structure 类,这是我的代码:

import ctypes

_CData = ctypes.c_int.__mro__[2]


def set_fields_from_annotations(cls):
    from typing import get_type_hints
    if annotations := getattr(cls, '__annotations__', {}):
        cls._fields_ = [(n, t) for n, t in get_type_hints(type('', (), {
            '__annotations__': annotations,
            '__module__': cls.__module__
        })).items() if not hasattr(cls, n) and issubclass(t, _CData)]


class Node(ctypes.Structure):
    value: ctypes.c_uint
    key: ctypes.c_uint
    parent: 'ctypes.POINTER(Node)'
    child: 'ctypes.POINTER(Node)'


set_fields_from_annotations(Node)

print(Node.parent)

在使用中,必须在定义每个子类之后立即调用set_fields_from_annotations,是否有任何方法可以挂钩“定义子类之后”?

我试过的:
我尝试使用__init_subclass__,然后出现错误NameError: name 'Node' is not defined.
当我删除 'ctypes.POINTER(Node)' 类型的属性时,它会引发 SystemError

class StructureByAnnotations(ctypes.Structure):
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        set_fields_from_annotations(cls)


class Node(StructureByAnnotations):
    value: ctypes.c_uint
    key: ctypes.c_uint
    # parent: 'ctypes.POINTER(Node)'
    # child: 'ctypes.POINTER(Node)'
Traceback (most recent call last):
  File "D:\Projects\pythonProject\main.py", line 21, in <module>
    class Node(StructureByAnnotations):
  File "D:\Projects\pythonProject\main.py", line 17, in __init_subclass__
    set_fields_from_annotations(cls)
  File "D:\Projects\pythonProject\main.py", line 9, in set_fields_from_annotations
    cls._fields_ = [(n, t) for n, t in get_type_hints(type('', (), {
SystemError: error return without exception set

2022-10-11 编辑:我的解决方案,感谢@SUTerliakov

import ctypes
import sys

_CData = ctypes.c_int.__mro__[2]


def set_fields_from_annotations(cls):
    from typing import get_type_hints
    global_namespace = getattr(sys.modules.get(cls.__module__, None), '__dict__', {})
    global_namespace[cls.__name__] = cls
    if annotations := getattr(cls, '__annotations__', {}):
        # It's what you were already doing
        cls._fields_ = [(n, t) for n, t in get_type_hints(type(cls.__name__, (), {
            '__annotations__': annotations,
            '__module__': cls.__module__
        }), global_namespace).items() if not hasattr(cls, n) and issubclass(t, _CData)]
    return cls


@set_fields_from_annotations
class Node(ctypes.Structure):
    value: ctypes.c_uint
    key: ctypes.c_uint
    parent: 'ctypes.POINTER(Node)'
    child: 'ctypes.POINTER(Node)'


print(Node.child)

【问题讨论】:

  • 您最好将您的解决方案作为答案发布 - 自我回答问题很好:)

标签: python python-3.x python-typing


【解决方案1】:

它最终比我最初想象的更有趣......我可以建议以下装饰器解决方案。

import ctypes

_CData = ctypes.c_int.__mro__[2]


def set_fields_from_annotations(cls):
    from typing import get_type_hints
    globals().update({cls.__name__: cls})  # Define the name
    if annotations := getattr(cls, '__annotations__', {}):
        # It's what you were already doing
        cls._fields_ = [(n, t) for n, t in get_type_hints(type(cls.__name__, (), {
            '__annotations__': annotations,
            '__module__': cls.__module__
        })).items() if not hasattr(cls, n) and issubclass(t, _CData)]


def c_dataclass(cls):
    # Create a structure with proper metaclass
    new = type(ctypes.Structure)(cls.__name__, (cls, ctypes.Structure), {
        '__annotations__': getattr(cls, '__annotations__', {}),
        '__module__': cls.__module__,
    })
    set_fields_from_annotations(new)
    return new

# And now no need to declare Structure as base
@c_dataclass
class Node:
    value: ctypes.c_uint
    key: ctypes.c_uint
    parent: 'ctypes.POINTER(Node)'
    child: 'ctypes.POINTER(Node)'


print(Node._fields_)
print(dir(Node))
print(Node.value)
print(Node.parent)

这是我的输出 (3.10.8):

[('value', <class 'ctypes.c_uint'>), ('key', <class 'ctypes.c_uint'>), ('parent', <class 'LP_Node'>), ('child', <class 'LP_Node'>)]
['__annotations__', '__class__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_b_base_', '_b_needsfree_', '_fields_', '_objects', 'child', 'key', 'parent', 'value']
<Field type=c_uint, ofs=0, size=4>
<Field type=LP_Node, ofs=8, size=8>

【讨论】:

  • 好答案!但仍然存在一些问题,例如您应该在 cls.__module__ 的命名空间中定义名称,否则当您导入装饰器时它不会工作。覆盖元类也是一个好主意,但它破坏了 ctypes.POINTER(Node) 的 IDE(我使用 pycharm)类型检查,因为 ctypes.POINTER 的签名是 POINTER(type: Type[_CT]) 并且它无法发现你覆盖了元类,我将在下面发布我改进的解决方案。
猜你喜欢
  • 2019-04-06
  • 2017-07-15
  • 2010-09-13
  • 2014-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多