But I didn´t find any reference of this aliases should/ are enforced in static analysis.
for instance i expect:
test: str = 'not_an_url'
smght = retry(test) # warning
test = 'not_an_url'
smght = retry(test) # maybe no warning based on --strict?
test: Url = 'not_an_url'
smght = retry(test) # no warning
Not sure if this is related with variance / contra-variance, but I think the usefulness of type alias is a bit diminished if they play no role at static analysis.
Is this the expected behavior? or does it exclusively depend on the choices of the static analyzer?
With type Url = str, Url apparently becomes a TypeAliasType. Introduced in PEP 695, though there isn’t much in the way of rationale for why it’s needed.
The PEP does specify that TypeAliasType is a runtime thing, so I guess that explains why mypy doesn’t see it.
A limitation of NewType is that it’s not actually a type at runtime.
Url = NewType("Url", str)
myurl = Url("discuss.python.org")
isinstance(myurl, Url) # TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
Sometimes that might be desirable, if one really only want the check to be static, but it’s worth keeping in mind.
Url = NewType("Url", str) Url is str => False
I think the table needs an update there haha, but then NewType is very similar to a class, which for me makes sense. And also (for me)the ‘type’ keyword should be the one doing that.
The only thing clear is that the zen of python is suffering with this topic haha,
NewType: The static type checker will treat the new type as if it were a subclass of the original type
So yes, NewType is what I’m looking for, a subclass with no/near zero runtime impact.
But then the question is why ‘type’ creates a TypeAliasType and not a NewType?
If you want to create a type that is effectively a subclass of an existing class from the perspective of a static type checker, that’s the job of NewType. That facility has existed since PEP 484, and it works well. There’s no need to create a new syntax for it.
The type syntax was created in PEP 695 as a way to create a type alias. This is typically used when you want to define a short and concise name for a longer, more complex type (such as a union of other types). It’s also useful for creating generic type aliases and recursive self-referential) aliases. The result of a type statement is intended to be used in type annotations, not as a runtime value.
These are two different mechanisms in the type system intended to address different use cases. @Pablo, for the problem you’re trying to solve, you should use NewType.
What is the benefit of a type Url = str alias over a Url = str alias?
The type statement unambiguously defines a type alias — a symbol that can be used in other type expressions (annotations).
The intent behind the statement Url = str is ambiguous because this syntax is used in Python for both defining a variable Url and for defining a type alias Url. Type checkers employ heuristics to distinguish between these two cases. These heuristic rules have never been spec’ed, which leads to inconsistencies between type checkers. This ambiguity even extends to the use of the resulting symbol, especially when generics are involved. Take for example MyList = list. If you later use MyList in a type annotation (x: MyList), should MyType be considered already specialized with an implicit type argument of Any, or should it be considered unspecialized, which makes x: MyList[int] legal? These ambiguities lead to problems for type checkers – and for code bases that are intended to work across type checkers.
This problem of ambiguity led to the introduction of the TypeAlias special form in PEP 613, which allows you to unambiguously indicate that your intent is to define a type alias rather than define a variable.
The type statement is an extension of the TypeAlias idea, but it accommodates the new type parameter syntax when defining generic type aliases. It also doesn’t require importing TypeAlias from typing, and it allows for recursive (self-referential) type aliases without the use of quotes.
In summary:
If your intent is to define a type alias that will be used in type annotations, I recommend using the type statement (or TypeAlias if your code must run on Python < 3.12).
If your intent is to define a variable that can be used within value expressions at runtime (such as a constructor call), then use the Url = str form.
If your intent is to define a “new class type” that acts as though it’s a subclass of another class for purposes of static typing, then use Url = NewType("Url", str).
Most languages that support static types clearly delineate between “value expressions” and “type expressions” and define separate syntax and semantic rules for each. In the early days of the Python type system, these concepts bled together in ways that cause many problems. We’ve been working to disentangle these concepts over the years, but this thread highlights the fact that these concepts is still unclear in the minds of many Python developers. I think we need to do a better job clarifying these concepts in the typing spec and related typing documentation.
Now I see that NewType behaves as a class for the static analysis while remaining low/no impact at runtime, that is what I was looking for. And also what I expect a type to do.
I guess the last question to clarify is why using the type keyword for an alias?
After this post I’m starting to think that these are very different concepts, so if i could, I would be tempted to use the keyword type for NewType and maybe, if needed using a new keyword alias or other for an alias.
Maybe the whole confusion is that, when I see NewType I naturally thing that is replaced by type as we already have changed Dict - dict, List - list etc in our code base.
During the design process for PEP 695, there was a healthy and open debate about the best keyword to use here. The word alias was considered, but we settled on type. This is the same keyword used in other languages for the same concept. There are obviously pros and cons to every choice.
Anything is possible if you can convince the typing community and the Typing Council to amend the typing spec, but I think it’s very unlikely that you would be able to make a convincing argument for such a change. First, it would be a breaking change, so it would break existing code bases that use type as it was spec’ed in PEP 695. Second, it would create ambiguity in a feature that was designed to eliminate ambiguity. So practically speaking, I think the answer to your question is “no, this isn’t possible”.
NewType is a very different concept than a type alias. I think it’s best for people to internalize that fact and use the construct that applies to their particular use cases.