Skip to content

Commit 4393cc3

Browse files
committed
mainly docs and decay stuff
1 parent cd91c2c commit 4393cc3

File tree

13 files changed

+299
-35
lines changed

13 files changed

+299
-35
lines changed

docs/pointers.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/using_pointers.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Using Pointers
2+
3+
## Creation
4+
5+
To create a pointer, you can use `to_ptr`, like so:
6+
7+
```py
8+
from pointers import to_ptr
9+
10+
ptr = to_ptr("hello world")
11+
```
12+
13+
You can also use the `_` object to replicate the address-of operator, like in other languages:
14+
15+
```py
16+
from pointers import _
17+
18+
ptr = _&"hello world"
19+
```
20+
21+
Finally, you can directly call `Pointer.make_from`:
22+
23+
```py
24+
from pointers import Pointer
25+
26+
ptr = Pointer.make_from("hello world")
27+
```
28+
29+
**Note:** `Pointer.make_from` is more a low level function for creating a pointer. Its API may be changed at any time without warning.
30+
31+
## Dereferencing
32+
33+
There are a few ways to get the underlying value of the pointer, the simplest being calling the `dereference()` method, like so:
34+
35+
```py
36+
from pointers import _
37+
38+
ptr = _&"hi"
39+
print(ptr.dereference()) # prints out "hi"
40+
```
41+
42+
Unfortunately, `dereference` is a pretty long name and doesn't look very pretty when you call it everywhere. Fortunately though, there are different (and more preffered) ways of dereferencing the pointer.
43+
44+
We can use the `_` object to once again replicate the syntax from other languages:
45+
46+
```py
47+
from pointers import _
48+
49+
ptr = _&"hi"
50+
print(_*ptr)
51+
```
52+
53+
In some cases like this one, you can even just directly use `*`, without even having to touch `_`!
54+
55+
```py
56+
ptr = _&"hi"
57+
print(*ptr) # works just fine
58+
```
59+
60+
However, `*` is for arg splats, which introduces some problems. You can use `~` as an alternative, which will always work:
61+
62+
```py
63+
ptr = _&"hi"
64+
print(~ptr)
65+
# ~ is a unary operator, so we can use it anywhere we want
66+
```
67+
68+
## Decaying
69+
70+
Converting objects to pointers everywhere may be a bit ugly. To fix this, pointers.py provides a few functions to decay parameters into their pointer equivalents.
71+
72+
The most simple one is `decay`:
73+
74+
```py
75+
from pointers import decay, Pointer
76+
77+
@decay
78+
def my_function(a: str, b: str, c: Pointer): # must be type hinted as Pointer to convert
79+
print(a, b, *c)
80+
81+
my_function('a', 'b', 'c')
82+
```
83+
84+
This will be fine for most people, but it hsa removes type safety on the target function. If you don't care about type safety in your code, then don't worry about this, but if you do, then there are alternatives.
85+
86+
The first alternative is `decay_annotated`, which decays parameters hinted as `Annotated[T, Pointer]` to a pointer.
87+
88+
Here's an example:
89+
90+
```py
91+
from pointers import decay_annotated, Pointer
92+
from typing import Annotated # if you are below python 3.9, you can use typing_extensions here
93+
94+
@decay_annotated
95+
def my_function(a: str, b: str, c: Annotated[str, Pointer]):
96+
print(a, b, *c)
97+
98+
my_function('a', 'b', 'c')
99+
```
100+
101+
However, `decay_annotated` has a very large drawback.
102+
103+
While it adds type safety for calling the function, it breaks it inside. A type checker still thinks that the argument is a `str`, and not a `Pointer`.
104+
105+
Take the following as an example:
106+
107+
```py
108+
@decay_annotated
109+
def my_function(a: str, b: str, c: Annotated[str, Pointer]):
110+
print(a, b, ~c) # type checker error!
111+
```
112+
113+
The solution is to use `decay_wrapped`, which takes the caller function as a paremeter:
114+
115+
```py
116+
from pointers import decay_wrapped, Pointer
117+
118+
def my_function_wrapper(a: str, b: str, c: str) -> None: # this should mimick your actual function
119+
...
120+
121+
@decay_wrapped(my_function_wrapper)
122+
def my_function(a: str, b: str, c: Pointer[str]):
123+
print(a, b, *c)
124+
print(a, b, ~c) # no type checker error, it thinks c is a pointer!
125+
126+
my_function('a', 'b', 'c') # works just fine, type checker things c takes a string
127+
```
128+
129+
It can be broken pretty easily though:
130+
131+
```py
132+
from pointers import decay_wrapped, Pointer
133+
134+
def my_function_wrapper(a: str, b: str, c: str, d: str) -> None:
135+
...
136+
137+
@decay_wrapped(my_function_wrapper)
138+
def my_function(a: str, b: str, c: Pointer[str]):
139+
print(a, b, *c)
140+
141+
my_function('a', 'b', 'c') # type checker error! missing parameter "d"
142+
```
143+
144+
## Assignment
145+
146+
We can change where the pointer is looking at by using `assign`, or more commonly, the `>>=` operator:
147+
148+
```py
149+
from pointers import _
150+
151+
ptr = _&"hi"
152+
ptr.assign("hello")
153+
ptr >>= "hello" # equivalent to the above
154+
155+
print(*ptr) # prints out "hello"
156+
```

