Closed ilevkivskyi closed 8 years ago
But AnyStr
is a type variable. I don't think we want to default to something that has a type variable.
Now, if we had a generic class whose type variable has an upper bound (e.g. TypeVar('T', bound=C)
or value constraints (e.g. TypeVar('T', str, bytes)
), it's perhaps reasonable that the default is not exactly Any but that upper bound or the union of the constraints. And for these special cases Match etc. the same could apply. Except that a union may be awkward to deal with -- if someone wrote Match
out of laziness and then proceeded to use it as if they had written Match[str]
they currently can get away with this. I can't decide whether I like that strictness or not...
@gvanrossum Sorry, I was not precise, I meant exactly what you said. But now on second thought I also started to doubt whether we need such strictness. Maybe we should add a short note, like "A type checker could make exceptions from this rule for generics defined with constrained/bounded type variables"?
I agree with Guido that AnyStr
is not a good default.
Using some sort of an upper bound is not a good idea either, because it would result in wider/more restrictive types and may require code to use isinstance
tests to do anything useful with values of such types, which is the opposite of what Any
does, which is to make it more flexible and never require any type checks. The property that Any
behaves kind of like both the supertype and subtype of everything is important. I can give examples if you aren't convinced :-)
If we had the concept of an 'unsafe' union, we could use Match[UnsafeUnion[str, bytes]]
as the meaning of Match
. Unsafe unions have been discussed before, but the idea is that a type checker would allow an operation if it's supported by some union item type, whereas for normal unions it has to be supported for all types. Regular unions wouldn't really work either because it would actually be more restrictive than, say, Match[str]
.
[Here's some informal discussion that might make this clearer: For a value, a wider or larger type is more restrictive, because it supports fewer operations. For example, object
is the widest ordinary type, and an expression (not lvalue) with type object
only lets you do a few things with it such as str(x)
. For a function argument, a wider type is more general/permissive from the caller's point of view, and an object
argument accepts anything, and similarly for lvalues. So types act differently in these two contexts. Match
types can be used both for values and function arguments, and the aim is to make this type permissive in both contexts, similar to how typing.List
(List[Any]
) is permissive. By defaulting to re.Match[Any]
, it is permissive both in an argument/lvalue type context and a value context.]
@JukkaL Thanks for a detailed answer! Indeed, you convinced me that there is a flaw in may logic: I was not thinking about generic types in contravariant positions, and those are clearly important. Closing this issue.
Currently, PEP 484 says that bare
List
is equivalent toList[Any]
, etc. However, I think that this rule should be changed for type aliases likere.Match
,re.Pattern
, etc. For such cases barere.Match
should be equivalent tore.Match[AnyStr]
.@gvanrossum If you agree, then you can assign this issue to me.