这篇教程Python写得很实用,希望能帮到您。 Python 想必大家都已经很熟悉了,甚至关于它有用或者无用的论点大家可能也已经看腻了。但是无论如何,它作为一个广受关注的语言还是有它独到之处的,今天我们就再展开聊聊 Python。 Python 是一门动态强类型语言 《流畅的 Python》一书中提到,如果一门语言很少隐式转换类型,说明它是强类型语言,例如 Java、C++ 和 Python 就是强类型语言。 
Python 的强类型体现 同时如果一门语言经常隐式转换类型,说明它是弱类型语言,PHP、JavaScript 和 Perl 是弱类型语言。 
动态弱类型语言:JavaScript 当然上面这种简单的示例对比,并不能确切的说 Python 是一门强类型语言,因为 Java 同样支持 integer 和 string 相加操作,且 Java 是强类型语言。因此《流畅的 Python》一书中还有关于静态类型和动态类型的定义:在编译时检查类型的语言是静态类型语言,在运行时检查类型的语言是动态类型语言。静态语言需要声明类型(有些现代语言使用类型推导避免部分类型声明)。 
综上所述,关于 Python 是动态强类型语言是比较显而易见没什么争议的。 Type Hints 初探 Python 在 PEP 484(Python Enhancement Proposals,Python 增强建议书)[https://www.python.org/dev/peps/pep-0484/]中提出了 Type Hints(类型注解)。进一步强化了 Python 是一门强类型语言的特性,它在 Python3.5 中第一次被引入。使用 Type Hints 可以让我们编写出带有类型的 Python 代码,看起来更加符合强类型语言风格。 这里定义了两个 greeting 函数: 普通的写法如下: name = "world"def greeting(name): return "Hello " + namegreeting(name) 加入了 Type Hints 的写法如下: name: str = "world"def greeting(name: str) -> str: return "Hello " + namegreeting(name) 以 PyCharm 为例,在编写代码的过程中 IDE 会根据函数的类型标注,对传递给函数的参数进行类型检查。如果发现实参类型与函数的形参类型标注不符就会有如下提示: 
常见数据结构的 Type Hints 写法 上面通过一个 greeting 函数展示了 Type Hints 的用法,接下来我们就 Python 常见数据结构的 Type Hints 写法进行更加深入的学习。 默认参数 Python 函数支持默认参数,以下是默认参数的 Type Hints 写法,只需要将类型写到变量和默认参数之间即可。 def greeting(name: str = "world") -> str: return "Hello " + namegreeting() 自定义类型 对于自定义类型,Type Hints 同样能够很好的支持。它的写法跟 Python 内置类型并无区别。 class Student(object): def __init__(self, name, age): self.name = name self.age = agedef student_to_string(s: Student) -> str: return f"student name: {s.name}, age: {s.age}."student_to_string(Student("Tim", 18)) 当类型标注为自定义类型时,IDE 也能够对类型进行检查。 
容器类型 当我们要给内置容器类型添加类型标注时,由于类型注解运算符 [] 在 Python 中代表切片操作,因此会引发语法错误。所以不能直接使用内置容器类型当作注解,需要从 typing 模块中导入对应的容器类型注解(通常为内置类型的首字母大写形式)。 from typing import List, Tuple, Dictl: List[int] = [1, 2, 3]t: Tuple[str, ...] = ("a", "b")d: Dict[str, int] = { "a": 1, "b": 2,} 不过 PEP 585[https://www.python.org/dev/peps/pep-0585/]的出现解决了这个问题,我们可以直接使用 Python 的内置类型,而不会出现语法错误。 l: list[int] = [1, 2, 3]t: tuple[str, ...] = ("a", "b")d: dict[str, int] = { "a": 1, "b": 2,} 类型别名 有些复杂的嵌套类型写起来很长,如果出现重复,就会很痛苦,代码也会不够整洁。 config: list[tuple[str, int], dict[str, str]] = [ ("127.0.0.1", 8080), { "MYSQL_DB": "db", "MYSQL_USER": "user", "MYSQL_PASS": "pass", "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "3306", },]def start_server(config: list[tuple[str, int], dict[str, str]]) -> None: ...start_server(config) 此时可以通过给类型起别名的方式来解决,类似变量命名。 Config = list[tuple[str, int], dict[str, str]]config: Config = [ ("127.0.0.1", 8080), { "MYSQL_DB": "db", "MYSQL_USER": "user", "MYSQL_PASS": "pass", "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "3306", },]def start_server(config: Config) -> None: ...start_server(config) 这样代码看起来就舒服多了。 可变参数 Python 函数一个非常灵活的地方就是支持可变参数,Type Hints 同样支持可变参数的类型标注。 def foo(*args: str, **kwargs: int) -> None: ...foo("a", "b", 1, x=2, y="c") IDE 仍能够检查出来。 
泛型 使用动态语言少不了泛型的支持,Type Hints 针对泛型也提供了多种解决方案。 TypeVar 使用 TypeVar 可以接收任意类型。 from typing import TypeVarT = TypeVar("T")def foo(*args: T, **kwargs: T) -> None: ...foo("a", "b", 1, x=2, y="c") Union 如果不想使用泛型,只想使用几种指定的类型,那么可以使用 Union 来做。比如定义 concat 函数只想接收 str 或 bytes 类型。 from typing import UnionT = Union[str, bytes]def concat(s1: T, s2: T) -> T: return s1 + s2concat("hello", "world")concat(b"hello", b"world")concat("hello", b"world")concat(b"hello", "world") IDE 的检查提示如下图: 
TypeVar 和 Union 区别 TypeVar 不只可以接收泛型,它也可以像 Union 一样使用,只需要在实例化时将想要指定的类型范围当作参数依次传进来来即可。跟 Union 不同的是,使用 TypeVar 声明的函数,多参数类型必须相同,而 Union 不做限制。 from typing import TypeVarT = TypeVar("T", str, bytes)def concat(s1: T, s2: T) -> T: return s1 + s2concat("hello", "world")concat(b"hello", b"world")concat("hello", b"world") 以下是使用 TypeVar 做限定类型时的 IDE 提示: 
Optional Type Hints 提供了 Optional 来作为 Union[X, None] 的简写形式,表示被标注的参数要么为 X 类型,要么为 None,Optional[X] 等价于 Union[X, None]。 from typing import Optional, Union# None => type(None)def foo(arg: Union[int, None] = None) -> None: ...def foo(arg: Optional[int] = None) -> None: ... Any Any 是一种特殊的类型,可以代表所有类型。未指定返回值与参数类型的函数,都隐式地默认使用 Any,所以以下两个 greeting 函数写法等价: from typing import Optional, Union# None => type(None)def foo(arg: Union[int, None] = None) -> None: ...def foo(arg: Optional[int] = None) -> None: ... 当我们既想使用 Type Hints 来实现静态类型的写法,也不想失去动态语言特有的灵活性时,即可使用 Any。 Any 类型值赋给更精确的类型时,不执行类型检查,如下代码 IDE 并不会有错误提示: from typing import Anya: Any = Nonea = [] # 动态语言特性a = 2s: str = ''s = a # Any 类型值赋给更精确的类型 可调用对象(函数、类等) Python 中的任何可调用类型都可以使用 Callable 进行标注。如下代码标注中 Callable[[int], str],[int] 表示可调用类型的参数列表,str 表示返回值。 from typing import Callabledef int_to_str(i: int) -> str: return str(i)def f(fn: Callable[[int], str], i: int) -> str: return fn(i)f(int_to_str, 2) 自引用 当我们需要定义树型结构时,往往需要自引用。当执行到 __init__ 方法时 Tree 类型还没有生成,所以不能像使用 str 这种内置类型一样直接进行标注,需要采用字符串形式“Tree”来对未生成的对象进行引用。 class Tree(object): def __init__(self, left: "Tree" = None, right: "Tree" = None): self.left = left self.right = righttree1 = Tree(Tree(), Tree()) IDE 同样能够对自引用类型进行检查。 
此形式不仅能够用于自引用,前置引用同样适用。 鸭子类型 Python 一个显著的特点是其对鸭子类型的大量应用,Type Hints 提供了 Protocol 来对鸭子类型进行支持。定义类时只需要继承 Protocol 就可以声明一个接口类型,当遇到接口类型的注解时,只要接收到的对象实现了接口类型的所有方法,即可通过类型注解的检查,IDE 便不会报错。这里的 Stream 无需显式继承 Interface 类,只需要实现了 close 方法即可。 from typing import Protocolclass Interface(Protocol): def close(self) -> None: ...# class Stream(Interface):class Stream: def close(self) -> None: ...def close_resource(r: Interface) -> None: r.close()f = open("a.txt")close_resource(f)s: Stream = Stream()close_resource(s) 由于内置的 open 函数返回的文件对象和 Stream 对象都实现了 close 方法,所以能够通过 Type Hints 的检查,而字符串“s”并没有实现 close 方法,所以 IDE 会提示类型错误。 
Type Hints 的其他写法 实际上 Type Hints 不只有一种写法,Python 为了兼容不同人的喜好和老代码的迁移还实现了另外两种写法。 使用注释编写 来看一个 tornado 框架的例子(tornado/web.py)。适用于在已有的项目上做修改,代码已经写好了,后期需要增加类型标注。 
使用单独文件编写(.pyi) 可以在源代码相同的目录下新建一个与 .py 同名的 .pyi 文件,IDE 同样能够自动做类型检查。这么做的优点是可以对原来的代码不做任何改动,完全解耦。缺点是相当于要同时维护两份代码。 
Type Hints 实践 基本上,日常编码中常用的 Type Hints 写法都已经介绍给大家了,下面就让我们一起来看看如何在实际编码中中应用 Type Hints。 dataclass Python的Pillow库进行图像文件处理(图文详解) Python基础之输入,输出与高阶赋值详解 |