说明
python类型说明,基于python3.13
type alias
type Address = tuple[str, int]对于3.12之前的版本可以省略type
NewType
from typing import NewType
UserId = NewType('UserId', int)类型名要和name参数一致
运行时会把实际的类型转换成指定的类型,比如上面的UserId本质就是int
# 运行时NewType会被当作函数调用,直接返回参数
foo = UserId(1) + UserId(2) # 3Note
NewType出来的类型,还可以继续用于NewType
NewType和TypeAlias的区别: NewType是新的类型,和原类型不兼容,而TypeAlias是别名,和原类型完全等价。
# type MyInt = int
MyInt = NewType('MyInt', int)
a = 1
b: MyInt
b = a # errorFunction
or callable object
from collections.abc import Callable
def foo(x: int, fn: Callable[[int], int]) -> int:
return fn(x)用法:
Callable[[<arg type>,...] | ..., <return type>]
...表示可以接受任意参数
还可以借助Protocol类来定义:
类的__call__方法就是它的调用方法,所以定义好它的签名即可。
from collections.abc import Protocol
class Combiner(Protocol):
def __call__(self, *vals: bytes,maxlen: int | None = None) -> list[bytes]: ...类型操作
高阶函数的参数是函数,在处理参数函数时会涉及类型推断和类型操作。
所谓类型操作就是把类型当作变量一样处理,Typescript中有很多这样的操作,如keyof、Partial、Record等。
类型变量使用[]符号,类似函数的()。
Note
类型操作实际是给编译器(或者说Lint之类的工具)看的,运行时会被忽略,这和TS一样。
常用的:
-
ParamSpec:捕获函数参数签名,便于后续对于捕获到的参数签名进行操作参数规范只能用于
Concatenate和Callable的第一个参数。from typing import Callable, ParamSpec, TypeVar # P = ParamSpec('P') # 表示"任意参数组合" # R = TypeVar('R') # 类型变量,泛型也可以看作类型变量 # 也可以使用[**P,R]来表示上面的含义,也就是不需要提前定义了 # P捕获了func的参数签名,后面就可以通过P.args和P.kwargs获取到参数的类型 def decorator[**P,R](func: Callable[P, R]) -> Callable[P, R]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: print("Before call") return func(*args, **kwargs) return wrapper上面的例子中参数规范P会根据参数推断出来,实际上P也能直接传入实参,使用一个列表(不是tuple)
[<type>,...]表示类型实参,本质就是Callable[[<type>,...], <return_type>]中的参数类型表示。class Zoo[**P,R]: pass print(Zoo[[int,str],int]) # P变成实参时,表示的就是函数参数类型的规范,[<type>,...]虽然可以,但是完全不建议,参数规范的目的还是捕获参数签名,也就是推断出来的,普通的泛型参数也一样。
P,泛型无法简单的替代,因为泛型实际上只表示一个类型这里的
-
Concatenate: 用于表示在现有参数基础上添加或删除参数Concatenate[str, P] # 可以添加str参数
更多的类型操作见”特殊类型”,“特殊forms”一节。
泛型
类似其他语言的泛型:
# T可以是任意类型
def foo[T](x: T) -> T:可以给泛型添加bound或constraint:
bound: class Foo[T: str],T <: str,为str的子类
constraint: class Foo[T: (int, str)],T 必须是int或str
上面是隐含的定义了一个TypeVar T, 这个T只在函数内部使用,在外部无法访问。
如果想复用T,可以使用TypeVar来手动定义:
from typing import TypeVar
T = TypeVar('T')
T = TypeVar('T',bound=str) # 定义bound
T = TypeVar('T',int,str) # 定义constraintNote
bound和constraint不能同时使用
不管隐式还是显示,都需要先定义后使用。 比如:
def foo(x: T) -> T: # error,T没有定义
def foo[T](x: T) -> T: # ok
T = TypeVar('T')
def foo(x: T) -> T: # okTypeVar在定义类型变量时,还可以指定variance逻辑:
invariant: 不变性,默认行为covariant: 协变性contravariant: 逆变性
这个是针对关联泛型的容器类型的,举例说明:
from typing import TypeVar, Generic
T = TypeVar('T', covariant=True)
class Animal:
pass
class Dog(Animal):
pass
class AnimalFeeder[T]:
passT 为invariant时:
Dog <: Animal
AnimalFeeder[Dog] <: AnimalFeeder[Animal] # error,容器之间没有继承关系T 为covariant时:
Dog <: Animal
AnimalFeeder[Dog] <: AnimalFeeder[Animal] # correct,容器和泛型存在同步关系T为contravariant时:
Dog <: Animal
AnimalFeeder[Animal] <: AnimalFeeder[Dog] # correct, 容器和泛型存在相反的关系Caution
比如pyright在类型检查时,会判定这个泛型有没有真实在使用,如果没有,可能将variance退回到默认值。
collections类型
本质是collections中元素类型。
-
list[int] -
dict[str, int] -
Mapping[str, int],抽象类型,比上面的定义更广泛,大部分时候2者可以互换 -
tuple[int, ...]元组的元素个数和类型不定,所以定义类型时需要明确类型个数,否则元素个数和类型个数不一致,无法通过检查 如果tuple中的元素类型一致,可以使用tuple[int, ...]表示,这样个数就不需要明确定义了tuple[()]表示空元组,只能被赋值为()tuple和tuple[Any, ...]等价(万恶的Any又来了)x: tuple[int] = (1, 2, 3) # error,有3个元素,但是只有一个类型 x: tuple[int,...] = (1, 2, 3) # correct
NOTE
list[T]如果需要协变,但是又不想T是协变的,可以使用Sequence[T], 这样就可以传入list[? extends T]
class type
2层意思:
- class是类型,常规理解
- class是对象,那class的类型呢?就是
type[<class>]class Foo: pass class Bar(Foo): pass c: type[Foo] c = Foo # correct c = Bar # correct
基本类型也是class,也可以当作对象使用:
c: type[int]
c = int # correct
a = 1
c = type(a) # correct,注意[]和(),这里是获取变量a的类型,推断为inttype可以接收class,Any,type变量,以及他们的联合作为参数。
泛型class
class Foo[T]:
def __init__(self, x: T) -> None:
self.x = x3.11及之前的版本:
from typing import TypeVar, Generic
T = TypeVar('T')
class Foo(Generic[T]): # 实际上泛型class 隐式的继承了Generic
def __init__(self, x: T) -> None:
self.x = x其他特性:
-
可以定义多个类型变量,即多个泛型参数,
[T,R] -
泛型类不指定泛型参数时,默认为
Any -
type定义alias时可以使用泛型参数,type Vet[T] = Iterable[tuple[T,T]] -
泛型参数和参数规范可以同时使用,
class Z[**P,R]class Zoo[**P,R]: def __init__(self, func: Callable[P, R]) -> None: self.func = func def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: print("Before call") return self.func(*args, **kwargs) def my_lambda(a: int, b: str) -> int: return a + len(b) # 都是合理的 zoo: Zoo[[int, str], int] = Zoo(my_lambda) zoo1 = Zoo(my_lambda) zoo2 = Zoo[[ int,str ],int](my_lambda)Important
所有的泛型参数,包括参数规范都不建议明确类型实参(zoo,zoo2),他们可以根据实际内容推断出来,而且有时候类型本身很难被表达出来,也容易出错。
Any
基本和TS中的any一样,可以赋值给任意类型,任意类型也可以赋值给它。
即Any是任意类型的父类型,也是任意类型的子类型。
Note
object是基类,可以被赋值为任意类型。
Any和object最大的区别:
使用object表示一个具体的类型,任何对象都可以赋值给它,而Any表示这是一个动态类型。
子类的定义
强类型系统中,比如java,c++中,子类必须显示继承父类,才能说这2个类型有关联。 但是又有TS中,2个类型没有关联,但是从结构上来说,它们具有相同的特征(属性),也可以认为这2个类型有关联。 python对上面2种情况都支持:
- 显示继承
- 通过结构关联
from collections.abc import Sized,Iterable,Iterator
class Bucket(Sized,Iterable[int]):
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
class Bucket_Struct:
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
# 只要实现了__iter__就可以被认为是Iterable
def collect(items: Iterable[int]) -> int: ...
collect(Bucket()) # OK
collect(Bucket_Struct()) # OKTip
,
...在函数定义中,表示抽象方法,pass表示占位,空实现,2者并不完全一样。
特殊类型
-
Any -
LiteralString: 字面量字符串,就像”Hello World”这样的字符串,不能是变量,和str类型不一样,str可能来源各种各样。 -
Never,NoReturn: 2者表示的意义是一样的,和TS中的never一样,没有任何子成员实例,除了它们自己,也就是不会发生的意思。from typing import Nerver # 不会发生返回,注意和返回None的不同,返回None是没有返回值 def foo(x: int) -> Never: raise ValueError("This function will never return") # 永远不会调用,因为没有对应的实参 def bar(x: Never) -> None: pass def int_or_str(arg: int | str) -> None: match arg: case int(): print("It's an int") case str(): print("It's a str") case _: bar(arg) # ok, arg is of type Never -
Self: 表示Class自己,为什么不直接用<ClassName>呢?,因为这会造成子类调用这个方法时返回的是父类型,除非你需要这样的结果。
特殊forms
支持[]描述符,可以传入类型,即特殊的类型运算。
-
UNION:UNION[X, Y]等效于X | YUnion[Union[int, str], float] == Union[int, str, float] type A = Union[int, str] Union[A, float] != Union[int, str, float] Union[int] == int # The constructor actually returns int -
OPTIONAL:OPTIONAL[X]等效于X | None -
Concatenate,专用于和参数规范配合使用,比如Concatenate[str, P],相当于增加了str类型的参数。 -
Literal: 字面量类型的值就是字面量,Literal[1, 2, 3]等效于1 | 2 | 3。部分概念和枚举类重合。 -
ClassVar: 类变量,类似java中的静态属性,python用它来区分静态和实例属性。 -
Final: 最终类型,类似java中的final关键字,不能和ClassVar一起使用,在class body中自动识别为ClassVar,在__init__自动识别为实例属性。init.
Final and ClassVar should not be used together. Mypy will infer the scope of a final declaration automatically depending on whether it was initialized in the class body or in
-
Required: 必须类型,用于标记TypedDict中的字段,为必需。Note
TypedDict就是TS中的interface
-
NotRequired: 同上,非必需。 -
ReadOnly: 同上,只读。 -
Annotated: 注解类型,用于添加额外的元信息到类型,供第三方库或type checker使用。 多个元信息是有顺序的from typing import Annotated age: Annotated[int, "年龄", "范围: 0-120"] = 30 @dataclass class MaxLen: value: int # 结合泛型使用 type Vec[T] = Annotated[list[tuple[T, T]], MaxLen(10)] type V = Vec[int] -
TypeIs: 用于类型断言,这个只能用于函数返回值,且函数必须返回True或False。当返回True时,函数参数的类型被断言为TypeIs中指定的类型,否则断言为函数参数的其他类型。from typing import TypeIs def is_non_empty_str(obj: object) -> TypeIs[str]: """Checks if an object is a non-empty string.""" return isinstance(obj, str) and len(obj) > 0 def process_item(item: str | int | None): if is_non_empty_str(item): # 在这个分支中,类型检查器知道 item 是 str 类型,并且是非空的 print(item.upper()) else: # 在这个分支中,由于 is_non_empty_str 返回 False, # 结合原始类型 str | int | None,类型检查器可以推断出 item 是 int 或 None。 print("Item is not a non-empty string")你必须确保
TypeIs中指定的类型,是函数实参允许的类型。 -
TypeGuard: 和TypeIs类似,但是主要用于collection内部的类型narrow,比如list[object]narrow 到list[str]。还有一个区别在于返回
Flase时,不像TypeIs会排除掉指定的类型,表达的含义就是无法narrow到guard的类型。def is_str_list(val: list[object]) -> TypeGuard[list[str]]: '''Determines whether all objects in the list are strings''' return all(isinstance(x, str) for x in val) def func1(val: list[object]): if is_str_list(val): # Type of ``val`` is narrowed to ``list[str]``. print(" ".join(val)) else: # Type of ``val`` remains as ``list[object]``. print("Not a list of strings!") -
Unpack: 主要针对类型元组变量和TypedDict,即将类型解包。from typing import Unpack,TypedDict Ts = TypeVarTuple('Ts') # 2种写法都可以 tup: tuple[Unpack[Ts]] tup: tuple[*Ts] # 针对TypedDict同样 class Movie(TypedDict): name: str year: int def foo(**kwargs: Unpack[Movie]): ...
Generic and Type alias
这些不是用来标注类型,而是创建类型。
-
Generic: 用来定义泛型类from typing import Generic, TypeVar T = TypeVar('T') class Box(Generic[T]): def __init__(self, x: T) -> None: self.x = x # 等同于上面的效果,即隐含的定义了一个局部TypeVar T,并继承至Generic class Box[T] -
TypeVar: 类型变量from typing import TypeVar # 显示定义,可复用 T = TypeVar('T') # 隐式定义,局部使用 class Foo[T]: ... # T is a TypeVarbound和constraint的使用见泛型
-
TypeVarTuple: 类型元组变量,可解决多个类型变量的情况from typing import TypeVarTuple Ts = TypeVarTuple('Ts') # 也可以使用*Ts直接定义,就像TypeVar一样 def fn[*Ts](*args: *Ts) -> tuple[*Ts]: ...Ts表示类型元组,不是元组类型,所以x: Ts是错误的,应该是x: tuple[*Ts]。,
-
ParamSpec: 参数规范,具体见ParamSpec 参数规范属于非常特殊的存在,它的作用在于映射一个函数的参数签名。from typing import ParamSpec P = ParamSpec('P') # 也可以使用[**P]直接定义,就像TypeVar一样 type IntFunc[**P] = Callable[P, int]类型参数能捕获位置和关键字参数,可以用
P.args和P.kwargs表示,且它们只能标注*args和**kwargs。Unpack[Ts]和Unpack[TypedDict]P.args和P.kwargs有点像
-
NewType: 参考NewType -
Protocol: 继承Protocol的类,在进行类型检查时,可以使用结构子类。class Foo(Protocol): def math(self, x: int, y: int) -> int: ... class Bar: def math(self, x: int, y: int) -> int: return x + y def func(x: Foo) -> int: return x.math(1, 2) # Bar虽然不是Foo的子类,但是有相同的结构 func(Bar())结合泛型:
class Zoo[T](Protocol): ...虽然static type checker可以通过,但是
isinstance和issubclass不能通过,除非使用runtime_checkable装饰器修饰Protocol类。hasattr直接检查结构。对于runtime_checkable的Protocol进行isinstance检查,性能非常差,还不如使用
-
TypedDict: value为类型的dict,用于描述dictfrom typing import TypedDict class Video(TypedDict,total=True): name: str year: int director: str # 另一种写法 Video = TypedDict('Video', {'name': str, 'year': int, 'director': str}, total=True) video: Video = {"name": "The Shawshank Redemption", "year": 1994, "director": "Frank Darabont"} assert Video(name="The Shawshank Redemption", year=1994, director="Frank Darabont") == video # okTypedDict可以结合Required,NotRequired,ReadOnly等来标记字段 可以使用total=False来表名所有字段都不是必须的。继承:
class Movie(Video): rating: float泛型:
class Group[T](TypedDict): key: T group: list[T]className[T]这种写法竟然3.12才有...,
Protocols
Protocol类明确可以使用结构子类,非常灵活,同时typing也提供了一些预置的Protocol类。
SupportsAbs: 支持abs方法,即实现了__abs__方法SupportsBytes: 支持bytes方法,即实现了__bytes__方法SupportsComplex: 支持complex方法,即实现了__complex__方法SupportsFloat: 支持float方法,即实现了__float__方法SupportsIndex: 支持index方法,即实现了__index__方法SupportsInt: 支持int方法,即实现了__int__方法SupportsRound: 支持round方法,即实现了__round__方法
from typing import SupportsAbs def foo(x: SupportsAbs[int]) -> int: return abs(x)
Functions and decorators
-
cast: 类型转换,强制转换为另一个类型,以通过类型检查。转换一定要符合上下文,否则runtime也会报错。 -
assert_type: 断言类型,用于编译检查。 -
assert_never: 断言该行代码永远unreachable,也就是它的参数必须是Never类型,否则会报错。 -
reveal_type: 显示类型检测器推断出的类型,runtime会打印runtime type。 -
@dataclass_transform: 用于标记一个函数、decorator类或metaclass,表明它是一个“数据类转换器”(dataclass transformer),被修饰或继承的类型会自动添加一些方法,比如__init__,__eq__等等,就好像它们直接被@dataclass修饰一样,当然@dataclass本质上也是添加这些方法。 这个装饰器本质还是为了适配以前的第三方库,让类型系统能检测到,现在当然直接使用@dataclass就可以了。 最终目的就是构建一个纯数据类,类似java中的Record。from typing import dataclass_transform, Any, get_type_hints @dataclass_transform() def generic_dataclass[T](cls: type[T]) -> type[T]: """ 支持泛型的数据类装饰器 自动为类生成__init__方法,并支持类型参数 """ annotations = get_type_hints(cls) def __init__(self, **kwargs: Any) -> None: # 处理类变量和默认值 for name, type_ in annotations.items(): if name in kwargs: setattr(self, name, kwargs[name]) elif hasattr(cls, name): # 使用类定义的默认值 setattr(self, name, getattr(cls, name)) else: raise TypeError(f"Missing required argument: '{name}'") setattr(cls, "__init__", __init__) return cls # generic_dataclass是一个dataclass_transform,给被装饰类添加了一个__init__方法 @generic_dataclass class Zoo: x: int y: str zoo = Zoo(x=1, y="hello") -
@overload: 标记为重载函数。最后必须要跟一个非重载函数(类型检查会忽略它,这可以看作函数的实现)。重载只能是参数类型不同,其他比如顺序,个数要保持一致。重载的函数对runtime没有影响,仅仅用于类型检查,而且它的执行体也不会被执行。可以使用get_overloads(<func>)在runtime获取重载的定义。from typing import clear_overloads, get_overloads, overload @overload def foo(x: int) -> int: # no effect return x + 1 @overload def foo(x: str) -> str: ... # 实现体,上面的重载可以看作接口定义 def foo(x: object) -> object: return xclear_overloads()会清除所有重载函数,可以用于测试。 -
@final: 标记一个函数表明它不能在子类中override,标记一个类表明它不能被继承。 -
@no_type_check: 对一个函数或类(类中的所有函数)不进行类型检查。 -
@override: 标记一个类函数表明它覆写了父类的同名函数。 -
@type_check_only: 标记类,只用于类型检查,即runtime时不可用。一般用于类型文件中。
Introspection helpers
类型在编译时有些信息会写入runtime,可以使用一些辅助函数来获取。
-
get_type_hints: 获取类型信息 -
get_origin: 获取类型的原始类型,比如list[int]的原始类型是list,Union[int,str]的原始类型是Union。不支持的则返回None。 -
get_args: 获取类型的参数,比如list[int]的参数是(int),Union[int,str]的参数是(int,str)。不支持的则返回()。 -
get_protocol_members: 获取Protocol类的成员,结构子类当然要提供获取成员(field,method)的方法,便于对比。 -
is_protocol: 判断是否是Protocol类。 -
is_typeddict: 判断是否是TypedDict类。TypedDict本身并不是,它是工厂类。 -
ForwardRef: 前向引用,可以用于延迟解析类型注解,也就是可以引用还未定义的类型。在递归,循环依赖中会用到。字符串字面量类型会被隐式转换为ForwardRef。class A: b: 'B' # 字符串形式的前向引用(等价于 ForwardRef('B')) class B: a: A print(get_type_hints(A)) # b的类型被正确解析为class B -
NoDefault: 判定一个类型参数是否有默认值,注意这是一个值,就像None一样。S = TypeVar("S") print(S.__default__) # typing.NoDefault -
TYPE_CHECKING: 是否在类型检查模式下,默认True,runtime时为False。可以针对这做一些逻辑处理。
builtin types
Truth Value
和C中的判定基本一致,非0为True,0为False。
但是可以通过__bool__方法重写,或者使用__len__方法,返回0时为false,其他情况为true。
所以,一个对象默认为真(因为字节肯定不是全0),如果有__bool__方法,则使用,否则使用__len__方法。
下面内置对象的布尔值为False:
NoneFalse0,0.0,0j,Decimal(0),Fraction(0)等任意numeric类型的0'',(),[],{},set(),range(0)等任意空sequence和空collections
boolean操作符
and,or,not,支持短路操作,优先级低于非bool操作符。
比较操作符
<,>,<=,>=,!=,==,is,is not,优先级相同,且都高于bool操作符。
==和is的区别: ==比较值,is比较内存地址。
Note
对象之间比较大小,需要实现
__lt__,__le__,__gt__,__ge__方法。
in,not in,可用于Iterable和实现了__contains__方法的类型。
numeric
int,float,complex(复数,用j表示虚数单位)
关于精度
python中的int可以表示
unlimited precision,也就是不像C那样有固定精度,它是根据值来动态分配的。 float通常使用C的double实现,也就是8个字节。 complex由2个float组成,所以通常是16个字节。
可以通过字面量或者对应的构造函数创建。
Tip
复数是计算工具,不是客观存在。
python中负数的floor实现,比如: -1 // 2 = -1
int
只有int支持位运算。
int类型的一些特殊方法:
-
int.bit_length():返回位数,但是不包括符号位和前面的0 -
int.bit_count(): 返回绝对值的二进制表示中1的个数 -
int.to_bytes(length=1, byteorder='big',signed=False): 返回字节序列,length表示使用的字节数,如果太小无法表示,则报错。byteorder表示大端还是小端,big | litter,signed,有无符号。关于大端小端
假设’abcd’: 大端就是先放高序列,就是按内存地址从a到d的放进内存 小端就是先放低序列,就是按内存地址从d到a的放进内存 字节本身的位顺序是不变的 单个字节内容无所谓大小端
-
int.from_bytes(bytes, byteorder='big',signed=False): 从字节序列中解析出int。
bool
2个实例:True,False
bool()可以将任何转换为bool实例。
和C一样,bool类型本质上是int,int(True) == 1,int(False) == 0
Iterator
迭代器必须要实现2个方法:
__iter__():返回迭代器,对于迭代器就是返回本身。容器也需要实现这个来完成for,in语句。__next__():返回下一个元素
Sequence
基本的sequence有个: list,tuple,range
它们有很多共同的方法:
| Operation | Result |
|---|---|
x in s | True if an item of s is equal to x, else False |
x not in s | False if an item of s is equal to x, else True |
s + t | the concatenation of s` and t |
s * n or n * s | equivalent to adding s to itself n times |
s[i] | ith item of s, origin 0 |
s[i:j] | s`lice of s from i to j |
s[i:j:k] | slice of s from i to j with step k |
len(s) | length of s |
min(s) | smallest item of s |
max(s) | largest item of s |
s.index(x[, i[, j]]) | index of the first occurrence of x in s (at or after index i and before index j) |
s.count(x) | total number of occurrences of x in s |
repeat操作:
其他好理解,和其他语言差不多,但是s * n这种repeat操作,有时候会出现难以理解的情况,如:
lists:list[list[int]] = [[]] * 3 # 结果: [[],[],[]],相当于list中的元素重复了3次,意义何在?
# 上面的重复是引用的重复所以:
lists[0].append(1) # 结果: [[1],[1],[1]]
# 里面的3个[]其实指向同一个对象slice操作:
所有的切片操作都不包括end index元素
s[i:j:k]: slice操作,k为步长,即每次选择会跳过k-1个元素
compare操作: 同类型的sequence支持compare,会逐个对比同位置的元素,所以2个sequence相等,必然类型一样,长度一样。
对于可变的sequence,支持如下操作:
| Operation | Result |
|---|---|
s[i] = x | item i of s is replaced by x |
s[i:j] = t | slice of s from i to j is replaced by the contents of the iterable t |
del s[i:j] | same as s[i:j] = [] |
s[i:j:k] = t | the elements of s[i:j:k] are replaced by those of t |
del s[i:j:k] | removes the elements of s[i:j:k] from the list |
s.append(x) | appends x to the end of the sequence (same as s[len(s):len(s)] = [x]) |
s.clear() | removes all items from s (same as del s[:]) |
s.copy() | creates a shallow copy of s (same as s[:]) |
s.extend(t) or s += t | extends s with the contents of t (for the most part the same as s[len(s):len(s)] = t) |
s *= n | updates s with its contents repeated n times |
s.insert(i, x) | inserts x into s at the index given by i (same as s[i:i] = [x]) |
s.pop() or s.pop(i) | retrieves the item at i and also removes it from s |
s.remove(x) | removes the first item from s where s[i] is equal to x |
s.reverse() | reverses the items of s in place |
slice修改中,t是一个sequence,要和slice出来的长度一致,不能多也不能少,且并不要求序列类型一致,比如s可以是list,t可以是tuple。
list
list是可变的。
构造:
[][a,b,c][x for x in range(10)]list()orlist(iterable)
支持重排:sort(*, key=None, reverse=False),注意是直接改变list,所以这个sort返回None。
tuple
tuple是不可变的。
构造过程和list基本一致。
tuple的核心是,,不是(),实际上除了empty tuple和引起语法歧义的地方,()都是可以省略的。
range
本质是一个迭代器,所以内存占用是不变的,时间换空间。
range不可变。
构造:
range(start, stop[, step])range(stop)此时start为0,step为1
r = range(stop)
# 则有:
# r[i] = start + i * step
# i >= 0 且 r[i] < abs(stop)Caution
,
start + i * step必须要能到达stop,否则返回[]比如range(1,0),range(0,10,-1)也就是如果没有r[i]满足stop的约束,则返回empty
range也支持Sequence的大部分操作,比如in,slice等等。
range同样支持==判定,但是并不判定start,stop,step是否相等,而是看构造的collection是否相等,比如:
range(0,3,2) == range(0,4,2)str
str是不可变的unicode point sequence
关于码位
以UTF-8为例,码位实际是一个标识符,表示这个字符,但是实际存储过程中,并不是直接存码位的值,比如“中”的码位是
U+4E2D,16位,实际存储时,使用0xE4 0xB8 0xAD,3个字节。 编码过程: 字符 → 码位 → 根据码位范围得出存储字节数 → 根据码位结合填充规范补完字节序列 解码过程: 字节序列 → 提取有效位 → 组合码位 → 映射字符
str字面量形式:
单行:
'str'"str"
多行:
"""str"""'''str'''
比较特殊的是字符串之间只有空格时,会被隐式的合并:
("spam " "eggs") == "spam eggs"关于转义:
"a\nb" # 换行符
r("a\nb") # raw 不会转义使用构造函数:
str(object=''),本质是调用type(object).__str__(object),或者是repr(object)。(这和Java中的表达基本一致)str(object=b'', encoding='utf-8', errors='strict')只针对bytes,bytearray类型,实际等效于bytes.decode(encoding,errors)
方法
常用方法 特殊方法说明:
-
str.casefold():比lowercase更彻底的小写,大部分时候和lowercase相同 -
str.center(width, fillchar=' '):在width中居中,使用fillchar填充左右(这种方法也提供…) -
str.expandtabs(tabsize=8):将字符串中的\t替换为空格,替换的空格数量,取决于\t的位置和tabsize,每tabsize中的\t都会转换为空格,以达到对齐的效果。s = '01\t012\t0123\t01234' expanded = s.expandtabs(4) # print '01 012 0123 01234' # 解释: # 01\t,到下一个制表位4,添加2个空格 # 012\t,到下一个制表位8,添加1个空格 # 0123\t,到下一个制表位12,添加4个空格 -
isdecimal(),isdigit(),isnumeric(): 都是判定是否数值类型,区别在于范围越来越大"1".isdecimal() # True "①".isdigit() # True "三".isnumeric() # True -
istitle(): 简单说就是Title Title格式的 -
static str.maketrans(x[, y[, z]]): 这个方法用于生成一个转换表,可以用于str.translate()方法# 2种映射格式 trans = str.maketrans({'a': 'X', 'b': "y"}) # trans = str.maketrans('abc', '123') print("aobeuf".translate(trans))
format
f-strings
str.format()有点类似于c中的printf,但是python还提供了更简单且强大的f-strings格式化语法。
# f'{expression}'
who = 'Alice'
f'Hello, {who}!'
x = 42
f'{{x}} is {x}' # 输出 {x} is 42
还可以结合!a,!r,!s来指定输出的格式:
# !a: ascii
# !r: repr
# !s: str
h = "我"
print(f'{h!a}') # '\u6211', 不是ascii码的则会转换为unicode point
print(f'{h!r}') # '我'
print(f'{h!s}') # 我将变量名也输出出来:
answer = 42
print(f'{answer=}') # answer=42格式化表达式结果:
# f'{expression:format_spec}'
print(f'{42:04d}') # 0042printf-style format
基本和C中的printf一样,格式:format % values
print('%s has %03d quote types.' % ('python',2)) # python has 002 quote types.flag和转换类型也和C类似,比如flag中的:
#,可以显示额外的格式,比如16进制的0x0,通常和宽度一起使用,比如%05d表示宽度为5,用0填充左边-,左对齐,默认是右对齐,通常也是和宽度一起使用,比如%-10s表示宽度为10,左对齐' ',可以给正数前面加空格,比如% d+,强制显示符号,比如正数的+,比如%+d
Binary Sequence Types
bytes
本质上是一个字节数组,但是不可变。
所以每个元素in range(0,256),也可以将bytes对象当作int sequence理解,实际上list(bytes)就可以输出list[int]。
构造:
- 字面量:
b'ABC',字符串字面量前面加b,但是字符串只能包括ASCII字符 bytes(length),包括length个字节,用0填充bytes(iterable),相当于每个元素对应的字节bytes(obj),复制已存在的二进制数据bytes.fromhex(hexstr),将十六进制字符串转换为二进制数据,注意字符必须要成对出现(2个16进制字符对应一个byte),空白字符会被忽略 bytes实例可以使用hex()方法转换为十六进制字符串
作为sequence同样可以slice,也可以根据index获取元素:
b'ABC'[0] # 65,字节对应的无符号整数
b'ABC'[0:1] # b'A',还是一个bytesbytearray
就是bytes的可变版本
没有字面量类型(必然,因为可变),只能通过构造函数创建。 基本上bytes支持的操作bytearray都支持。
注意: bytes和bytearray的对应的Sequence操作,比如count,removeprefix,endswith等,传入的参数都应该是bytes-like,比如b'abc',bytes([95,96,97]),bytearray(b'abc'),而不是字符串,例:
b'abc'.count(b'a') # 1很多操作就算是bytearray也不是直接修改,而是copy一个new object,比如capitalize,expandtabs等等
Note
,
bytes,bytearray有大量方法和str对应,比如expandtabs,islower,只是一个操作字符,一个操作字节,但是本质上一样。 连printf-style格式化也是一样的,只是都要转成字面量bytes
memoryview
什么是内存view,本质是一块连续内存的视图,视图即视角,也就是不同的解释模型,比如一块内存,可以看作无符号,也可以看作有符号,也可以看作字符串
但是只能操作支持buffer protocol的类型,比如bytes,bytearray。
内存安全
内存块当然不能随意直接修改,否则可能造成不可预料的后果(比如数组越界问题),特别是有些数据结构复杂的,只能通过代码来修改。
memoryview同样支持切片,也可以获取index元素,它的slice本质是换成另一个view了,没有copy,也没有cut。
data = bytearray(b'abcefg')
m = memoryview(data)
# 前提是data是可修改的
m[0] = ord(b'a') # m[0]是byte,只能赋值byte,byte本质是int
m[0:1] = b'a' # m[0:1]是bytes不是byte
一些特殊方法:
release(): 创建memoryview会对原object有一定的限制,有些还会占用一些资源,操作完后建议release。release后不能进行其他操作了。cast[format,[, shape]]: 类型转换,但是这个转换本质是改变数据的解释方式,不修改底层数据。import struct buf = struct.pack("i"*12, *list(range(12))) # 将12个int打包成二进制数据,放入12个"i"中,每个4个字节 x = memoryview(buf) y = x.cast('i', shape=[2,2,3]) # 根据shape转换,3个维度,一维2个元素,2维2个元素,3维3个元素 y.tolist() # [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]
一些属性:
obj: 被包裹的对象nbytes: 内存占用字节数itemsize: 单个元素字节数(这个元素是最小颗粒的元素,不是维度元素)。注意不要用len(m) * m.itemsize计算nbytes,因为len只算一个维度ndim: 维度数strides: 每个维度每个元素的字节数
关于format:
@前缀表示本地字节序
>前缀表示大端字节序
<前缀表示小端字节序
| Format | C Type | Python Type | Standard Size |
|---|---|---|---|
| x | pad byte | no value | |
| c | char | bytes of length 1 | 1 |
| b | signed char | integer | 1 |
| B | unsigned char | integer | 1 |
| ? | _Bool | bool | 1 |
| h | short | integer | 2 |
| H | unsigned short | integer | 2 |
| i | int | integer | 4 |
| I | unsigned int | integer | 4 |
| l | long | integer | 4 |
| L | unsigned long | integer | 4 |
| q | long long | integer | 8 |
| Q | unsigned long long | integer | 8 |
| n | ssize_t | integer | |
| N | size_t | integer | |
| e | float | 2 | |
| f | float | float | 4 |
| d | double | float | 8 |
| s | char[] | bytes | |
| p | char[] | bytes | |
| P | void* | integer |
set, frozenset
和list最大的区别就是无序,且不能重复,set中的值必须是hashable,也就是不可变的。
set是可变的,但是frozenset是不可变的。
Note
,
dict的key必须是hashable,因为内部实现是通过哈希表完成的(HashMap),如果key是可变的,会造成hash变化,存取时可能导致冲突。
构造:
- set字面量:
{1, 2, 3} - 构造函数:
set([iterable]),frozenset([iterable])
一些特殊方法:
isdisjoint(other_set): 判断两个集合是否有交集union(*other_set): 返回并集,会自动剔除重复元素symmetric_difference(other_set): 返回两个集合的差集,也就是并集移除交集部分,UNION - INTERSECTIONcopy(): 注意是集合中的元素浅拷贝,不是set,但是set又要求元素是不可变的,所以元素的深浅拷贝效果一样set之间还有对应的update_版方法,用于更新而不是判断,参考
set之间可以直接用操作符来比较,比如<=,>,|,&,-,^等,但是操作符只能对set操作,而对应的函数调用可以使用iterable作为参数。
set和frozenset可以一起运算(可以简单理解为frozenset extends set),对于需要返回的,返回类型取决于第一个操作数。
c = frozenset([1, 2, 3])
d = set([1, 2, 3])
c == d # True
frozenset('abc') & set('bc') # frozenset({'c', 'b'})dict
类似hashmap,key-value形式,key必须是hashable的
构造:
- dict字面量:
{'a': 1, 'b': 2} - 构造函数:
dict(**kwargs) - 构造函数:
dict(mapping, **kwargs) - 构造函数:
dict(iterable, **kwargs),iterable的元素必须是iterable,且有且仅有2个元素
# 可以非常灵活的构造dict
d = dict(a=1, b=2, c=3)
d1 = dict(d, d=4) # 使用已有的dict
d2 = dict([('a', 1), ('b', 2), ('c', 3)],d=4) # 使用list[tuple]创建2个dict在拥有完全一样的key-value对时,表示相等,顺序无所谓,因为存储位置是和hash有关。
Note
虽然存储顺序和构建顺序无关,但是构建顺序影响输出顺序,所以可以
zip(d.keys(), d.values()),一一对应,这和set不同。
一些方法,更具体的参考文档:
-
d[key]: 返回key对应的值,这个支持操作符重载# 没有对应的key,则执行__missing__方法 class Mydict(dict): def __missing__(self, key): return key -
del d[key]: 删除key对应的值 -
iter(d.keys())或d.iter():返回key的迭代器 -
items(): 返回(key, value)entries,可迭代,即iter(d.items())返回了一个iterator -
dict.fromkeys(iterable, value=None): 返回一个包含iterable中所有元素作为key的dict,其中value可以指定默认值。这是一个classmethod -
popitem(): 返回一个(key, value)元组,并且删除该(key, value)对应的值,采用后进先出的栈模式 -
d | other: 返回一个新的dict,并集,other的value会覆盖d,如果key相同 -
d |= other: 同上,但是是更新d
dictionary view
keys(),values(),items()返回的类型。根据名称view就能知道,仅仅是一种解释行为,源数据还在原地。
Tip
,
values()的结果永远不会和另一个values()的结果相同,就算是同一个dict的,可能考虑value可能是mutable的?
view有个mapping属性,返回一个只读的dict对象,可以用于原始字典的只读访问。
d = dict(a=1, b=2, c=3)
assert d.keys().mapping == d.values().mapping == d.items().mapping # True
GenericAlias
Tip
在python中,泛型类型,比如
list[int]本身是GenericAlias类的实例,会进入runtime,可以被作用域的__annotations__属性获取 这和TS不同,TS编译后类型基本完全擦除,但是python会保留一些信息进入runtime,虽然不会影响运行,但是可以用于内省
对于实现了__class_getitem__的类,可以使用泛型参数来定义一个泛型类型。比如list[int],对于一些非容器类的,泛型参数一般会表示这些类某些方法的返回类型。
# 注意2种写法的区别
t = list[int] # type(t) --> <class 'types.GenericAlias'>
type T = list[int] # type(T) --> <class 'typing.TypeAliasType'>
mylist = t([1,2,3])所以list[int]这种泛型类型,本质上可以看作list的子类。
GenericAlias一些常用方法:
__args__: 获取泛型参数,比如list[int]的参数是(int)__origin__: 获取泛型类型的原类型,比如list[int]的原类型是list--parameters__: 获取TypeVar定义的参数
union
同样的联合类型也是一个实例,它的类型是typing.UnionType
联合类型可以使用==运算:
(int | str) | float == int | str | float # Trueother types
module
module作为import模块的类型,它的实例有一个__dict__属性,包括这个模块的符号表,可以直接修改符号表,但是不能直接修改__dict__对应的对象,即可以改值,但是不能改对象。
实际上改值也不建议。
class
class本身也是一个实例,这和java中的概念类似。
function
同样函数也是class function的实例,这和js中的function类似,但是函数的类型用Callable表达。
method
本质上是函数。但是比较特殊,主要有2个来源:
- 内置方法,比如
list.append - 类实例方法
对于类的实例方法,有2个属性:
__self__: 这个方法所属的类实例__func__: 这个方法本身,也就是function
实例调用方法时,会自动传入self,所以这也是为什么method定义时第一个参数总是self的原因,名称self只是约定俗成的,换其他名称也是一样,这是个位置参数。
code
<function>.__code__的类型是code,一般表示函数的body。
None,Ellipsis,NotImplemented
这三个值是内置的,可以直接使用。
可以通过type(<None | Ellipsis | NotImplemented>)()来获取对应的单例
一些类型的通用属性
| 属性 | 说明 |
|---|---|
__module__ | class或function所属模块名 |
__qualname__ | 类型名 |
__name__ | 类型名 |
__doc__ | class或function的文档 |
__type_params__ | 获取泛型类型的泛型参数 |
关于int和str的转换
由于Cpython的int不是固定长度,理论上可以放非常大的数字,比如10万个bit位的int。 这就可能造成一个问题,比如大数字的转换,10进制的数字字符串转换为int,或者反过来,都会消耗大量的cpu时间。 所以Cpython有一个全局限制,限制了数字字符串的长度,默认为4300。
sys.set_int_max_str_digits(4300) # default这个限制只针对10进制,2、8、16的转换不受限制,因为都是2个倍数,效率很高。