Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
333 views
in Technique[技术] by (71.8m points)

python - Type hints: Is it a bad practice to alias primitive data types?

In Python documentation for typing & type hints we have the below example:

Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

Vector type alias clearly shows that type aliases are useful for simplifying complex type signatures.

However, what about aliasing primitive data types?

Let's contrast two basic examples of function signatures:

URL = str    

def process_url(url: URL) -> URL:
    pass

vs.

def process_url(url: str) -> str:
    pass

Version with type alias URL for primitive type str is:

  • self-documenting (among others, now I can skip documenting returned value, as it should be clearly an url),
  • resistant to type implementation change (I can switch URL to be Dict or namedtuple later on without changing functions signatures).

The problem is I cannot find anyone else following such practice. I am simply afraid that I am unintentionally abusing type hints to implement my own ideas instead of following their intended purpose.


Note from 2020-10

Python 3.9 introduces "flexible function and variable annotations", which allows to make annotations like:

def speed_1(distance: "feet", time: "seconds") -> "miles per hour":
    pass

def speed_2(
    distance: Annotated[float, "feet"], time: Annotated[float, "seconds"]
) -> Annotated[float, "miles per hour"]:
    pass

Which renders aliasing data types for documenting purposes rather redundant!

See:

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Using an alias to mark the meaning of a value can be misleading and dangerous. If only a subset of values are valid, a NewType should be used instead.

Recall that the use of a type alias declares two types to be equivalent to one another. Doing Alias = Original will make the static type checker treat Alias as being exactly equivalent to Original in all cases. This is useful when you want to simplify complex type signatures.

Simple aliasing works both ways: the alias URL = str means any URL is a str and also means any str is a URL – which is usually not correct: A URL is a special kind of str and not any can take its place. An alias URL = str is a too strong statement of equality, as it cannot express this distinction. In fact, any inspection that does not look at the source code does not see the distinction:

In [1]: URL = str
In [2]: def foo(bar: URL):
   ...:     pass
   ...:
In [3]: foo?
Signature: foo(bar: str)

Consider that you alias Celsius = float in one module, and Fahrenheit = float in another. This signals that it is valid to use Celsius as Fahrenheit, which is wrong.

Unless your types do cary separative meaning, you should just take a url: str. The name signifies the meaning, the type the valid values. That means that your type should be suitable to separate valid and invalid values!

Use aliases to shorten your hints, but use NewType to refine them.

Vector = List[float]        # alias shortens
URL = NewType("URL", str)   # new type separates

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...