Skip to content

Commit

Permalink
Merge pull request #50 from ionite34/docs-update
Browse files Browse the repository at this point in the history
  • Loading branch information
ionite34 committed Feb 2, 2023
2 parents 932462e + 685861c commit 31b50f6
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 2 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ PyListObject(at 0x2833738):
[py_doc_mutable_seq]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
## Mutate tuples, strings, ints, or other immutable types
> [TupleView][doc_tuple_view] and [StrView][doc_str_view] supports all [MutableSequence][py_doc_mutable_seq] methods (append, extend, insert, pop, remove, reverse, clear).
> ⚠️ A note on [safety.](#safety)
```python
from einspect import view

Expand Down Expand Up @@ -111,6 +113,7 @@ custom: 2
```

## Implement methods on built-in types
> See the [Extending Types](https://docs.ionite.io/einspect/extending_types.html) docs page for more information.
```python
from einspect import impl, orig

Expand Down Expand Up @@ -141,6 +144,70 @@ print("meaning of life" == 42) # True
## Fully typed interface
<img width="551" alt="image" src="https://user-images.githubusercontent.com/13956642/211129165-38a1c405-9d54-413c-962e-6917f1f3c2a1.png">

## Safety

This project is mainly for learning purposes or inspecting and debugging CPython internals for development and fun. You should not violate language conventions like mutability in production software and libraries.

The interpreter makes assumptions regarding types that are immutable, and changing them causes all those usages to be affected. While the intent of the project is to make a memory-correct mutation without further side effects, there can be very significant runtime implications of mutating interned strings with lots of shared references, including interpreter crashes.

For example, some strings like "abc" are interned and used by the interpreter. Changing them changes all usages of them, even attribute calls like `collections.abc`.

> The spirit of safety maintained by einspect is to do with memory layouts, not functional effects.
### For example, appending to tuple views (without an unsafe context) will check that the resize can fit within allocated memory
```python
from einspect import view

tup = (1, 2)
v = view(tup)

v.append(3)
print(tup) # (1, 2, 3)

v.append(4)
# UnsafeError: insert required tuple to be resized beyond current memory allocation. Enter an unsafe context to allow this.
```
- Despite this, mutating shared references like empty tuples can cause issues in interpreter shutdown and other runtime operations.
```python
from einspect import view

tup = ()
view(tup).append(1)
```
```
Exception ignored in: <module 'threading' from '/lib/python3.11/threading.py'>
Traceback (most recent call last):
File "/lib/python3.11/threading.py", line 1563, in _shutdown
_main_thread._stop()
File "/lib/python3.11/threading.py", line 1067, in _stop
with _shutdown_locks_lock:
TypeError: 'str' object cannot be interpreted as an integer
```

### Similarly, memory moves are also checked for GC-header compatibility and allocation sizes
```python
from einspect import view

v = view(101)
v <<= 2

print(101) # 2

v <<= "hello"
# UnsafeError: memory move of 54 bytes into allocated space of 32 bytes is out of bounds. Enter an unsafe context to allow this.
```

- However, this will not check the fact that small integers between (-5, 256) are interned and used by the interpreter. Changing them may cause issues in any library or interpreter Python code.
```python
from einspect import view

view(0) << 100

exit()
# sys:1: ImportWarning: can't resolve package from __spec__ or __package__, falling back on __name__ and __path__
# IndexError: string index out of range
```

## Table of Contents
- [Views](#views)
- [Using the `einspect.view` constructor](#using-the-einspectview-constructor)
Expand Down
6 changes: 6 additions & 0 deletions docs/source/api/orig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# orig

```{eval-rst}
.. autoclass:: einspect.type_orig.orig
:members:
```
2 changes: 2 additions & 0 deletions docs/source/api/views/view_type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ TypeView
.. autoclass:: einspect.views.view_type.TypeView
:members:
:inherited-members:

.. autodecorator:: einspect.views.view_type.impl
93 changes: 93 additions & 0 deletions docs/source/extending_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Extending Types

- {class}`~einspect.views.view_type.TypeView` subscripting can be used to set attributes on type objects, including built-in types.
- The {meth}`~einspect.views.view_type.impl` decorator can implement the decorated method onto a type.

## Using the {meth}`~einspect.views.view_type.impl` decorator

> Works for normal methods, dunder methods, or decorated static / class / property methods.
```python
from einspect import impl

@impl(int)
def is_even(self):
return self % 2 == 0

print((2).is_even()) # True

@impl(int)
def __matmul__(self, other):
return self * other

print(5 @ 3) # 15

@impl(int)
@property
def real(self):
return self + 1

print((2).real) # 3

@impl(int)
@classmethod
def try_from(cls, x):
try:
return cls(x)
except ValueError:
return None

print(int.try_from('2')) # 2
print(int.try_from('a')) # None

@impl(list)
@staticmethod
def abc():
return "abc"

print(list.abc()) # "abc"
print([].abc()) # "abc"
```
## Using {class}`~einspect.type_orig.orig` to get original attributes
Sometimes you may want to defer to the original implementation of a method or attribute before it was overriden by `impl` or `TypeView`, in this case calling `orig(<type>)` will return a proxy object of the type where attribute access will yield original attributes.

For example, we want to override the `__add__` of floats, by adding a print statement, but we want to still call the original `__add__` after our print.

Calling `orig(float)` will give us the float proxy object, where `orig(float).__add__` will give us the original `float.__add__` method before our override.

```python
from einspect import impl, orig

@impl(float)
def __add__(self, other):
print(f"Adding {self} and {other}")
return orig(float).__add__(self, other)
```

## Using {meth}`~einspect.views.view_type.TypeView` subscripting
Views of types can be subscripted to either get or set attributes on the type. This works in all cases where `@impl` can be used.

In addition, this form can also set static attributes like `__dict__` or `__name__`.
```python
from einspect import view

v = view(int)
print(v["__name__"]) # int

v["is_even"] = lambda self: self % 2 == 0
print((2).is_even()) # True

v["__name__"] = "MyInt"
print(int.__name__) # MyInt
print(int) # <class 'MyInt'>
```

Multiple attributes can be set together by passing multiple attribute names.
```python
from einspect import view

v = view(str)
v["__truediv__", "__floordiv__"] = str.split

print("Hello, world!" / ", ") # ['Hello', 'world!']
print("abc-xyz" // "-") # ['abc', 'xyz']
```
2 changes: 2 additions & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Extended Inspections for CPython.
intro
views
structs
extending_types
```

```{toctree}
Expand All @@ -29,6 +30,7 @@ structs
api/views/index
api/structs/index
api/orig
api/types
```

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "einspect"
version = "0.5.7"
version = "0.5.7.post1"
packages = [{ include = "einspect", from = "src" }]
description = "Extended Inspect - view and modify memory structs of runtime objects."
authors = ["ionite34 <dev@ionite.io>"]
Expand Down
2 changes: 1 addition & 1 deletion src/einspect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@

__all__ = ("view", "unsafe", "impl", "orig", "ptr", "NULL")

__version__ = "0.5.7"
__version__ = "0.5.7.post1"

unsafe: ContextManager[None] = global_unsafe

0 comments on commit 31b50f6

Please sign in to comment.