4.Python类型检查

什么是类型检查,为什么需要检查?静态类型检查和运行时类型检查有什么区别?

Python是一门很强的动态类型的编程语言,对变量的定义极具灵活性。然而动态类型带给我们方便的同时,也会带来一些问题。

本文介绍python的类型检查的一些工具,让我们开发更方便,代码更友好。

1 工具

静态类型

  1. mypy
  2. pyre
  3. Pyright
  4. pytype
  5. pyanalyze

运行时类型检查/数据验证

  1. marshmallow
  2. pydantic
  3. typeguard
  4. typical
  5. pytypes

具体项目

  1. pydantic-django
  2. django-stubs
  3. typeddjango
  4. flask-pydantic
  5. flask-marshmallow
  6. fastapi(内建pydantic)

2 类型提示Type Hints

Type hints在Python3.5版本就已经被加入。 举个栗子,以下这个函数计算平均温度:

def daily_average(temperatures):
    return sum(temperatures) / len(temperatures)

当我们传入列表时:

average_temperature = daily_average([22.819.625.9])
print(average_temperature)  # => 22.76666666666667

运行正常,但当我们传入以时间戳为键、温度为值的字典时:

average_temperature = daily_average({159912590622.8159912570619.6159912600625.9})
print(average_temperature)  # => 1599125872.6666667

函数工作了,但是它计算的是时间戳的平均值。

为了避免这种情况,我们就需要通过对参数和返回值添加注释或注解达到类型提示:

from typing import List

def daily_average(temperatures: List[float]) -> float:
    return sum(temperatures) / len(temperatures)

加入这些限定之后,就能强制我们传递正确的参数了。typing还有其他很多类型能满足我们的需求。

3 运行时类型检查

pydantic

pydantic用于数据验证,当提供的数据匹配不上对应的类型时会抛出异常。

$ pip install pydantic

易于使用:

from datetime import date
from typing import List

from pydantic import BaseModel

class Song(BaseModel):
    id: int
    name: str
    release: date
    genres: List[str]

现在,当我们用有效数据初始化一首新歌时,一切正常:

song = Song(
    id=101,
    name='Bohemian Rhapsody',
    release='1975-10-31',
    genres=[
        'Hard Rock',
        'Progressive Rock'
    ]
)
print(song)
# id=101 name='Bohemian Rhapsody' release=datetime.date(1975, 10, 31)
# genres=['Hard Rock', 'Progressive Rock']

但是,当我们尝试使用无效数据('1975-31-31')初始化新的Song时,会引发ValidationError

song = Song(
    id=101,
    name='Bohemian Rhapsody',
    release='1975-31-31',
    genres=[
        'Hard Rock',
        'Progressive Rock'
    ]
)
print(song)
# pydantic.error_wrappers.ValidationError: 1 validation error for Song
# release
#   invalid date format (type=value_error.date)

使用pydantic,我们可以确保在我们的应用程序中仅使用与定义的类型匹配的数据。这不仅减少的错误,而且可以编写更少的测试。通过使用诸如pydantic之类的工具,我们不需要为用户发送完全错误的数据的情况编写测试。它由pydantic处理-引发ValidationError。 例如,FastAPI使用pydantic验证HTTP请求和响应主体:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item

这使我们对请求里的数据验证变得更简单,不用自己写代码单独对每个数据进行验证。

除了进行数据验证外,我们还可以添加自定义验证器以确保超出其类型的数据的正确性。为属性添加自定义验证非常容易。 例如,为了防止Song类中的genres重复,我们可以像这样添加验证:

from datetime import date
from typing import List

from pydantic import BaseModel, validator

class Song(BaseModel):
    id: int
    name: str
    release: date
    genres: List[str]

    @validator('genres')
    def no_duplicates_in_genre(cls, v):
        if len(set(v)) != len(v):
            raise ValueError(
                'No duplicates allowed in genre.'
            )
        return v

song = Song(
    id=101,
    name='Bohemian Rhapsody',
    release='1975-10-31',
    genres=[
        'Hard Rock',
        'Progressive Rock',
        'Progressive Rock',
    ]
)
print(song)
# pydantic.error_wrappers.ValidationError: 1 validation error for Song
# genre
#   No duplicates allowed in genre. (type=value_error)

通过validator装饰器自定义函数来验证我们的数据。

我们也可以在验证发生之前更改值。只要将pre = Truealways = True添加到validator装饰器:

@validator('genres', pre=True, always=True)

例如,我们可以将genres类型转换为小写,如下所示:

from datetime import date
from typing import List

from pydantic import BaseModel, validator


class Song(BaseModel):
    id: int
    name: str
    release: date
    genres: List[str]

    @validator('genres', pre=True, always=True)
    def to_lower_case(cls, v):
        return [genre.lower() for genre in v]

    @validator('genres')
    def no_duplicates_in_genre(cls, v):
        if len(set(v)) != len(v):
            raise ValueError(
                'No duplicates allowed in genre.'
            )
        return v

song = Song(
    id=101,
    name='Bohemian Rhapsody',
    release='1975-10-31',
    genres=[
        'Hard Rock',
        'PrOgReSsIvE ROCK',
        'Progressive Rock',
    ]
)
print(song)
# pydantic.error_wrappers.ValidationError: 1 validation error for Song
# genre
#   No duplicates allowed in genre. (type=value_error)

to_lower_casegenres列表中的每个元素都转换为小写。由于pre设置为True,因此在pydantic验证类型之前调用此方法。所有类型均转换为小写,然后使用no_duplicates_in_genre进行验证。

pydantic还提供了更严格的类型,例如PositiveIntEmailStr,以使我们的验证更加出色。 查看pydantic官网获取更多信息[1]

4 总结

当代码很少时,类型检查可能似乎是不必要的,但是代码越多,项目越大,代码检查就越重要。这是一层保护我们免于犯容易预防的错误的保护层。 类型提示尽管不是由解释程序强制执行的,但有助于更好地表达变量,函数或类的意图。大多数现代IDE和代码编辑器都提供插件,以根据类型提示将类型不匹配通知开发人员。尽管静态类型检查可以改善我们的代码,但是我们必须考虑到我们的软件正在与外部世界通信。因此,鼓励添加运行时类型检查器,例如pydanticmarshmallow。 它们有助于在最早的阶段验证用户输入并抛出错误。我们发现错误的速度越快,就越容易纠正它并继续进行下去。

更多文档请参考 www.testdriven.io[2]

参考资料

[1]

pydantic: https://pydantic-docs.helpmanual.io/

[2]

更多文档: https://testdriven.io/blog/python-type-checking/

 wechat
您的支持将鼓励我继续创作!
您的支持将鼓励我继续创作!
--------------本文结束,感谢您的阅读--------------