From 33e4004aef94e3379d2c19721f0f1633dbfcafc1 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:52:55 +0900 Subject: [PATCH 01/18] Update --- README.md | 48 ++++++++++++++++++- reification/__init__.py | 21 +++++++- reification/reific.py | 34 +++++++------ reification/reified.py | 18 +++++++ reification/reify.py | 24 ++++++++++ reification/utils.py | 46 ++++++++++++++++++ tests/reific_stack.py | 1 + tests/reified_types.py | 3 ++ tests/test_reific.py | 39 ++++++++++++++- tests/{test_utils.py => test_reific_stack.py} | 0 tests/test_reified_types.py | 5 ++ tests/test_type_basics.py | 29 +++++++++++ 12 files changed, 245 insertions(+), 23 deletions(-) create mode 100644 reification/reified.py create mode 100644 reification/reify.py create mode 100644 tests/reified_types.py rename tests/{test_utils.py => test_reific_stack.py} (100%) create mode 100644 tests/test_reified_types.py create mode 100644 tests/test_type_basics.py diff --git a/README.md b/README.md index 6ed6480..ecd3c21 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,50 @@ -# Reification (Python package) +# Reification (Python library) + +This value repr types. +Typ semantics are depends on each usage + +## Install + +```sh +pip3 install reification +``` + +## API + +all public API is just below `reification` package + +### `refiy` (function) + +make defined normal generic class parameter-reified + +`type_args` : + +#### example + +```py +>>> from reification import reify +>>> xs = reify(list)[int]([1, 2, 3]) +>>> t, = xs.type_args +>>> t +int +``` + +``` +reify(list)[int] is reify(list)[] +reify(list)[] is reify(list)[] +``` + +### `Reific` (class) + +create type parameter-reified generics class + + +derives from base class +subtype nominaly, type eq when type parameter eq + +``` +MyStack[int] +``` ## License diff --git a/reification/__init__.py b/reification/__init__.py index 36c4c48..506e96a 100644 --- a/reification/__init__.py +++ b/reification/__init__.py @@ -1,3 +1,20 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" -from reification.reific import Reific +from .reify import reify +from .reific import Reific +from .reified import ReifiedType + +__all__ = ["reify", "Reific", "ReifiedType"] + + + +#a: type[int] = bool +#b: type[bool] = int #NG + +e: type[bool] = bool + +#c: type[bool] = a # NG +d: type[int] = e #OK + +f: list[bool] = [] +m: list[int] = f diff --git a/reification/reific.py b/reification/reific.py index f346d61..ee37141 100644 --- a/reification/reific.py +++ b/reification/reific.py @@ -1,25 +1,23 @@ -import types from typing import Generic, TypeVarTuple, Any +from .utils import get_type, tuplize_class_getitem_params + + + Ts = TypeVarTuple("Ts") -type_dict = {} class Reific(Generic[*Ts]): - def __class_getitem__(cls, key): - if key in type_dict: - return type_dict[key] - - #class r(cls): - # pass - - reified = types.new_class( - name=cls.__name__, - bases=(cls,), - exec_body=(lambda ns: ns) - ) - if isinstance(key, type): - reified.type_arg = key - type_dict[key] = reified - return reified + # TODO: Reified[Self] + def __class_getitem__(cls, params: Any) -> type[Self]: + + # + super().__class_getitem__(params) + + # + param_tuple = tuplize_class_getitem_params(params) + t = get_type(cls, param_tuple) + # + #t.key = + return t diff --git a/reification/reified.py b/reification/reified.py new file mode 100644 index 0000000..b3efd0c --- /dev/null +++ b/reification/reified.py @@ -0,0 +1,18 @@ +from typing import Generic, TypeVar, Any +from abc import ABC, abstractmethod + + +T = TypeVar("T", covariant=True) + + +type[T & protocol] + + + +from typing import Protocol + +# Reified : T +class Reified: + #@abstractmethod + def type_args(cls) -> Any: + pass diff --git a/reification/reify.py b/reification/reify.py new file mode 100644 index 0000000..227b1b6 --- /dev/null +++ b/reification/reify.py @@ -0,0 +1,24 @@ +from typing import TypeVar, Any +from .utils import get_type, tuplize_class_getitem_params + +T = TypeVar("T") + +# TODO: Reified[T] +# Reified = type[T] & Reified +def reify(base_type: type[T]) -> type[T]: + #@cache しないといけない + + # Generic を継承する? + class _Reified(base_type): + + def __class_getitem__(cls, params: Any): + + if hasattr(super(), "__class_getitem__"): + super().__class_getitem__(params) + + + param_tuple = tuplize_class_getitem_params(params) + t = get_type(cls, param_tuple) + t.type_args = params + return t + return _Reified diff --git a/reification/utils.py b/reification/utils.py index e69de29..00479d6 100644 --- a/reification/utils.py +++ b/reification/utils.py @@ -0,0 +1,46 @@ +import types +from typing import TypeAlias, Any + +a: type = list +b: type[int] = bool +c: type[bool] = int + +d: tuple[int, str] = (1, "") + +#a: TypeAlias = +#a: TypeAlias = + +type_dict: dict[tuple[type, tuple[type | Any]], type] = dict() + + +def get_type(base_cls: type[T], type_args: tuple[type | Any]) -> type[T]: + k = (base_cls, type_args) + + if k in type_dict: + return type_dict[k] + else: + t = new_type(base_cls, type_args) + type_dict[k] = t + return t + +from typing import Protocol +class RT(Protocol) + + +def new_type(cls: type[T], params: tuple) -> type[Reified, T]: + #class ntype(base_cls): + # typeargs = key + reified2 = types.new_class( + name=cls.__name__, + bases=(cls,), + exec_body=(lambda ns: ns) + ) + + return reified2 + + +def tuplize_class_getitem_params(params: Any) -> tuple: + if isinstance(params, tuple): + return params + else: + return (params,) diff --git a/tests/reific_stack.py b/tests/reific_stack.py index 156c7ab..5490ae5 100644 --- a/tests/reific_stack.py +++ b/tests/reific_stack.py @@ -5,3 +5,4 @@ class Stack(Reific[T]): pass + diff --git a/tests/reified_types.py b/tests/reified_types.py new file mode 100644 index 0000000..fb77a39 --- /dev/null +++ b/tests/reified_types.py @@ -0,0 +1,3 @@ +from reification import reify + +reified_list = reify(list) diff --git a/tests/test_reific.py b/tests/test_reific.py index 29ec856..ef66f93 100644 --- a/tests/test_reific.py +++ b/tests/test_reific.py @@ -1,7 +1,27 @@ import itertools from unittest import TestCase -from reific_stack import Stack +""" +from reific_stack import CheckedStack +from reific_dict import ReifiedDict + +class GetTypeTest(TestCase): + + + def test_stack(): + types = [int, float, str, bool, list, list[int], dict[int, list[str]]] + Stack[] + + def test_non_(): + a = Stack() + assertError() + + + def test_dict + + + def test_dict_non + class SubClassTest(TestCase): @@ -17,7 +37,22 @@ def test_list(self): else: self.assertFalse(issubclass(l1, l2)) + def test_union + + def test_alias class InstanceTest: - pass + + def test_list(self): + i = Stack[int]() + f = Stack[float]() + self.assertInstance(i, Stack[int]) + self.assertInstance(i, Stack[int]) + isinstance(i, ) + + def test_in(): + i = Stack[int | str]() + f = Stack[float]() + +""" diff --git a/tests/test_utils.py b/tests/test_reific_stack.py similarity index 100% rename from tests/test_utils.py rename to tests/test_reific_stack.py diff --git a/tests/test_reified_types.py b/tests/test_reified_types.py new file mode 100644 index 0000000..de262f3 --- /dev/null +++ b/tests/test_reified_types.py @@ -0,0 +1,5 @@ +from reified_types import reified_list + + +a = reified_list[int]() + diff --git a/tests/test_type_basics.py b/tests/test_type_basics.py new file mode 100644 index 0000000..d24920a --- /dev/null +++ b/tests/test_type_basics.py @@ -0,0 +1,29 @@ +from typing import TypeVar, Any +from unittest import TestCase + + +T = TypeVar("T") +S = TypeVar("S") + + +class Eqtest(TestCase): + + def test_union(self): + self.assertEqual(int | str, str | int) + self.assertNotEqual(int | str, int) + self.assertEqual(list[int] | list[str], list[str] | list[int]) + self.assertNotEqual(list | list[int], list[int]) + + def test_generics(self): + self.assertEqual(list[int], list[int]) + self.assertNotEqual(list[int], list[str]) + self.assertEqual(dict[int, list[str]], dict[int, list[str]]) + self.assertNotEqual(dict[int, list[str]], dict[int, list[float]]) + + def test_any(self): + self.assertEqual(Any, Any) + self.assertNotEqual(list[int], list[Any]) + + def test_typevar(self): + self.assertEqual(T, T) + self.assertNotEqual(T, S) From 1116eaa892fc2a0715d4b3f335902aa2555354da Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:44:26 +0900 Subject: [PATCH 02/18] Update README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7e63a31..6c60885 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # Reification (Python library) +Reified generics in Python to get type parameters at runtime ```py from reification import Reified -class ReifiedList[T](Reified): pass +class ReifiedList[T](Reified, list): + pass -l = ReifiedList[int]([1, 2, 3]) - -print(l.types) # int +xs = ReifiedList[int]([1, 2, 3]) +print(xs.reified_type) # ``` ## Requirements Python >= 3.12 -Any non-builtin modules are NOT required. +This library is written in pure Python and any non-builtin modules are NOT required. ## Install From 1b369602be31ecdc41cc49e9f02352b3b35b0dc1 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:57:49 +0900 Subject: [PATCH 03/18] Complete basics test --- tests/test_type_basics.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/test_type_basics.py b/tests/test_type_basics.py index d24920a..8699b59 100644 --- a/tests/test_type_basics.py +++ b/tests/test_type_basics.py @@ -1,13 +1,9 @@ -from typing import TypeVar, Any +import types +from typing import Generic, Any from unittest import TestCase -T = TypeVar("T") -S = TypeVar("S") - - -class Eqtest(TestCase): - +class BasicsTest(TestCase): def test_union(self): self.assertEqual(int | str, str | int) self.assertNotEqual(int | str, int) @@ -24,6 +20,26 @@ def test_any(self): self.assertEqual(Any, Any) self.assertNotEqual(list[int], list[Any]) - def test_typevar(self): + def test_typevar[T, S](self): self.assertEqual(T, T) self.assertNotEqual(T, S) + + def test_bases(self): + class X(list[str]): + pass + + self.assertEqual(types.get_original_bases(X)[0], list[str]) + self.assertNotEqual(types.get_original_bases(X)[0], list[int]) + self.assertNotEqual(types.get_original_bases(X)[0], list) + + def test_mro(self): + class A: + pass + + class B[T](A): + def some(val: T) -> T: + return val + + self.assertFalse(issubclass(A, Generic)) + self.assertTrue(issubclass(B, Generic)) + self.assertLess(B.__mro__.index(B), B.__mro__.index(Generic)) From 3086647797b3a73b2ed79b03d18bcb4967a72215 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:07:14 +0900 Subject: [PATCH 04/18] Add threading test --- tests/test_threading.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_threading.py diff --git a/tests/test_threading.py b/tests/test_threading.py new file mode 100644 index 0000000..e01df90 --- /dev/null +++ b/tests/test_threading.py @@ -0,0 +1,25 @@ +import concurrent.futures +from concurrent.futures import ThreadPoolExecutor +from unittest import TestCase +from reification import Reified + + +class ReifiedClass[T](Reified): + def some(val: T) -> T: + return val + + +class ThreadingTest(TestCase): + def test_safety(self, n: int = 200, workers: int = 1024): + def get_reified_type(param): + return ReifiedClass[param] + + with ThreadPoolExecutor(max_workers=workers) as executor: + for k in range(n): + with self.subTest(k=k): + literal_type = str(k) + fs = [executor.submit(get_reified_type, literal_type) for _ in range(workers)] + concurrent.futures.wait(fs) + t = get_reified_type(literal_type) + results = [f.result() == t for f in fs] + self.assertTrue(all(results)) From 12db0d72700fc30861366a9e8e2691297c6f564a Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:27:49 +0900 Subject: [PATCH 05/18] Update module root --- reification/__init__.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/reification/__init__.py b/reification/__init__.py index 506e96a..1fad359 100644 --- a/reification/__init__.py +++ b/reification/__init__.py @@ -1,20 +1,5 @@ -__version__ = "0.1.1" +__version__ = "0.2.0" -from .reify import reify -from .reific import Reific -from .reified import ReifiedType +from .core import Reified -__all__ = ["reify", "Reific", "ReifiedType"] - - - -#a: type[int] = bool -#b: type[bool] = int #NG - -e: type[bool] = bool - -#c: type[bool] = a # NG -d: type[int] = e #OK - -f: list[bool] = [] -m: list[int] = f +__all__ = ["Reified"] From 34c073d57eecd1ac422bdedd98b6b6d915b16cfc Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:16:50 +0900 Subject: [PATCH 06/18] Update mro test --- tests/test_type_basics.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_type_basics.py b/tests/test_type_basics.py index 8699b59..d0b6c90 100644 --- a/tests/test_type_basics.py +++ b/tests/test_type_basics.py @@ -40,6 +40,10 @@ class B[T](A): def some(val: T) -> T: return val + class C[T](B[T]): + pass + self.assertFalse(issubclass(A, Generic)) self.assertTrue(issubclass(B, Generic)) self.assertLess(B.__mro__.index(B), B.__mro__.index(Generic)) + self.assertLess(C.__mro__.index(B), C.__mro__.index(Generic)) From c17a305722af1de99a7b7011e91e6ae4abec33f5 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sat, 28 Oct 2023 21:36:35 +0900 Subject: [PATCH 07/18] Update core --- reification/core.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 reification/core.py diff --git a/reification/core.py b/reification/core.py new file mode 100644 index 0000000..2c4787a --- /dev/null +++ b/reification/core.py @@ -0,0 +1,16 @@ +from typing import Any +from .utils import get_reified_type, tuplize_class_getitem_params + + +class Reified: + targ: Any = type | Any + + type_args: tuple[type | Any, ...] = (Any,) + + # Return type should be inferred + def __class_getitem__(cls, params: Any): + param_tuple = tuplize_class_getitem_params(params) + rt = get_reified_type(cls, param_tuple) + rt.targ = params + rt.type_args = param_tuple + return rt From 99b8806ed28de77d099af417a333973da3eb1f55 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:03:04 +0900 Subject: [PATCH 08/18] Prohibit from instantiating directly --- reification/core.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/reification/core.py b/reification/core.py index 2c4787a..1215200 100644 --- a/reification/core.py +++ b/reification/core.py @@ -7,8 +7,18 @@ class Reified: type_args: tuple[type | Any, ...] = (Any,) + def __new__(cls, *args, **kwargs): + # Prohibit from instantiating directly + if cls is Reified: + raise RuntimeError("Cannot instantiate 'Reified' class directly.") + return super().__new__(cls, *args, **kwargs) + # Return type should be inferred def __class_getitem__(cls, params: Any): + # Prohibit from instantiating directly + if cls is Reified: + raise RuntimeError("Cannot instantiate 'Reified' class directly.") + # Returns a separated reified type param_tuple = tuplize_class_getitem_params(params) rt = get_reified_type(cls, param_tuple) rt.targ = params From ef08f8490c0d00ae34600af9e6b8eafb6896e3ff Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:04:57 +0900 Subject: [PATCH 09/18] Add tests/test_reified.py --- tests/test_reified.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_reified.py diff --git a/tests/test_reified.py b/tests/test_reified.py new file mode 100644 index 0000000..c82a965 --- /dev/null +++ b/tests/test_reified.py @@ -0,0 +1,17 @@ +from unittest import TestCase +from reification import Reified + + +class ReifiedSet[T](Reified, set[T]): + pass + + +class ReifiedTest(TestCase): + def test_mro(self): + self.assertIn(Reified, ReifiedSet[int].__mro__) + + def test_banned_instantiating(self): + with self.assertRaises(Exception): + Reified() + with self.assertRaises(Exception): + Reified[int]() From 78ca76048afe0e068bef6c3a899c7273bf95df48 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:09:51 +0900 Subject: [PATCH 10/18] Add stack test --- tests/test_reified_stack.py | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_reified_stack.py diff --git a/tests/test_reified_stack.py b/tests/test_reified_stack.py new file mode 100644 index 0000000..83f65b2 --- /dev/null +++ b/tests/test_reified_stack.py @@ -0,0 +1,40 @@ +from unittest import TestCase +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") + + +class ReifiedStackTest(TestCase): + def test_type_args(self): + stack = ReifiedStack[int]() + self.assertIs(stack.targ, int) + + def test_ok(self): + stack = ReifiedStack[int]() + stack.push(10) + stack.push(42) + self.assertEqual(stack.pop(), 42) + self.assertEqual(stack.pop(), 10) + + def test_fail(self): + stack = ReifiedStack[str]() + stack.push("spam") + with self.assertRaises(TypeError): + stack.push(100) From cc961df0cd73a04c9c531621410de86c82acaf31 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:30:51 +0900 Subject: [PATCH 11/18] Update tests --- tests/reific_stack.py | 8 --- tests/reified_types.py | 3 - tests/test_reific.py | 58 ------------------- tests/test_reific_stack.py | 0 tests/test_reified.py | 44 +++++++++++++++ tests/test_reified_dict.py | 103 ++++++++++++++++++++++++++++++++++ tests/test_reified_list.py | 109 ++++++++++++++++++++++++++++++++++++ tests/test_reified_stack.py | 6 ++ tests/test_reified_types.py | 5 -- 9 files changed, 262 insertions(+), 74 deletions(-) delete mode 100644 tests/reific_stack.py delete mode 100644 tests/reified_types.py delete mode 100644 tests/test_reific.py delete mode 100644 tests/test_reific_stack.py create mode 100644 tests/test_reified_dict.py create mode 100644 tests/test_reified_list.py delete mode 100644 tests/test_reified_types.py diff --git a/tests/reific_stack.py b/tests/reific_stack.py deleted file mode 100644 index 5490ae5..0000000 --- a/tests/reific_stack.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import TypeVar -from reification import Reific - -T = TypeVar("T") - -class Stack(Reific[T]): - pass - diff --git a/tests/reified_types.py b/tests/reified_types.py deleted file mode 100644 index fb77a39..0000000 --- a/tests/reified_types.py +++ /dev/null @@ -1,3 +0,0 @@ -from reification import reify - -reified_list = reify(list) diff --git a/tests/test_reific.py b/tests/test_reific.py deleted file mode 100644 index ef66f93..0000000 --- a/tests/test_reific.py +++ /dev/null @@ -1,58 +0,0 @@ -import itertools -from unittest import TestCase - -""" -from reific_stack import CheckedStack -from reific_dict import ReifiedDict - -class GetTypeTest(TestCase): - - - def test_stack(): - types = [int, float, str, bool, list, list[int], dict[int, list[str]]] - Stack[] - - def test_non_(): - a = Stack() - assertError() - - - def test_dict - - - def test_dict_non - - -class SubClassTest(TestCase): - - def test_list(self): - types = [int, str, float] - for t1, t2 in itertools.product(types, repeat=2): - with self.subTest(left=t1, right=t2): - l1 = Stack[t1] - l2 = Stack[t2] - print(l1, l2) - if t1 is t2: - self.assertTrue(issubclass(l1, l2)) - else: - self.assertFalse(issubclass(l1, l2)) - - def test_union - - def test_alias - - -class InstanceTest: - - def test_list(self): - i = Stack[int]() - f = Stack[float]() - self.assertInstance(i, Stack[int]) - self.assertInstance(i, Stack[int]) - isinstance(i, ) - - def test_in(): - i = Stack[int | str]() - f = Stack[float]() - -""" diff --git a/tests/test_reific_stack.py b/tests/test_reific_stack.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_reified.py b/tests/test_reified.py index c82a965..4ea9744 100644 --- a/tests/test_reified.py +++ b/tests/test_reified.py @@ -1,3 +1,4 @@ +import itertools from unittest import TestCase from reification import Reified @@ -6,6 +7,19 @@ class ReifiedSet[T](Reified, set[T]): pass +class ReifiedClass[T](Reified): + def some(val: T) -> T: + return val + + +class ReifiedList[T](Reified, list[T]): + pass + + +class ReifiedStrList[T](ReifiedClass[T], ReifiedList[str]): + pass + + class ReifiedTest(TestCase): def test_mro(self): self.assertIn(Reified, ReifiedSet[int].__mro__) @@ -15,3 +29,33 @@ def test_banned_instantiating(self): Reified() with self.assertRaises(Exception): Reified[int]() + + def test_subclass(self): + types = [ + int, + str, + float, + bool, + type, + list[int], + dict[int, str], + tuple[bool], + tuple[int], + ReifiedClass[int], + ReifiedClass[ReifiedClass[int]], + ReifiedClass[ReifiedClass[dict[str, float]]], + ] + for t1, t2 in itertools.product(types, repeat=2): + with self.subTest(left=t1, right=t2): + l1 = ReifiedStrList[t1] + l2 = ReifiedStrList[t2] + self.assertTrue(issubclass(l1, list)) + self.assertTrue(issubclass(l1, Reified)) + self.assertTrue(issubclass(l1, ReifiedList)) + self.assertTrue(issubclass(l1, ReifiedList[str])) + self.assertTrue(issubclass(l1, ReifiedClass)) + self.assertFalse(issubclass(l1, ReifiedClass[t1])) + if l1 == l2: + self.assertTrue(issubclass(l1, l2)) + else: + self.assertFalse(issubclass(l1, l2)) diff --git a/tests/test_reified_dict.py b/tests/test_reified_dict.py new file mode 100644 index 0000000..5affdf4 --- /dev/null +++ b/tests/test_reified_dict.py @@ -0,0 +1,103 @@ +import itertools +from unittest import TestCase +from reification import Reified + + +class ReifiedDict[T, S](Reified, dict[T, S]): + pass + + +class ReifiedDictSub[T](ReifiedDict[str, T]): + pass + + +class ReifiedDictTest(TestCase): + def test_type_args(self): + ReifiedFloatList = ReifiedDict[int, float] + self.assertEqual(ReifiedFloatList.targ, (int, float)) + self.assertEqual(ReifiedFloatList.type_args, (int, float)) + d = ReifiedFloatList([(1, 2.3), (2, 4.5)]) + self.assertEqual(d.targ, (int, float)) + self.assertEqual(d.type_args, (int, float)) + ReifiedListDict = ReifiedDictSub[list[int]] + self.assertEqual(ReifiedListDict.targ, list[int]) + self.assertEqual(ReifiedListDict.type_args, (list[int],)) + ds = ReifiedListDict() + self.assertEqual(ds.targ, list[int]) + self.assertEqual(ds.type_args, (list[int],)) + + def test_equivalence(self): + types = [ + int, + str, + float, + bool, + list[int], + dict[int, str], + tuple[bool], + tuple[int], + ReifiedDict[int], + type, + ] + for t1, t2, t3, t4 in itertools.product(types, repeat=4): + with self.subTest(left=(t1, t2), right=(t3, t4)): + d1 = ReifiedDict[t1, t2] + d2 = ReifiedDict[t3, t4] + if (t1, t2) == (t3, t4): + self.assertEqual(d1, d2) + else: + self.assertNotEqual(d1, d2) + + def test_instance(self): + types = [ + int, + str, + float, + bool, + type, + tuple[bool], + tuple[int], + list[int], + dict[int, str], + ReifiedDict[int], + ReifiedDict[ReifiedDict[int]], + ReifiedDict[ReifiedDict[dict[str, float]]], + ] + for t1, t2, t3, t4 in itertools.product(types, repeat=4): + with self.subTest(left=(t1, t2), right=(t3, t4)): + d = ReifiedDict[t1, t2]() + self.assertIsInstance(d, dict) + self.assertIsInstance(d, Reified) + self.assertIsInstance(d, ReifiedDict) + self.assertIsInstance(d, ReifiedDict[t1, t2]) + if (t1, t2) == (t3, t4): + self.assertIsInstance(d, ReifiedDict[t3, t4]) + else: + self.assertNotIsInstance(d, ReifiedDict[t3, t4]) + + def test_subclass(self): + types = [ + int, + str, + float, + bool, + type, + list[int], + dict[int, str], + tuple[bool], + tuple[int], + ReifiedDict[int], + ReifiedDict[ReifiedDict[int]], + ReifiedDict[ReifiedDict[dict[str, float]]], + ] + for t1, t2, t3, t4 in itertools.product(types, repeat=4): + with self.subTest(left=(t1, t2), right=(t3, t4)): + d1 = ReifiedDict[t1, t2] + d2 = ReifiedDict[t3, t4] + self.assertTrue(issubclass(d1, dict)) + self.assertTrue(issubclass(d1, Reified)) + self.assertTrue(issubclass(d1, ReifiedDict)) + if d1 == d2: + self.assertTrue(issubclass(d1, d2)) + else: + self.assertFalse(issubclass(d1, d2)) diff --git a/tests/test_reified_list.py b/tests/test_reified_list.py new file mode 100644 index 0000000..b61e67e --- /dev/null +++ b/tests/test_reified_list.py @@ -0,0 +1,109 @@ +import itertools +from unittest import TestCase +from reification import Reified + + +class ReifiedList[T](Reified, list[T]): + pass + + +class ReifiedListSub[T](ReifiedList[T]): + pass + + +class ReifiedListTest(TestCase): + def test_type_args(self): + ReifiedIntList = ReifiedList[int] + self.assertEqual(ReifiedIntList.targ, int) + self.assertEqual(ReifiedIntList.type_args, (int,)) + l = ReifiedIntList([1, 2, 3]) + self.assertEqual(l.targ, int) + self.assertEqual(l.type_args, (int,)) + ReifiedIntListSub = ReifiedListSub[int] + self.assertEqual(ReifiedIntListSub.targ, int) + self.assertEqual(ReifiedIntListSub.type_args, (int,)) + ls = ReifiedIntListSub() + self.assertEqual(ls.targ, int) + self.assertEqual(ls.type_args, (int,)) + + def test_typing(self): + l = ReifiedList[int]() + l.append(11) + n = l[0] + self.assertEqual(n + 2, 13) + + def test_equivalence(self): + types = [ + int, + str, + float, + bool, + list[int], + dict[int, str], + tuple[bool], + tuple[int], + ReifiedList[int], + type, + ] + for t1, t2 in itertools.product(types, repeat=2): + with self.subTest(left=t1, right=t2): + l1 = ReifiedList[t1] + l2 = ReifiedList[t2] + if t1 == t2: + self.assertEqual(l1, l2) + else: + self.assertNotEqual(l1, l2) + + def test_instance(self): + types = [ + int, + str, + float, + bool, + type, + tuple[bool], + tuple[int], + list[int], + dict[int, str], + ReifiedList[int], + ReifiedList[ReifiedList[int]], + ReifiedList[ReifiedList[dict[str, float]]], + ] + for t1, t2 in itertools.product(types, repeat=2): + with self.subTest(left=t1, right=t2): + l = ReifiedList[t1]() + self.assertIsInstance(l, list) + self.assertIsInstance(l, Reified) + self.assertIsInstance(l, ReifiedList) + self.assertIsInstance(l, ReifiedList[t1]) + if t1 == t2: + self.assertIsInstance(l, ReifiedList[t2]) + else: + self.assertNotIsInstance(l, ReifiedList[t2]) + + def test_subclass(self): + types = [ + int, + str, + float, + bool, + type, + list[int], + dict[int, str], + tuple[bool], + tuple[int], + ReifiedList[int], + ReifiedList[ReifiedList[int]], + ReifiedList[ReifiedList[dict[str, float]]], + ] + for t1, t2 in itertools.product(types, repeat=2): + with self.subTest(left=t1, right=t2): + l1 = ReifiedList[t1] + l2 = ReifiedList[t2] + self.assertTrue(issubclass(l1, list)) + self.assertTrue(issubclass(l1, Reified)) + self.assertTrue(issubclass(l1, ReifiedList)) + if l1 == l2: + self.assertTrue(issubclass(l1, l2)) + else: + self.assertFalse(issubclass(l1, l2)) diff --git a/tests/test_reified_stack.py b/tests/test_reified_stack.py index 83f65b2..1875e95 100644 --- a/tests/test_reified_stack.py +++ b/tests/test_reified_stack.py @@ -38,3 +38,9 @@ def test_fail(self): stack.push("spam") with self.assertRaises(TypeError): stack.push(100) + + def test_nested_type(self): + stack = ReifiedStack[ReifiedStack[int]]() + stack.push(ReifiedStack[int]()) + with self.assertRaises(TypeError): + stack.push(ReifiedStack[str]()) diff --git a/tests/test_reified_types.py b/tests/test_reified_types.py deleted file mode 100644 index de262f3..0000000 --- a/tests/test_reified_types.py +++ /dev/null @@ -1,5 +0,0 @@ -from reified_types import reified_list - - -a = reified_list[int]() - From 5eaf3392f013b6ca60d04fbf83cc8ad4f05e70c0 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:53:45 +0900 Subject: [PATCH 12/18] Update Makefile --- Makefile | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fdd3de9..d876a04 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build install preview publish clean test +.PHONY: build install devinstall preview publish clean format check test build: clean python3 -m build @@ -6,16 +6,26 @@ build: clean install: build pip3 install . +devinstall: build + pip3 install -e .[dev] + preview: build python3 -m twine upload --repository-url "https://test.pypi.org/legacy/" dist/* publish: build - python3 -m twine upload --repository "https://upload.pypi.org/legacy/" dist/* + python3 -m twine upload --repository-url "https://upload.pypi.org/legacy/" dist/* clean: python3 -c 'import shutil; shutil.rmtree("dist", ignore_errors=True)' python3 -c 'import shutil; shutil.rmtree("build", ignore_errors=True)' python3 -c 'import shutil; shutil.rmtree("reification.egg-info", ignore_errors=True)' + python3 -c 'import shutil; shutil.rmtree(".mypy_cache", ignore_errors=True)' + +format: + python3 -m black -l 200 reification tests + +check: + python3 -m mypy reification tests test: python3 -m unittest discover -v tests From 731d265f71ab786ed286bd1e98089391a313e64a Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sun, 29 Oct 2023 00:59:57 +0900 Subject: [PATCH 13/18] Rem --- reification/reific.py | 23 ----------------------- reification/reified.py | 18 ------------------ reification/reify.py | 24 ------------------------ 3 files changed, 65 deletions(-) delete mode 100644 reification/reific.py delete mode 100644 reification/reified.py delete mode 100644 reification/reify.py diff --git a/reification/reific.py b/reification/reific.py deleted file mode 100644 index ee37141..0000000 --- a/reification/reific.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Generic, TypeVarTuple, Any -from .utils import get_type, tuplize_class_getitem_params - - - - -Ts = TypeVarTuple("Ts") - - -class Reific(Generic[*Ts]): - - # TODO: Reified[Self] - def __class_getitem__(cls, params: Any) -> type[Self]: - - # - super().__class_getitem__(params) - - # - param_tuple = tuplize_class_getitem_params(params) - t = get_type(cls, param_tuple) - # - #t.key = - return t diff --git a/reification/reified.py b/reification/reified.py deleted file mode 100644 index b3efd0c..0000000 --- a/reification/reified.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Generic, TypeVar, Any -from abc import ABC, abstractmethod - - -T = TypeVar("T", covariant=True) - - -type[T & protocol] - - - -from typing import Protocol - -# Reified : T -class Reified: - #@abstractmethod - def type_args(cls) -> Any: - pass diff --git a/reification/reify.py b/reification/reify.py deleted file mode 100644 index 227b1b6..0000000 --- a/reification/reify.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import TypeVar, Any -from .utils import get_type, tuplize_class_getitem_params - -T = TypeVar("T") - -# TODO: Reified[T] -# Reified = type[T] & Reified -def reify(base_type: type[T]) -> type[T]: - #@cache しないといけない - - # Generic を継承する? - class _Reified(base_type): - - def __class_getitem__(cls, params: Any): - - if hasattr(super(), "__class_getitem__"): - super().__class_getitem__(params) - - - param_tuple = tuplize_class_getitem_params(params) - t = get_type(cls, param_tuple) - t.type_args = params - return t - return _Reified From 931b2804d1bdb462eac374b9d9de8d93285d7463 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Sun, 29 Oct 2023 14:43:51 +0900 Subject: [PATCH 14/18] Update pyproject --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f9cc09a..144800d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [project] name = "reification" -description = "Reified generics to get type parameters at runtime in Python" +description = "Reified generics in Python to get type parameters at runtime" keywords = ["typing", "generics", "parametric polymorphism", "reification", "type parameter", "reflection"] -requires-python = ">=3.11" +requires-python = ">=3.12" readme = "README.md" license = {text = "WTFPL"} maintainers = [ @@ -21,6 +21,9 @@ classifiers = [ ] dynamic = ["version"] +[project.optional-dependencies] +dev = ["pip", "setuptools", "build", "twine", "black", "mypy"] + [project.urls] homepage = "https://github.com/curegit/reification" repository = "https://github.com/curegit/reification.git" From 3ce8f955cf87beb71830cb925f0ce434d3d10648 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:06:50 +0900 Subject: [PATCH 15/18] Update README --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6c60885..223b60b 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,19 @@ Reified generics in Python to get type parameters at runtime ```py from reification import Reified -class ReifiedList[T](Reified, list): +class ReifiedList[T](Reified, list[T]): pass -xs = ReifiedList[int]([1, 2, 3]) -print(xs.reified_type) # +xs = ReifiedList[int](range(10)) +print(xs) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +print(xs.targ) # ``` ## Requirements Python >= 3.12 -This library is written in pure Python and any non-builtin modules are NOT required. +This library is written in pure Python and does NOT require any non-builtin modules. ## Install @@ -24,13 +25,59 @@ This library is written in pure Python and any non-builtin modules are NOT requi pip3 install reification ``` -## Usage +## API + +### `Reified` (class) + +`Reified` is a Mixin class designed to facilitate the creation of new types based on reified type parameters. + +You cannot instantiate this class directly. + +#### `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 values. +If 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 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 reify ->>> a = reify[list[int]]([1, 2, 3]) ->>> a.type -list[int] +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 ``` ```py From 994a62869e211ac254e7e0e2a10cb2fe20528a96 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:14:57 +0900 Subject: [PATCH 16/18] Update README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 223b60b..7e66b64 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,17 @@ 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 + ```py T = TypeVar("T") From 5615f71e247c834f371f7ddcda0e842313badc1f Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:30:49 +0900 Subject: [PATCH 17/18] Update --- README.md | 2 ++ reification/core.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e66b64..a8d01dd 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ pip3 install reification `Reified` is a Mixin class designed to facilitate the creation of new types based on reified type parameters. +This class is threadsafe so that inheriting classes can be used in multiple threads. + You cannot instantiate this class directly. #### `targ: type | tuple[type | Any, ...] | Any` (class property) diff --git a/reification/core.py b/reification/core.py index 1215200..59304a8 100644 --- a/reification/core.py +++ b/reification/core.py @@ -14,7 +14,7 @@ def __new__(cls, *args, **kwargs): return super().__new__(cls, *args, **kwargs) # Return type should be inferred - def __class_getitem__(cls, params: Any): + def __class_getitem__(cls, params: type | tuple[type | Any, ...] | Any): # Prohibit from instantiating directly if cls is Reified: raise RuntimeError("Cannot instantiate 'Reified' class directly.") From b0c7893167dc6dc1965df99fe2e7a169d9a1d6b9 Mon Sep 17 00:00:00 2001 From: curegit <37978051+curegit@users.noreply.github.com> Date: Fri, 3 Nov 2023 20:02:06 +0900 Subject: [PATCH 18/18] Update --- README.md | 118 +++++++++++++++++++++++++------------------ reification/utils.py | 46 ++++++----------- 2 files changed, 84 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index a8d01dd..2c146de 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ 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) # @@ -15,9 +17,9 @@ print(xs.targ) # ## Requirements -Python >= 3.12 +- Python >= 3.12 -This library is written in pure Python and does NOT require any non-builtin modules. +This library is written in pure Python and does not require any non-builtin modules. ## Install @@ -52,7 +54,7 @@ If no type arguments are given, it may contain single 'Any'. 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 +## Example Usage: Type Checked Generic Stack ```py from reification import Reified @@ -84,85 +86,101 @@ 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). +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. +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. +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 -```py -T = TypeVar("T") +With `Reified` generic types, type parameters are considered for understanding and respecting the typing semantics as much as possible. -class CheckedStack(Reific[T]): +Python's native `isinstance` function works seamlessly with reified generic types. -``` +In context of reified generics: ```py ->>> int_stack = CheckedStack[int]() ->>> isinstance(int_stack, CheckedStack[int]) +>>> isinstance(ReifiedList[int](), ReifiedList[int]) True ->>> isinstance(int_stack, CheckedStack[str]) -False ``` -subclass works as you expected. +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 ->>> issubclass(Reified[int], Reified[int]) ->>> issubclass(Reified, Reified[int]) ->>> issubclass(Reified[int], Reified) ->>> issubclass(Reified[str], Reified[int]) +>>> isinstance(ReifiedList[str](), ReifiedList[int]) +False ``` -# Reification (Python library) - -This value repr types. -Typ semantics are depends on each usage -## Install +This returns `False` because, while both the objects are instances of the `ReifiedList` class, their type parameters are different (string vs integer). -```sh -pip3 install reification -``` +### Type equivalence -## API +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, ...)`. -all public API is just below `reification` package +```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 +``` -### `refiy` (function) +### Subtyping -make defined normal generic class parameter-reified +The `Reified` Mixin supports nominal subtyping. -`type_args` : +Type `A` is a subtype of type `B` if `A == B` or `A` is directly derived from `B`. -#### example +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 ->>> from reification import reify ->>> xs = reify(list)[int]([1, 2, 3]) ->>> t, = xs.type_args ->>> t -int -``` - -``` -reify(list)[int] is reify(list)[] -reify(list)[] is reify(list)[] +>>> 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 ``` -### `Reific` (class) +#### Type Variance -create type parameter-reified generics class +`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 +``` -derives from base class -subtype nominaly, type eq when type parameter eq +## Tips -``` -MyStack[int] -``` +`typing_inspect` ## License diff --git a/reification/utils.py b/reification/utils.py index 00479d6..b7f9a3a 100644 --- a/reification/utils.py +++ b/reification/utils.py @@ -1,45 +1,31 @@ import types -from typing import TypeAlias, Any +from typing import Any +from threading import RLock -a: type = list -b: type[int] = bool -c: type[bool] = int -d: tuple[int, str] = (1, "") +_type_dict: dict[tuple[type, tuple[type | Any, ...]], type] = dict() -#a: TypeAlias = -#a: TypeAlias = -type_dict: dict[tuple[type, tuple[type | Any]], type] = dict() +_lock = RLock() -def get_type(base_cls: type[T], type_args: tuple[type | Any]) -> type[T]: - k = (base_cls, type_args) +def get_reified_type[T](base_cls: type[T], type_args: tuple[type | Any, ...]) -> type[T]: + key = (base_cls, type_args) + with _lock: + if key in _type_dict: + return _type_dict[key] + else: + new_type = clone_type(base_cls, type_args) + _type_dict[key] = new_type + return new_type - if k in type_dict: - return type_dict[k] - else: - t = new_type(base_cls, type_args) - type_dict[k] = t - return t - -from typing import Protocol -class RT(Protocol) - - -def new_type(cls: type[T], params: tuple) -> type[Reified, T]: - #class ntype(base_cls): - # typeargs = key - reified2 = types.new_class( - name=cls.__name__, - bases=(cls,), - exec_body=(lambda ns: ns) - ) +def clone_type[T](cls: type[T], type_args: tuple[type | Any, ...]) -> type[T]: + reified2 = types.new_class(name=cls.__name__ + str(type_args), bases=(cls,)) return reified2 -def tuplize_class_getitem_params(params: Any) -> tuple: +def tuplize_class_getitem_params(params: type | tuple[type | Any, ...] | Any) -> tuple[type | Any, ...]: if isinstance(params, tuple): return params else: