2025, Dec 07 05:00
Avoid the Python ctypes 'TypeError: _type_ must have storage info': delay POINTER creation with a metaclass
Learn why Python ctypes raises TypeError: _type_ must have storage info when POINTER is used in __init_subclass__, plus a metaclass fix to auto-create pointer.
When defining Python ctypes structures, it’s tempting to automatically attach a pointer type to every subclass as soon as it’s declared. But if you try to do this in __init_subclass__, you might run into the notorious TypeError: _type_ must have storage info. Below is a minimal, reproducible scenario and a robust pattern to avoid the trap.
Problem setup
The goal is to create a pointer type for each new ctypes.Structure subclass at the moment it’s declared. Here’s the minimal code that triggers the error.
import ctypes
from ctypes.wintypes import *
class BaseStruct(ctypes.Structure):
def __init_subclass__(subcls):
alias_ptr = ctypes.POINTER(subcls)
class OFFSET_VAL_V2_Q7(BaseStruct):
_fields_ = [('original', DOUBLE),
('max_delta', DOUBLE),
('cur_delta', DOUBLE)]
This fails with TypeError: _type_ must have storage info on the POINTER call. Meanwhile, a functionally equivalent approach that delays creating the pointer until after the class definition works without issues.
import ctypes
from ctypes.wintypes import *
class BaseStruct(ctypes.Structure):
pass
class OFFSET_VAL_V2_Q7(BaseStruct):
_fields_ = [('original', DOUBLE),
('max_delta', DOUBLE),
('cur_delta', DOUBLE)]
ptr_t = ctypes.POINTER(OFFSET_VAL_V2_Q7)
What’s actually going on
The crux lies in the timing of __init_subclass__. When it runs for a subclass, the subclass object exists but the class body hasn’t fully executed yet. Specifically for ctypes.Structure, that means _fields_ has not yet been set at that moment. As far as ctypes is concerned, the subclass does not have storage info and therefore cannot be the target of POINTER. The second example succeeds because POINTER is called only after the structure is fully defined and _fields_ is in place.
Fix: delay pointer creation until the structure is finalized
If the objective is to automatically define a pointer type for each structure subclass, the POINTER call needs to happen after the class is completely built. A metaclass is a clean way to do that, because its __init__ runs after the class body has been executed. Here’s a working pattern.
import ctypes
from ctypes.wintypes import *
class PtrAutoMeta(type(ctypes.Structure)):
def __init__(cls, name, bases, ns):
super().__init__(name, bases, ns)
if hasattr(cls, "_fields_"):
cls.PTYPE = ctypes.POINTER(cls)
class BaseStruct(ctypes.Structure, metaclass=PtrAutoMeta):
pass
class OFFSET_VAL_V2_Q7(BaseStruct):
_fields_ = [('original', DOUBLE),
('max_delta', DOUBLE),
('cur_delta', DOUBLE)]
# Usage
ptr_t = OFFSET_VAL_V2_Q7.PTYPE
Here, the metaclass checks for _fields_ and only then wires up PTYPE, ensuring ctypes sees a fully realized structure with storage info.
Why this matters
ctypes is strict about when a type becomes usable as a memory-backed structure. Any attempt to derive dependent types like pointers before _fields_ is established will fail. Understanding the lifecycle of class creation, and especially the order in which __init_subclass__, the class body, and metaclass hooks run, helps avoid subtle initialization bugs in low-level interop code.
Takeaways
Don’t construct ctypes.POINTER during __init_subclass__ for ctypes.Structure subclasses. The structure isn’t finalized at that moment. If you want automatic pointer aliases, use a metaclass and create them after _fields_ is present. Alternatively, keep it simple and declare pointer types explicitly after each structure is defined. Either way, deferring pointer creation until after the structure has storage info keeps your interop layer predictable and error-free.