Generic Type Parameters
Hello, Instead of writing 'class A(Generic[T]): ...', can we do something like rust lang, i.e., 'class A[T]: ...'? Rust uses <T> but in python we use [T]. This reflects the type annotation for the class nicely. For example, a: A[T]. It’s weird seeing the word Generic inside the class inheritance parentheses which should be reserved for abstract base classes (abc) or explicit protocol classes or superclasses in general (even with their generic type parameters. For example, class A[T](Sequence[T]). Rust also defines the generic type parameter for functions. “fn func<T>(a: A[T]) -> T {function body}”. In Python, we can do the following: def func[T](a: A[T]) -> T: … With the new proposed type hint for Callable, The type annotation for the above function will be .. func: [T](A[T]) -> T And this as well reflects nicely with the function signature. Now, suddenly, we don’t need to write 'T=TypeVar(“T”, bound=…,)' if the generic type is enclosed for the class and function scopes (local not global) as in rust. "class A[T]: ...” can be thought of as a syntactic sugar for "class A(Generic[TypeVar(“T”)]): … “ . If the generic type implements a protocol or inherits from a base class, then we can do something like the following: @dataclass class A[T, U: Container[T]]: attr1: T attr2: U def check_all(self, x: T, container: U) -> bool: return (self.attr1 in self.attr2) and (x in container) here the T has no bounds nor constraints. It can be anything, but the U has to implement the Container protocol of type T. Lets create a function and see how we are going to use the class A above.. def test_function[T, U: Container[T]](a: A[T, U], x: T, container: U) -> bool: return a.check_all(x, container) a1 = A(10, [1, 2, 10]) # Valid creation since [1, 2, 10] is a Container[int] a2 = A(10, [“aa", “bb", “cc"]) # Not Valid, attr1: int, but attr2 is not Container[int] a3 = A(10.0, {1.0, 2.0, 10.0}) # Valid since {1.0, 2.0, 10.0} is a Container[float] test_function(a1, 3, [1, 5, 3]) #Valid test_function(a1, 3, {1, 5, 3}) # Not Valid The last one will be valid if the function signature were like the following: def test_function[T, U: Container[T]](a: A[T, U], x: T, container: Container[T]) -> bool: … because the the container argument (set[int]) does not need to be the same as U in a1 (list[int]). In Rust, they use the keyword “where” if the generic type parameters are long. I don’t know how we can do something similar in Python. Maybe like this? def test_function[T, U, V]( a: A[T, U], x: T, container: U, y: V ) -> bool: where ( U: Container[T], V: Sized + RandomProtocol, #suppose RandomProtocol has “random" method ) print(f”variable y size is {len(y)} and the random numbers are {y.random()}") return a.check(x, container) #### The + sign is to bound V further. That’s how rust does it. Think of intersection (Sized & RandomProtocol) as opposed to union. It could support multiple inheritance. To completely mimic TypeVar, we need to think of the constraints *args and the covariant and contravariant parameters. Maybe something like the following format: T: Bound(s) = constraint1 | constraint2 ... # T is invariant, e.g., "AnyStr = str | byte" has no bounds/abc/protocols, it has only constraints T<: Bound(s) = constraint1 | constraint2 … # T is covariant T>: Bound(s) = constraint1 | constraint2 … # T is contravariant U: Bound(s) = default_generic_type For the default type, this example illustrates it: class AddProtocol[T=Self, U=Self](Protocol): def __add__(self, other: T) -> U: … class GreaterProtocol[T=Self](Protocol): def __gt__(self, other: T) -> bool: ... @dataclass class Test[U]: where U: AddProtocol + GreaterProtocol = int #here we didn’t need to write AddProtocol[V, W] or GreaterProtocol[V] because they take default parameter Self (which is U in this case) for the generic types attr: U Also the below function does not need to define the generic parameter U if int is wanted def func_generic[U: AddProtocol + CompareProtocol](test1: Test[U], test2: Test[U]) -> tuple[U, bool]: addition = test1.attr + test2.attr comparison = test1.attr > test2.attr return addition, comparison def func_int(test1: Test, test2: Test) -> tuple[int, bool]: … test_int_1 = Test(1) test_int_2 = Test(2) test_str_1 = Test(“hello ”) test_str_2 = Test(“world”) func_generic(test_int_1, test_int_2) # Valid func_int(test_int_1, test_int_2) # Valid func_generic(test_str_1, test_str_2) # Valid func_int(test_str_1, test_str_2) # Invalid
participants (1)
-
Abdulla Al Kathiri