说明

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) # 3

Note

NewType出来的类型,还可以继续用于NewType

NewType和TypeAlias的区别: NewType是新的类型,和原类型不兼容,而TypeAlias是别名,和原类型完全等价。

# type MyInt = int
MyInt = NewType('MyInt', int)
 
a = 1
b: MyInt
b = a # error

Function

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中有很多这样的操作,如keyofPartialRecord等。 类型变量使用[]符号,类似函数的()

Note

类型操作实际是给编译器(或者说Lint之类的工具)看的,运行时会被忽略,这和TS一样。

常用的:

  • ParamSpec:捕获函数参数签名,便于后续对于捕获到的参数签名进行操作

    参数规范只能用于ConcatenateCallable的第一个参数。

    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:

可以给泛型添加boundconstraintbound: 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) # 定义constraint

Note

bound和constraint不能同时使用

不管隐式还是显示,都需要先定义使用。 比如:

def foo(x: T) -> T: # error,T没有定义
def foo[T](x: T) -> T: # ok
 
T = TypeVar('T')
def foo(x: T) -> T: # ok

TypeVar在定义类型变量时,还可以指定variance逻辑:

  • invariant: 不变性,默认行为
  • covariant: 协变性
  • contravariant: 逆变性

这个是针对关联泛型的容器类型的,举例说明:

from typing import TypeVar, Generic
T = TypeVar('T', covariant=True)
class Animal:
    pass
class Dog(Animal):
    pass
 
class AnimalFeeder[T]:
    pass

T 为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[()]表示空元组,只能被赋值为() tupletuple[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层意思:

  1. class是类型,常规理解
  2. 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的类型,推断为int

type可以接收class,Any,type变量,以及他们的联合作为参数。

泛型class

class Foo[T]:
    def __init__(self, x: T) -> None:
        self.x = x

3.11及之前的版本:

from typing import TypeVar, Generic
T = TypeVar('T')
class Foo(Generic[T]): # 实际上泛型class 隐式的继承了Generic
    def __init__(self, x: T) -> None:
        self.x = x

其他特性:

  1. 可以定义多个类型变量,即多个泛型参数,[T,R]

  2. 泛型类不指定泛型参数时,默认为Any

  3. type定义alias时可以使用泛型参数,type Vet[T] = Iterable[tuple[T,T]]

  4. 泛型参数和参数规范可以同时使用,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是基类,可以被赋值为任意类型。

Anyobject最大的区别: 使用object表示一个具体的类型,任何对象都可以赋值给它,而Any表示这是一个动态类型。

子类的定义

强类型系统中,比如java,c++中,子类必须显示继承父类,才能说这2个类型有关联。 但是又有TS中,2个类型没有关联,但是从结构上来说,它们具有相同的特征(属性),也可以认为这2个类型有关联。 python对上面2种情况都支持:

  1. 显示继承
  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())  # OK

Tip

,...在函数定义中,表示抽象方法,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 | Y

    Union[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: 用于类型断言,这个只能用于函数返回值,且函数必须返回TrueFalse。当返回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 TypeVar

    boundconstraint的使用见泛型

  • 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.argsP.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可以通过,但是isinstanceissubclass不能通过,除非使用runtime_checkable装饰器修饰Protocol类。

    hasattr直接检查结构。

    对于runtime_checkable的Protocol进行isinstance检查,性能非常差,还不如使用

  • TypedDict: value为类型的dict,用于描述dict

    from 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 # ok

    TypedDict可以结合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 x

    clear_overloads()会清除所有重载函数,可以用于测试。

  • @final: 标记一个函数表明它不能在子类中override,标记一个类表明它不能被继承。

  • @no_type_check: 对一个函数或类(类中的所有函数)不进行类型检查。

  • @override: 标记一个类函数表明它覆写了父类的同名函数。

  • @type_check_only: 标记类,只用于类型检查,即runtime时不可用。一般用于类型文件中。

Introspection helpers

类型在编译时有些信息会写入runtime,可以使用一些辅助函数来获取。

  • get_type_hints: 获取类型信息

  • get_origin: 获取类型的原始类型,比如list[int]的原始类型是listUnion[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

docs

Truth Value

和C中的判定基本一致,非0为True,0为False。 但是可以通过__bool__方法重写,或者使用__len__方法,返回0时为false,其他情况为true。 所以,一个对象默认为真(因为字节肯定不是全0),如果有__bool__方法,则使用,否则使用__len__方法。

下面内置对象的布尔值为False:

  • None
  • False
  • 0, 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) == 1int(False) == 0

Iterator

迭代器必须要实现2个方法:

  • __iter__():返回迭代器,对于迭代器就是返回本身。容器也需要实现这个来完成for,in语句。
  • __next__():返回下一个元素

Sequence

基本的sequence有个: list,tuple,range

它们有很多共同的方法:

OperationResult
x in sTrue if an item of s is equal to x, else False
x not in sFalse if an item of s is equal to x, else True
s + tthe concatenation of s` and t
s * n or n * sequivalent 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,支持如下操作:

OperationResult
s[i] = xitem i of s is replaced by x
s[i:j] = tslice 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] = tthe 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 += textends s with the contents of t (for the most part the same as s[len(s):len(s)] = t)
s *= nupdates 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() or list(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}') # 0042
printf-style format

基本和C中的printf一样,格式:format % values

print('%s has %03d quote types.' % ('python',2)) # python has 002 quote types.

flag和转换类型也和C类似,比如flag中的:

  • #,可以显示额外的格式,比如16进制的0x
  • 0,通常和宽度一起使用,比如%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',还是一个bytes

bytearray

就是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: @前缀表示本地字节序 >前缀表示大端字节序 <前缀表示小端字节序

FormatC TypePython TypeStandard Size
xpad byteno value
ccharbytes of length 11
bsigned charinteger1
Bunsigned charinteger1
?_Boolbool1
hshortinteger2
Hunsigned shortinteger2
iintinteger4
Iunsigned intinteger4
llonginteger4
Lunsigned longinteger4
qlong longinteger8
Qunsigned long longinteger8
nssize_tinteger
Nsize_tinteger
efloat2
ffloatfloat4
ddoublefloat8
schar[]bytes
pchar[]bytes
Pvoid*integer

set, frozenset

list最大的区别就是无序,且不能重复,set中的值必须是hashable,也就是不可变的。

set是可变的,但是frozenset是不可变的。

Note

,dictkey必须是hashable,因为内部实现是通过哈希表完成的(HashMap),如果key是可变的,会造成hash变化,存取时可能导致冲突。

构造:

  • set字面量: {1, 2, 3}
  • 构造函数: set([iterable]), frozenset([iterable])

一些特殊方法:

  • isdisjoint(other_set): 判断两个集合是否有交集
  • union(*other_set): 返回并集,会自动剔除重复元素
  • symmetric_difference(other_set): 返回两个集合的差集,也就是并集移除交集部分,UNION - INTERSECTION
  • copy(): 注意是集合中的元素浅拷贝,不是set,但是set又要求元素是不可变的,所以元素的深浅拷贝效果一样
  • set之间还有对应的update_版方法,用于更新而不是判断,参考

set之间可以直接用操作符来比较,比如<=,>,|,&,-,^等,但是操作符只能对set操作,而对应的函数调用可以使用iterable作为参数。

setfrozenset可以一起运算(可以简单理解为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 # True

other 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__classfunction所属模块名
__qualname__类型名
__name__类型名
__doc__classfunction的文档
__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个倍数,效率很高。