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 









_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/MBRGJJ3Z7YXQAD4WEKWHNVN7DIM4GTXG/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to