reification


Namereification JSON
Version 1.0.1 PyPI version JSON
download
home_pageNone
SummaryReified generics in Python to get type parameters at runtime
upload_time2024-03-20 16:28:36
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseWTFPL
keywords typing generics parametric polymorphism reification type parameter reflection
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Reification (Python library)

Reified generics in Python to get type parameters at runtime

```py
from reification import Reified


class ReifiedList[T](Reified, list[T]):
    pass


xs = ReifiedList[int](range(10))
print(xs)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(xs.targ)  # <class 'int'>
```

## Requirements

- Python >= 3.12

This library is written in pure Python and does not require any external modules.

## Install

```sh
pip3 install reification
```

## API

The public API is defined under the root of the `reification` package.

### `Reified` (class)

Usage: `from reification import Reified`

`Reified` is a Mixin class designed to facilitate the creation of new types based on reified type parameters.

This class is thread-safe so that inheriting classes can be used in multiple threads.

You cannot directly instantiate this class.

#### `targ: type | tuple[type | Any, ...] | Any` (class property)

This class property represents the type argument(s) specified for the reified generic class.
If there's more than one type argument, `targ` will be a tuple containing each given type or type-like value.
If a type argument is not specified, it may return `Any`.

#### `type_args: tuple[type | Any, ...]` (class property)

This is another class property that carries the type argument(s) provided for the reified generic class.
Unlike `targ`, `type_args` always returns a tuple of the specified type arguments, even when there's only one type argument.
If no type arguments are given, it may contain a single `Any`.

#### `__class_getitem__(cls, params: type | tuple[type | Any, ...] | Any) -> type` (special class method, for Mixin)

This method, which the class overrides, is used for creating new types each time it is called with distinct type arguments.
It serves a key role in handling parameterized generic classes, enabling the different identities on different type arguments of the same base class.

## Example Usage: Type-Checked Generic Stack

```py
from reification import Reified


class ReifiedStack[T](Reified):
    def __init__(self) -> None:
        super().__init__()
        self.items: list[T] = []

    def push(self, item: T) -> None:
        # We can do runtime check
        if isinstance(item, self.targ):
            self.items.append(item)
        else:
            raise TypeError()

    def pop(self) -> T:
        if self.items:
            return self.items.pop()
        else:
            raise IndexError("pop from empty stack")


stack = ReifiedStack[str]()
stack.push("spam")  # OK
stack.push(42)  # raise TypeError
```

The `ReifiedStack` class created here is generic and derived from the `Reified` base class, and implements a simple stack with `push` and `pop` methods.

In the `push` method, we are checking at runtime if the item being pushed is of the specified generic type (this type is accessible via the `targ` attribute inherited from `Reified`).
If the type of the item does not match, a `TypeError` is raised.

In the example usage, we create an instance of the ReifiedStack class with a type argument as string. When we try to push a string `"spam"`, the item is accepted since it matches with the stack's specified type argument.
However, when we try to push an integer `42`, a `TypeError` is raised because the type of item does not match with the stack's type argument.

This demonstrates the use of reified generics in Python where we can have runtime access to the type parameters, enabling us to type check dynamically at runtime.
This is useful in situations where we need to enforce type safety in our code or use type information at runtime.

## Typing

With `Reified` generic types, type parameters are considered for understanding and respecting the typing semantics as much as possible.

Python's native `isinstance` function works seamlessly with reified generic types.

In context of reified generics:

```py
>>> isinstance(ReifiedList[int](), ReifiedList[int])
True
```

The above expression returns `True` as a `ReifiedList` object of integer type is indeed an instance of a `ReifiedList` of integer type.

On the other hand:

```py
>>> isinstance(ReifiedList[str](), ReifiedList[int])
False
```

This returns `False` because, while both the objects are instances of the `ReifiedList` class, their type parameters are different (string vs integer).

### Type Equivalence

It treats two instances of the `Reified` derived same class as equivalent only if the type parameters provided in their instantiation are exactly the same.
That is, `ReifiedClass[T, ...] == ReifiedClass[S, ...]` if and only if `(T, ...) == (S, ...)`.

```py
>>> ReifiedList[float] == ReifiedList[float]
True
>>> ReifiedList[float] == ReifiedList[int]
False
>>> ReifiedList[tuple[int, str]] == ReifiedList[tuple[int, str]]
True
>>> ReifiedList[tuple[int, str]] == ReifiedList[tuple[int, float]]
False
>>> ReifiedList[ReifiedList[int]] == ReifiedList[ReifiedList[int]]
True
>>> ReifiedList[ReifiedList[int]] == ReifiedList[ReifiedList[str]]
False
```

### Subtyping

The `Reified` Mixin supports nominal subtyping.

Let type `A` and `B` be `Reified` derived class.
Type `A` is a subtype of type `B` if `A == B` or `A` is directly derived from `B`.