hatch.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ path = "src/pointers/version.py"
44
[envs.default]
55
dependencies = [
66
"ward",
7+
"typing_extensions",
78
]
89
[envs.default.scripts]
910
tests = "ward"

src/pointers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .c_utils import force_set_attr
1111
from .calloc import AllocatedArrayPointer, calloc
1212
from .custom_binding import binding, binds
13-
from .decay import decay
13+
from .decay import decay, decay_annotated, decay_wrapped
1414
from .exceptions import (
1515
AllocationError, DereferenceError, FreedMemoryError,
1616
InvalidBindingParameter, InvalidSizeError

src/pointers/base_pointers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def _make_stream_and_ptr(
173173
self,
174174
size: int,
175175
address: int,
176-
) -> Tuple[ctypes._PointerLike, bytes]:
176+
) -> Tuple["ctypes._PointerLike", bytes]:
177177
...
178178

179179

@@ -279,7 +279,7 @@ def _make_stream_and_ptr(
279279
self,
280280
size: int,
281281
address: int,
282-
) -> Tuple[ctypes._PointerLike, bytes]:
282+
) -> Tuple["ctypes._PointerLike", bytes]:
283283
bytes_a = (ctypes.c_ubyte * size).from_address(address)
284284
return self.make_ct_pointer(), bytes(bytes_a)
285285

@@ -394,7 +394,7 @@ def _make_stream_and_ptr(
394394
self,
395395
size: int,
396396
address: int,
397-
) -> Tuple[ctypes._PointerLike, bytes]:
397+
) -> Tuple["ctypes._PointerLike", bytes]:
398398
if self.freed:
399399
raise FreedMemoryError("memory has been freed")
400400

src/pointers/bindings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
T = TypeVar("T")
2929

30-
PointerLike = Union[TypedCPointer[Any], VoidPointer, None]
30+
PointerLike = Union[TypedCPointer[T], VoidPointer, None]
3131
StringLike = Union[str, bytes, VoidPointer, TypedCPointer[bytes]]
3232
Format = Union[StringLike, PointerLike]
33-
TypedPtr = Optional[TypedCPointer[T]]
33+
TypedPtr = Optional[PointerLike[T]]
3434
PyCFuncPtrType = type(ctypes.CFUNCTYPE(None))
3535

