将动态的python项目迁移到静态

举报
码乐 发表于 2024/01/02 09:57:54 2024/01/02
【摘要】 1 mypy 类型检查Mypy 是 Python 的可选静态类型检查器,旨在结合动态(或“鸭子”)类型和静态类型的优点。它 将 Python 的表达能力和便利性与强大的类型系统和编译时类型检查相结合。Mypy 类型检查标准 Python 程序;使用任何 Python VM 运行它们,基本上没有运行时开销。python的类型系统:将现有代码迁移到静态类型,一次一个函数。您可以在程序、模块或表...

1 mypy 类型检查

Mypy 是 Python 的可选静态类型检查器,旨在结合动态(或“鸭子”)类型和静态类型的优点。它 将 Python 的表达能力和便利性与强大的类型系统和编译时类型检查相结合。Mypy 类型检查标准 Python 程序;使用任何 Python VM 运行它们,基本上没有运行时开销。

python的类型系统:

将现有代码迁移到静态类型,一次一个函数。您可以在程序、模块或表达式中自由混合静态类型和动态类型。无需放弃动态类型——在有意义的时候使用静态类型。通常只需添加函数签名即可为您提供静态类型的代码。Mypy 可以推断其他变量的类型。

2 类型检测

编译时类型检查有不少优点,尤其在大型项目中。 mypy支持对python项目做如下操作:

  • 静态类型使得通过更少的调试更容易找到错误。
  • 通常更容易维护。
  • 类型声明充当机器检查文档。静态类型使您的代码更易于理解和修改,而不会引入错误。
  • 将程序从动态类型迁移到静态 类型
  • 您可以使用动态类型开发程序并在代码成熟后添加静态类型,或者将现有的 Python 代码迁移到静态类型。

简单例子:一个hello word 程序,展示mypy如何使用的

        mypy main.py --python-version 3.6

        mypy main.py

        (venvbackend) # mypy resource.py  --python-version 3.5
                resource.py:15: error: Variable annotation syntax is only supported in Python 3.6 and greater
                Found 1 error in 1 file (errors prevented further checking)
                (venvbackend) root@fk:/data/code/hello_world# mypy resource.py  --python-version 3.6
                resource.py:30: note: Revealed type is "builtins.int"
                (venvbackend) root@fk:/data/code/hello_world# cat resource.py
                from typing import Dict, Optional

代码如下:

	## python3.x

	def square(x:float, y:float) -> float:
	    return x * y

	# typing module was added in pyton3.5

	def log(s:str, *, filename:Optional[str] = None) -> None:
	    ...

	# add in python3.6
	x: int = 5

	from typing import NamedTuple

	class NT(NamedTuple):
	    x: int
	    y: int

	## added in python3.9

	# old way
	def print_items(dct:Dict[str, str]) -> None:...

	# def print_items[dct:dict[str,str]) -> None: ...

	reveal_type(NT(1,2).y) # mypy 检测提示不匹配的地方 resource.py:30: note: Revealed type is "builtins.int"

	import dataclasses

	@dataclasses.dataclass
	class Tiem(object):
	    a: str
	    b: int
	reveal_type(Tiem("n",12).a)
  • 统计字典中词频

在此示例中,我们为变量 d 添加了一个显式类型声明,因为它在局部上下文中并不明显。

Mypy检查动态类型字典

    # Display the frequencies of words in a file.
	import sys
	import re

	if not sys.argv[1:]:
	    raise RuntimeError('Usage: wordfreq FILE')

	d = {}

	with open(sys.argv[1]) as f:
	    for s in f:
	        for word in re.sub('\W', ' ', s).split():
	            d[word] = d.get(word, 0) + 1

	# Use list comprehension
	l = [(freq, word) for word, freq in d.items()]

	for freq, word in sorted(l):
	    print('%-6d %s' % (freq, word))

Mypy检查静态类型字典
如下程序,显示一个文件中的词频

	import sys
	import re
	from typing import Dict

	if not sys.argv[1:]:
	    raise RuntimeError('Usage: wordfreq FILE')

	d = {}  # type: Dict[str, int]

	with open(sys.argv[1]) as f:
	    for s in f:
	        for word in re.sub('\W', ' ', s).split():
	            d[word] = d.get(word, 0) + 1

	l = [(freq, word) for word, freq in d.items()]

	for freq, word in sorted(l):
	    print('%-6d %s' % (freq, word))
  • 简单类型

在本例中,我们选择使用整数来表示余额。例如,这在游戏中会很好,但在其他应用程序中,不同的类型会更有意义。

Mypy检查带有动态类型的类型

   class BankAccount:
	    def __init__(self, initial_balance=0):
	        self.balance = initial_balance
	    def deposit(self, amount):
	        self.balance += amount
	    def withdraw(self, amount):
	        self.balance -= amount
	    def overdrawn(self):
	        return self.balance < 0

	my_account = BankAccount(15)
	my_account.withdraw(5)
	print(my_account.balance)

Mypy检查带有静态类型的类

   class BankAccount:
	    def __init__(self, initial_balance: int = 0) -> None:
	        self.balance = initial_balance
	    def deposit(self, amount: int) -> None:
	        self.balance += amount
	    def withdraw(self, amount: int) -> None:
	        self.balance -= amount
	    def overdrawn(self) -> bool:
	        return self.balance < 0

	my_account = BankAccount(15)
	my_account.withdraw(5)
	print(my_account.balance)
  • 带生成器的素数筛