A `Reified` derived class with type parameters is considered a subtype of the same class without type parameters.
This means that `ReifiedClass[T, ...]` is a subtype of `ReifiedClass`.

```py
>>> issubclass(ReifiedList[int], ReifiedList[int])
True
>>> issubclass(ReifiedList, ReifiedList[int])
False
>>> issubclass(ReifiedList[int], ReifiedList)
True
>>> issubclass(ReifiedList[str], ReifiedList[int])
False
>>> class ReifiedListSub(ReifiedList[int]):
...     pass
...
>>> issubclass(ReifiedListSub, ReifiedList[int])
True
```

#### Type Variance

`Reified` Mixin only considers direct equivalence of type parameters for subtyping and does not cater for type variance.

```py
>>> issubclass(bool, int)
True
>>> class ReifiedTuple[T](Reified, tuple[T]):
...     pass
...
>>> issubclass(ReifiedTuple[bool], ReifiedTuple[int])
False
```

## License

WTFPL

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "reification",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": "curegit <contact@curegit.jp>",
    "keywords": "typing, generics, parametric polymorphism, reification, type parameter, reflection",
    "author": null,
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/a0/93/0b5006fd5e8cfde05f1d98c4a34679e8b39a8dfd66763314aa0721019f1d/reification-1.0.1.tar.gz",
    "platform": null,
    "description": "# Reification (Python library)\n\nReified generics in Python to get type parameters at runtime\n\n```py\nfrom reification import Reified\n\n\nclass ReifiedList[T](Reified, list[T]):\n    pass\n\n\nxs = ReifiedList[int](range(10))\nprint(xs)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\nprint(xs.targ)  # <class 'int'>\n```\n\n## Requirements\n\n- Python >= 3.12\n\nThis library is written in pure Python and does not require any external modules.\n\n## Install\n\n```sh\npip3 install reification\n```\n\n## API\n\nThe public API is defined under the root of the `reification` package.\n\n### `Reified` (class)\n\nUsage: `from reification import Reified`\n\n`Reified` is a Mixin class designed to facilitate the creation of new types based on reified type parameters.\n\nThis class is thread-safe so that inheriting classes can be used in multiple threads.\n\nYou cannot directly instantiate this class.\n\n#### `targ: type | tuple[type | Any, ...] | Any` (class property)\n\nThis class property represents the type argument(s) specified for the reified generic class.\nIf there's more than one type argument, `targ` will be a tuple containing each given type or type-like value.\nIf a type argument is not specified, it may return `Any`.\n\n#### `type_args: tuple[type | Any, ...]` (class property)\n\nThis is another class property that carries the type argument(s) provided for the reified generic class.\nUnlike `targ`, `type_args` always returns a tuple of the specified type arguments, even when there's only one type argument.\nIf no type arguments are given, it may contain a single `Any`.\n\n#### `__class_getitem__(cls, params: type | tuple[type | Any, ...] | Any) -> type` (special class method, for Mixin)\n\nThis method, which the class overrides, is used for creating new types each time it is called with distinct type arguments.\nIt serves a key role in handling parameterized generic classes, enabling the different identities on different type arguments of the same base class.\n\n## Example Usage: Type-Checked Generic Stack\n\n```py\nfrom reification import Reified\n\n\nclass ReifiedStack[T](Reified):\n    def __init__(self) -> None:\n        super().__init__()\n        self.items: list[T] = []\n\n    def push(self, item: T) -> None:\n        # We can do runtime check\n        if isinstance(item, self.targ):\n            self.items.append(item)\n        else:\n            raise TypeError()\n\n    def pop(self) -> T:\n        if self.items:\n            return self.items.pop()\n        else:\n            raise IndexError(\"pop from empty stack\")\n\n\nstack = ReifiedStack[str]()\nstack.push(\"spam\")  # OK\nstack.push(42)  # raise TypeError\n```\n\nThe `ReifiedStack` class created here is generic and derived from the `Reified` base class, and implements a simple stack with `push` and `pop` methods.\n\nIn the `push` method, we are checking at runtime if the item being pushed is of the specified generic type (this type is accessible via the `targ` attribute inherited from `Reified`).\nIf the type of the item does not match, a `TypeError` is raised.\n\nIn the example usage, we create an instance of the ReifiedStack class with a type argument as string. When we try to push a string `\"spam\"`, the item is accepted since it matches with the stack's specified type argument.\nHowever, when we try to push an integer `42`, a `TypeError` is raised because the type of item does not match with the stack's type argument.\n\nThis demonstrates the use of reified generics in Python where we can have runtime access to the type parameters, enabling us to type check dynamically at runtime.\nThis is useful in situations where we need to enforce type safety in our code or use type information at runtime.\n\n## Typing\n\nWith `Reified` generic types, type parameters are considered for understanding and respecting the typing semantics as much as possible.\n\nPython's native `isinstance` function works seamlessly with reified generic types.\n\nIn context of reified generics:\n\n```py\n>>> isinstance(ReifiedList[int](), ReifiedList[int])\nTrue\n```\n\nThe above expression returns `True` as a `ReifiedList` object of integer type is indeed an instance of a `ReifiedList` of integer type.\n\nOn the other hand:\n\n```py\n>>> isinstance(ReifiedList[str](), ReifiedList[int])\nFalse\n```\n\nThis returns `False` because, while both the objects are instances of the `ReifiedList` class, their type parameters are different (string vs integer).\n\n### Type Equivalence\n\nIt treats two instances of the `Reified` derived same class as equivalent only if the type parameters provided in their instantiation are exactly the same.\nThat is, `ReifiedClass[T, ...] == ReifiedClass[S, ...]` if and only if `(T, ...) == (S, ...)`.\n\n```py\n>>> ReifiedList[float] == ReifiedList[float]\nTrue\n>>> ReifiedList[float] == ReifiedList[int]\nFalse\n>>> ReifiedList[tuple[int, str]] == ReifiedList[tuple[int, str]]\nTrue\n>>> ReifiedList[tuple[int, str]] == ReifiedList[tuple[int, float]]\nFalse\n>>> ReifiedList[ReifiedList[int]] == ReifiedList[ReifiedList[int]]\nTrue\n>>> ReifiedList[ReifiedList[int]] == ReifiedList[ReifiedList[str]]\nFalse\n```\n\n### Subtyping\n\nThe `Reified` Mixin supports nominal subtyping.\n\nLet type `A` and `B` be `Reified` derived class.\nType `A` is a subtype of type `B` if `A == B` or `A` is directly derived from `B`.\n\nA `Reified` derived class with type parameters is considered a subtype of the same class without type parameters.\nThis means that `ReifiedClass[T, ...]` is a subtype of `ReifiedClass`.\n\n```py\n>>> issubclass(ReifiedList[int], ReifiedList[int])\nTrue\n>>> issubclass(ReifiedList, ReifiedList[int])\nFalse\n>>> issubclass(ReifiedList[int], ReifiedList)\nTrue\n>>> issubclass(ReifiedList[str], ReifiedList[int])\nFalse\n>>> class ReifiedListSub(ReifiedList[int]):\n...     pass\n...\n>>> issubclass(ReifiedListSub, ReifiedList[int])\nTrue\n```\n\n#### Type Variance\n\n`Reified` Mixin only considers direct equivalence of type parameters for subtyping and does not cater for type variance.\n\n```py\n>>> issubclass(bool, int)\nTrue\n>>> class ReifiedTuple[T](Reified, tuple[T]):\n...     pass\n...\n>>> issubclass(ReifiedTuple[bool], ReifiedTuple[int])\nFalse\n```\n\n## License\n\nWTFPL\n",
    "bugtrack_url": null,
    "license": "WTFPL",
    "summary": "Reified generics in Python to get type parameters at runtime",
    "version": "1.0.1",
    "project_urls": {
        "homepage": "https://github.com/curegit/reification",
        "repository": "https://github.com/curegit/reification.git"
    },
    "split_keywords": [
        "typing",
        " generics",
        " parametric polymorphism",
        " reification",
        " type parameter",
        " reflection"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "50c954e6e5183e69ce4d25d90c2cf35575f9d3d62f9788f39cb775c4950ddb93",
                "md5": "95b0c2fa150b012662b03f683f88f692",
                "sha256": "4f4dbf8298fe8364a3676b87df9f170bda95df5fdf9f070a028e1e4318ce7c81"
            },
            "downloads": -1,
            "filename": "reification-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "95b0c2fa150b012662b03f683f88f692",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 5525,
            "upload_time": "2024-03-20T16:28:35",
            "upload_time_iso_8601": "2024-03-20T16:28:35.642009Z",
            "url": "https://files.pythonhosted.org/packages/50/c9/54e6e5183e69ce4d25d90c2cf35575f9d3d62f9788f39cb775c4950ddb93/reification-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a0930b5006fd5e8cfde05f1d98c4a34679e8b39a8dfd66763314aa0721019f1d",
                "md5": "97f98c9fa219df18a8d0051b55b8fb9d",
                "sha256": "460503bb386148a419da46b6e0bbc64094b18d67a1b9719f0b29f93c007fe431"
            },
            "downloads": -1,
            "filename": "reification-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "97f98c9fa219df18a8d0051b55b8fb9d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 6832,
            "upload_time": "2024-03-20T16:28:36",
            "upload_time_iso_8601": "2024-03-20T16:28:36.753209Z",
            "url": "https://files.pythonhosted.org/packages/a0/93/0b5006fd5e8cfde05f1d98c4a34679e8b39a8dfd66763314aa0721019f1d/reification-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-20 16:28:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "curegit",
    "github_project": "reification",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "reification"
}
        
Elapsed time: 0.24578s