3636
__all__ = (

src/pointers/c_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
def move_to_mem(
20-
ptr: ctypes._PointerLike,
20+
ptr: "ctypes._PointerLike",
2121
stream: bytes,
2222
*,
2323
unsafe: bool = False,

src/pointers/calloc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(
2828
chunks: int,
2929
chunk_size: int,
3030
current_index: int,
31-
chunk_cache: Optional[Dict[int, "AllocatedArrayPointer[T]"]] = None,
31+
chunk_store: Optional[Dict[int, "AllocatedArrayPointer[T]"]] = None,
3232
freed: bool = False,
3333
origin_address: Optional[int] = None,
3434
) -> None:
@@ -37,12 +37,12 @@ def __init__(
3737
self._size = chunk_size
3838
self._current_index = current_index
3939
self._chunks = chunks
40-
self._chunk_store = chunk_cache or {0: self}
40+
self._chunk_store = chunk_store or {0: self}
4141
self._assigned = True
4242
self._tracked = False
4343
self._freed = freed
4444

45-
if chunk_cache:
45+
if chunk_store:
4646
self._chunk_store[self.current_index] = self
4747

4848
# https://github.com/python/mypy/issues/4125

src/pointers/decay.py

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
11
import inspect
22
from contextlib import suppress
33
from functools import wraps
4-
from typing import Callable, TypeVar, get_type_hints
4+
from typing import Any, Callable, Dict, Tuple, TypeVar, get_type_hints
55

6-
from typing_extensions import ParamSpec
6+
from typing_extensions import Annotated, ParamSpec, get_args, get_origin
77

88
from .object_pointer import Pointer, to_ptr
99

1010
T = TypeVar("T")
1111
P = ParamSpec("P")
1212

13-
__all__ = ("decay",)
13+
__all__ = ("decay", "decay_annotated", "decay_wrapped")
14+
15+
16+
def _make_func_params(
17+
func: Callable[P, Any],
18+
args: Tuple[Any, ...],
19+
kwargs: Dict[str, Any],
20+
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
21+
hints = get_type_hints(func, include_extras=True)
22+
actual: dict = {}
23+
params = inspect.signature(func).parameters
24+
25+
for index, key in enumerate(params):
26+
if key in kwargs:
27+
actual[key] = kwargs[key]
28+
else:
29+
with suppress(IndexError):
30+
actual[params[key].name] = args[index]
31+
32+
return (hints, actual)
33+
34+
35+
def _decay_params(
36+
func: Callable[..., Any],
37+
args: Tuple[Any, ...],
38+
kwargs: Dict[str, Any],
39+
) -> Dict[str, Any]:
40+
hints, actual = _make_func_params(func, args, kwargs)
41+
42+
for key, value in hints.items():
43+
if (get_origin(value) is Pointer) or (value is Pointer):
44+
actual[key] = to_ptr(actual[key])
45+
46+
return actual
1447

1548

1649
def decay(func: Callable[P, T]) -> Callable[..., T]:
1750
"""Automatically convert values to pointers when called."""
1851

1952
@wraps(func)
2053
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
21-
hints = get_type_hints(func)
22-
actual: dict = {}
23-
params = inspect.signature(func).parameters
54+
actual = _decay_params(func, args, kwargs)
55+
return func(**actual) # type: ignore
56+
57+
return inner
2458

25-
# mypy is giving false positives here since it doesn't know how to
26-
# handle paramspec
27-
for index, key in enumerate(params):
28-
if key in kwargs: # type: ignore
29-
actual[key] = kwargs[key] # type: ignore
30-
else:
31-
with suppress(IndexError):
32-
actual[params[key].name] = args[index] # type: ignore
3359

34-
for key, value in hints.items():
35-
if hasattr(value, "__origin__"):
36-
origin = value.__origin__
60+
def decay_annotated(func: Callable[P, T]) -> Callable[P, T]:
61+
@wraps(func)
62+
def wrapped(*args: P.args, **kwargs: P.kwargs):
63+
hints, actual = _make_func_params(func, args, kwargs)
3764

38-
if origin is not Pointer:
39-
continue
65+
for param, hint in hints.items():
66+
if get_origin(hint) is not Annotated:
67+
continue
4068

41-
actual[key] = to_ptr(actual[key])
69+
hint_arg = get_args(hint)[1]
70+
71+
if (hint_arg is Pointer) or (get_origin(hint_arg) is Pointer):
72+
actual[param] = to_ptr(actual[param])
4273

4374
return func(**actual) # type: ignore
4475

45-
return inner
76+
return wrapped
77+
78+
79+
def decay_wrapped(wrapper: Callable[P, T]) -> Callable[..., Callable[P, T]]:
80+
def decorator(func: Callable[..., T]) -> Callable[P, T]:
81+
@wraps(func)
82+
def wrapped(*args: P.args, **kwargs: P.kwargs):
83+
actual = _decay_params(func, args, kwargs)
84+
return func(**actual)
85+
86+
return wrapped
87+
88+
return decorator # type: ignore

src/pointers/struct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __init__(
8787
@property
8888
def _as_parameter_(
8989
self,
90-
) -> Union[int, ctypes._PointerLike]:
90+
) -> Union[int, "ctypes._PointerLike"]:
9191
existing = self._existing
9292

9393
if existing:

0 commit comments

Comments
 (0)