-
-
Notifications
You must be signed in to change notification settings - Fork 35k
gh-104533: Add a dataclass-like decorator for ctypes structures
#153781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f0db59d
b73c912
94eb25a
bba7682
91eb76b
103a740
b14dd51
864b811
c0afd38
2e469b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3161,6 +3161,72 @@ fields, or any other data types containing pointer type fields. | |
| that should be merged into a containing structure or union. | ||
|
|
||
|
|
||
| .. decorator:: struct(*, align=None, layout, endian='native', pack=None) | ||
| :module: ctypes.util | ||
|
|
||
| A :term:`decorator` that allows generating structure types using an | ||
| annotation-based syntax, similar to the :mod:`dataclasses` module. | ||
|
|
||
| For example: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from ctypes.util import struct | ||
| from ctypes import c_int | ||
|
|
||
| @struct | ||
| class Point: | ||
| x: c_int | ||
| y: c_int | ||
|
|
||
| point = Point(1, 2) | ||
|
|
||
| *align*, *layout*, and *pack* supply the value for the :attr:`~ctypes.Structure._align_`, | ||
| :attr:`~ctypes.Structure._layout_`, and :attr:`~ctypes.Structure._pack_` | ||
| attributes, respectively. | ||
|
|
||
| *endian* controls which structure class will be used as the base. | ||
|
|
||
| - If *endian* is ``'native'``, :class:`~ctypes.Structure` will be used. | ||
| - If *endian* is ``'big'``, :class:`~ctypes.BigEndianStructure` will be used. | ||
| - If *endian* is ``'little'``, :class:`~ctypes.LittleEndianStructure` will be used. | ||
|
|
||
| Any other value will raise a :class:`ValueError`. | ||
|
|
||
| For controlling field-specific data, wrap the annotation in :class:`typing.Annotated` | ||
| with :class:`CFieldInfo` as the second argument, like so: | ||
|
Comment on lines
+3196
to
+3197
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a silly question, but why does this use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is more correct. |
||
|
|
||
| .. code-block:: python | ||
|
|
||
| @struct | ||
| class PyObject: | ||
| ob_refcnt: c_ssize_t | ||
| ob_type: c_void_p | ||
|
|
||
| @struct | ||
| class PyHovercraftObject: | ||
| ob_base: Annotated[PyObject, CFieldInfo(anonymous=True)] | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. class:: CFieldInfo(anonymous=False, bit_width=None) | ||
| :module: ctypes.util | ||
|
|
||
| Information regarding a structure field defined by the :func:`struct` | ||
| decorator. This should be used in the second argument of a | ||
| :class:`typing.Annotated` wrapping a ctypes type. | ||
|
|
||
| *anonymous* specifies whether the field will be present in the | ||
| :attr:`~ctypes.Structure._anonymous_` attribute of the generated class. | ||
|
|
||
| If *bit_width* is non-``None``, the annotated field will be *bit_width* | ||
| number of bits in the generated structure. This is equivalent to passing | ||
| a third item in :attr:`~ctypes.Structure._fields_`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. _ctypes-arrays-pointers: | ||
|
|
||
| Arrays and pointers | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,16 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from dataclasses import dataclass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy import functools | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy import shutil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy import annotationlib | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy from typing import Annotated, get_args, ClassVar, get_origin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lazy from ctypes import Structure, BigEndianStructure, LittleEndianStructure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # find_library(name) returns the pathname of a library, or None. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.name == "nt": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -491,6 +498,82 @@ def dllist(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctypes.byref(ctypes.py_object(libraries))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return libraries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @dataclass(slots=True, frozen=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class CFieldInfo: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anonymous: bool = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bit_width: int | None = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _process_struct(decorated_class, /, *, align, layout, endian, pack): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fields = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anonymous = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if issubclass(decorated_class, Structure): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fields.extend(decorated_class._fields_) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anonymous.extend(decorated_class._anonymous_) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for name, hint in annotationlib.get_annotations(decorated_class).items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if get_origin(hint) is ClassVar: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field = [name] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if get_origin(hint) is Annotated: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cls, field_info = get_args(hint) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field.append(cls) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(field_info, CFieldInfo): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise TypeError(f"expected CFieldInfo in Annotated, got {field_info!r}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if field_info.bit_width is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field.append(field_info.bit_width) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if field_info.anonymous is True: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anonymous.append(name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field.append(hint) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fields.append(field) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if endian == 'big': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endian_class = BigEndianStructure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif endian == 'little': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endian_class = LittleEndianStructure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif endian == 'native': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endian_class = Structure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(f"expected 'big', 'little', or 'native', but got {endian!r}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @functools.wraps(decorated_class, updated=()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class _Struct(endian_class): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vars().update(vars(decorated_class)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if align is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _align_ = align | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if layout is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _layout_ = layout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if pack is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _pack_ = pack | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _fields_ = fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _anonymous_ = anonymous | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _Struct | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def struct(class_or_none=None, /, *, align=None, layout=None, endian='native', pack=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process_the_struct = functools.partial( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _process_struct, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align=align, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout=layout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endian=endian, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pack=pack | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if class_or_none is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def inner(decorated_class): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return process_the_struct(decorated_class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return inner | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return process_the_struct(class_or_none) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+560
to
+575
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant it like this:
Suggested change
That's how decorators of this fashion are usually made.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edited the suggestion |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ################################################################ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # test code | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a collation: old syntax vs. new syntax.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's necessary -- the examples for
Structureare right above.