Is there ar preferable way to create type aliases for compound types with Python's typing module?

限于喜欢 提交于 2020-04-11 04:38:10

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!