问题
I have a function with one parameter, which should take an int
or a None
as argument. There are several ways to create a type alias for such a compound type:
# test.py
import typing
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
IntOrNone_2 = typing.Union[int, None]
def my_func1(xyz: IntOrNone_1):
return xyz
def my_func2(xyz: IntOrNone_2):
return xyz
my_func1(12)
my_func1(None)
my_func1(13.7)
my_func1('str')
my_func2(12)
my_func2(None)
my_func2(13.7)
my_func2('str')
Both methods do what I expect them to do, however, the correspondig mypy
erros differ slightly, but basically have the same meaning.
test.py:14: error: Value of type variable "IntOrNone_1" of "my_func1" cannot be "float"
test.py:15: error: Value of type variable "IntOrNone_1" of "my_func1" cannot be "str"
test.py:19: error: Argument 1 to "my_func2" has incompatible type "float"; expected "Optional[int]"
test.py:20: error: Argument 1 to "my_func2" has incompatible type "str"; expected "Optional[int]"
I tend to use the second approach, since it additionally reports which argument caused the error.
Are both methods indeed equivalent, as I suppose, or is one of them to be preferred?
回答1:
The two methods are very far from being equivalent. You should avoid thinking of TypeVars as being mere aliases -- rather, they're more special forms that you use when you want to make your functions generic.
It's easiest to explain what a "generic function" is with an example. Suppose you want to write a function that accepts some object (any object!) and return another object of the exact same type. How would you do this?
One method we could do is to try using object
:
def identity(x: object) -> object:
return x
This gets us close, since our identity
function can at least accept literally anything (since all types inherit from object
in Python). However, this solution is flawed: if we pass in an int, we get back out object
, which isn't what we want.
Rather, what we need is a way for the type checker to understand that there's a "relationship" between these two types. This is exactly where TypeVar
comes in handy:
T = TypeVar('T')
def identity(x: T) -> T:
return x
Our TypeVar 'T' in this case is acting as a "placeholder" that can be bound to any type we want. So if we do identity(3)
, the T
will be bound to int
-- so the type checker will therefore understand the return type must also be int
!
And if we use T
multiple times within our parameter type hints, the type checker will make sure that the types are the same each time.
So, what is the expression below doing?
IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
Well, it turns out that it can sometimes be useful to add constraints to our special placeholder type. For example, you've constrained IntOrNone_1
so that it can be bound to only int
or None
, and no other types.
And finally, to answer your last question: in the examples you've given, you should absolutely be using Union, not TypeVars.
Whether you use a Union or a type alias to a Union is really a matter of personal taste, but if you don't need this "placeholder" or "generic" behavior, you shouldn't be using TypeVars.
The mypy docs has a section on generics if you'd like to learn more about how to use TypeVars. It covers several things I skipped, including how to make generic classes (not just generic functions).
来源:https://stackoverflow.com/questions/55336690/is-there-ar-preferable-way-to-create-type-aliases-for-compound-types-with-python