Mypy 检查带有动态类型,一个从2到无穷大的素数筛生产器。 可以永远生成素数

   import itertools
	def iter_primes():
	     
	     numbers = itertools.count(2)

	     while True:
	         # Get the first number from the iterator
	         # (always a prime)
	         prime = next(numbers)
	         yield prime

	         # This code iteratively builds up a chain
	         # of filters...
	         numbers = filter(prime.__rmod__, numbers)

	for p in iter_primes():
	    if p > 1000:
	        break
	    print(p)

静态类型的 Mypy检查支持
静态类型的一个从2到无穷大的素数筛生产器。

    import itertools
	from typing import Iterator

	def iter_primes() -> Iterator[int]: 
	     numbers = itertools.count(2)

	     while True:
	         
	         prime = next(numbers)
	         yield prime

	         numbers = filter(prime.__rmod__, numbers)

	for p in iter_primes():
	    if p > 1000:
	        break
	    print(p)

3 检查泛型:在python中 使用 mypy

  • 泛型

先介绍如何定义自己的泛型类,这些泛型类采用一个或多个类型参数,类似于内置类型,例如list[X]. 用户定义的泛型是一个中等高级的特性,你可以在不使用它们的情况下走得更远——请随意跳过本节,稍后再回来。

  • 定义泛型类

内置集合类是泛型类。泛型有一个或多个类型参数,可以是任意类型。例如,具有类型参数and ,并且具有类型参数。

dict[int, str]intstrlist[int]int

程序还可以定义新的通用类。这是一个非常简单的泛型类,代表一个堆栈:

以下堆栈类创建一个类型为 T 的空列表

   from typing import TypeVar, Generic

	T = TypeVar('T')

	class Stack(Generic[T]):
	    def __init__(self) -> None:
	        
	        self.items: list[T] = []

	    def push(self, item: T) -> None:
	        self.items.append(item)

	    def pop(self) -> T:
	        return self.items.pop()

	    def empty(self) -> bool:
	        return not self.items

该类Stack可用于表示任何类型的堆栈: Stack[int]、等。Stack[tuple[int, str]]

使用Stack类似于内置容器类型:

创建一个空堆栈实例,最报错 类型错误

	stack = Stack[int]()
	stack.push(2)
	stack.pop()
	stack.push('x')        # 类型错误

类型推断也适用于用户定义的泛型类型: Stack[int] 的 参数类型推断

		def process(stack: Stack[int]) -> None: ...

		process(Stack())   
  • 内部通用类

    您可能想知道在运行Stack时索引时会发生什么 。
    Stack索引返回一个通用别名 , 它在实例化Stack时返回原始类的实例:

     print(Stack)
         __main__.Stack
    
     print(Stack[int])
    
         __main__.Stack[int]
    
     print(Stack[int]().__class__)
    
         __main__.Stack
    

    泛型别名可以被实例化或子类化,类似于真实的类,但上面的例子说明了类型变量在运行时被擦除。

    泛型Stack实例只是普通的 Python 对象,除了重载索引运算符的元类之外,它们没有额外的运行时开销或由于泛型而产生的魔力。

    需要注意,在 Python 3.8 及更低版本中,内置类型 list和dict其他类型不支持索引。
    这就是在模块中使用别名List等 的原因 。

    索引这些别名会为您提供一个通用别名,类似于通过在更新的 Python 版本中直接索引目标类而构造的通用别名:Dicttyping

    3.8版本及以下版本如下:

     from typing import List
     List[int]
     typing.List[int]
    

    请注意,中的通用别名typing不支持构造实例:

     from typing import List
    
     List[int]()
     Traceback (most recent call last):
     
     ...
     TypeError: Type List cannot be instantiated; use list() instead
    

    用户定义的泛型类typing 可以用作另一个类的基类,包括泛型和非泛型。例如:

     from typing import Generic, TypeVar, Mapping, Iterator
    
     KT = TypeVar('KT')
     VT = TypeVar('VT')
    
     class MyMap(Mapping[KT, VT]):  # This is a generic subclass of Mapping
         def __getitem__(self, k: KT) -> VT:
             ...  # Implementations omitted
         def __iter__(self) -> Iterator[KT]:
             ...
         def __len__(self) -> int:
             ...
    
     items: MyMap[str, int]  # Okay
    
     class StrDict(dict[str, str]):  # This is a non-generic subclass of dict
         def __str__(self) -> str:
             return f'StrDict({super().__str__()})'
    
     data: StrDict[int, int]  # Error! StrDict is not generic
     data2: StrDict  # OK
    
     class Receiver(Generic[T]):
         def accept(self, value: T) -> None:
             ...
    
     class AdvancedReceiver(Receiver[T]):
         ...
    

    如果您希望 mypy 将用户定义的类视为映射(以及 Sequence序列等),则必须添加显式基类。

    这是因为 mypy 不对 这些 ABC 使用结构子类型Iterable,这与使用结构子类型的更简单的协议不同。

    Generic如果有其他包含类型变量的基类,则可以从基类中省略,例如 在上面的示例中。

    如果你包含在基础中,那么它应该列出其他基础中存在的所有类型变量(或者更多,如果需要的话)。
    类型变量的顺序由以下规则定义:Mapping[KT, VT]Generic[…]

    如果Generic[…]存在,则变量的顺序总是由它们在Generic[…] 中的顺序决定。

    如果没有,则所有类型变量都按字典顺序收集(即按第一次出现)。

    类型别名不定义新类型。对于泛型类型别名,这意味着用于别名定义的类型变量的变化不适用于别名。
    参数化的通用别名被简单地视为原始类型,并替换了相应的类型变量。

3 小结

各语言发展最后都越来越相似。

参考文档

https://mypy.readthedocs.io/en/stable/

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。