All In AI之二:一文构建Python核心语法体系

前言

所有编程语言都是为了作为人操作计算机资源的中介而存在,其中最重要的就是操作内存和CPU,其中操作内存的模式基本上就决定了这个编程语言的设计思想,所以快速且全面深入学习一个编程语言的方式就是从以下几个方面入手:

  • 执行模型
  • 数据模型
  • 导入模型

本文内容全部出自官方文档,且随着作者的不断实践会继续完善和修正。

执行模型

Python 程序的执行模型涉及到代码块、名称绑定、命名空间、作用域和名称解析等方面,理解这些概念对于写出高效、可维护的代码至关重要。以下是对这些概念的精简、整合和扩展,旨在使其更加全面且通俗易懂。

程序的结构

Python 程序由多个代码块组成,代码块是作为一个单元执行的一段 Python 程序文本。以下内容都属于代码块:

  • 模块
  • 函数体
  • 类定义
  • 交互式输入的每条命令
  • 通过脚本文件运行的程序
  • 使用 -c 选项在命令行中运行的命令
  • 使用 -m 参数运行的模块(即 __main__ 模块)
  • 传递给内置函数 eval()exec() 的字符串参数

每个代码块在执行时都会产生一个执行帧,帧中包含一些管理信息,如调试信息,并决定代码块执行完后如何继续。

名称与绑定

在 Python 中,名称是用来指代对象的。名称通过绑定操作被引入。绑定名称的操作包括:

  • 函数的正式参数
  • 类定义
  • 函数定义
  • 赋值表达式
  • import 语句
  • type 语句
  • del 语句(解除绑定)

如果某个名称在代码块内被绑定,它是该代码块的局部变量,除非它被声明为 nonlocalglobal。如果某个名称在模块层级被绑定,它则是全局变量。模块中的变量既是局部变量,也是全局变量。如果名称在某个代码块中被使用但没有绑定,它是自由变量。

命名空间和作用域

命名空间是名称到对象的映射,通常使用字典来实现。命名空间的例子包括:

  • 内置命名空间:存放 Python 内建函数和异常(如 print、Exception)。
  • 模块的全局命名空间:每个模块的顶层变量和函数。
  • 局部命名空间:函数调用时创建的命名空间,包含函数内的变量。
  • 类的命名空间:类定义中的属性和方法。

命名空间的一个关键特点是,不同命名空间中的名称是相互独立的。命名空间在不同时间被创建,且拥有不同的生命周期。例如,内置名称的命名空间在 Python 解释器启动时创建,并且在整个程序运行期间都存在。模块的全局命名空间在读取模块定义时创建,并且通常会一直存在直到解释器退出。局部命名空间则是在函数调用时创建,在函数返回或异常抛出时销毁。递归调用会创建新的局部命名空间。

每个命名空间都有一个作用域,即它的可见范围,在该区域内可以直接访问该命名空间中的名称。作用域虽然是静态确定的,但会在运行时动态地使用。Python 在执行时通常会有三到四个嵌套的作用域:

  1. 最内层作用域,包含局部名称,优先在其中查找名称。
  2. 外层闭包作用域,包含“非局部、非全局”的名称,逐层向外搜索。
  3. 倒数第二层作用域,包含当前模块的全局名称。
  4. 最外层作用域,即内置名称的命名空间。

名称的解析

作用域定义了一个代码块中名称的可见性。当 Python 代码访问某个名称时,它会按照LEGB(Local、Enclosing、Global、Built-in)规则依次在作用域中查找:

  • Local:首先查找局部作用域。
  • Enclosing:如果没有找到,会查找外层函数的作用域。
  • Global:再查找模块的全局作用域。
  • Built-in:最后查找内置作用域。

如果在这些作用域中都没有找到该名称,会抛出 NameError 异常。如果在局部作用域中查找时发现名称未绑定,将抛出 UnboundLocalError 异常。

local、nonlocal 和 global

  • local:默认情况下,名称的绑定发生在局部作用域中,且只在当前代码块有效。
  • nonlocal:用于在嵌套函数中引用外层函数的变量,修改该变量时,不会在当前局部作用域创建新的绑定,而是直接修改外层作用域中的变量。
  • global:用于在函数或其他代码块中声明某个变量为全局变量,使得该变量指向模块的全局命名空间。修改时,会影响整个模块范围内的该变量。

类定义与作用域

在类定义中,类本身会创建一个命名空间,用于存储类的属性和方法。类定义的代码块中定义的名称仅在类内部可见。类的作用域不会自动扩展到方法的代码块中。

例如,下面的代码会失败:

class A:
    a = 42
    b = list(a + i for i in range(10))  # 错误:a 尚未绑定

而下面的代码则可以正常工作:

class A:
    type Alias = Nested
    class Nested: pass

print(A.Alias)  # <class '__main__.A.Nested'>

数据模型

对象 是 Python 中对数据的抽象。 Python 程序中的所有数据都是由对象或对象间关系来表示的。每个对象有三个核心概念:

  • 标识符:标识符可以理解为该对象在内存中的地址, 一个对象被创建后它的标识号就绝不会改变, is运算符比较两个对象的标识号是否相同;id() 函数返回一个代表其标识号的整数。
  • 类型:对象的类型决定该对象所支持的操作并且定义了该类型的对象可能的取值。type() 函数能返回一个对象的类型 (类型本身也是对象)。与编号一样,一个对象的类型也是不可改变的。
  • 值:有些对象的值可以改变。值可以改变的对象被称为 可变对象;值不可以改变的对象就被称为不可变对象。 一个对象的可变性是由其类型决定的;例如,数字、字符串和元组是不可变的,而字典和列表是可变的。

对象绝不会被显式地销毁;然而,当无法访问时它们可能会被作为垃圾回收。

标准类型层级结构

以下是 Python 内置类型的列表。以下部分类型的描述中包含有 ‘特殊属性列表’ 段落。这些属性提供对具体实现的访问而非通常使用。它们的定义在未来可能会改变。

None

此类型只有一种取值。是一个具有此值的单独对象。此对象通过内置名称 None 访问。在许多情况下它被用来表示空值,例如未显式指明返回值的函数将返回 None。它的逻辑值为假。

NotImplemented

此类型只有一种取值。 是一个具有该值的单独对象。 此对象通过内置名称 NotImplemented 访问。 数值方法和丰富比较方法如未实现指定运算符表示的运算则应返回该值。 它不应被解读为布尔值。

Ellipsis

此类型只有一种取值。是一个具有此值的单独对象。此对象通过字面值 … 或内置名称 Ellipsis 访问。它的逻辑值为真。

数字类型(Number)

«abstract»
Number
«abstract»
Integral
«abstract»
Real
«abstract»
Complex
int
bool
float
complex

在 Python 中,数字类型(如整数、浮点数和复数)是通过直接的字面量值(例如 13.142 + 3j)或通过运算生成的。Python 中的数字对象在创建时会自动根据其类型分配给相应的数字类。数字对象是不可变的。其中:

  • intIntegral 类的一个子类,用于表示任意大小的整数,支持大整数(不受大小限制,受内存限制)。
  • boolint 的子类,表示逻辑值 False(相当于 0)和 True(相当于 1)。它在 Python 中表现得像整数 01,除了在转换为字符串时,分别返回 "False""True"
  • floatReal 类的子类,用于表示双精度浮点数。
  • complexComplex的子类,用于表示复数,复数由实部和虚部组成,在 Python 中是由两个 float 数字表示的。
    • 复数的实部和虚部可以通过 .real.imag 属性获取。
    • 在 Python 中,复数表示为 a + bj,其中 a 为实部,b 为虚部,j 是虚数单位。
    • 复数支持常见的复数运算,如加减乘除等。

序列类型(Sequence)

«abstract»
Sequence
«abstract»
ImmutableSequence
«abstract»
MutableSequence
str
tuple
bytes
list
bytearray

在 Python 中,序列类型用于表示以非负整数为索引的有限有序集合。序列支持对元素的访问、切片操作等,并根据其可变性分为可变序列和不可变序列。

  • 序列的长度由 len() 函数返回,索引从 0 开始。
  • 支持负索引:a[-1] 返回序列中的最后一项,a[-2] 返回倒数第二项。
  • 支持切片:a[i:j] 返回索引从 ij-1 的所有项。
  • 支持扩展切片:a[i:j:k] 可以指定步长 k,返回每隔 k 项的切片。
不可变序列(ImmutableSequence

不可变序列一旦创建后无法修改。虽然对象中可能包含对可变对象的引用,但这些对象本身不能修改。

  1. str(字符串):字符串是 Unicode 码位的序列,每个码位由一个字符表示。取值范围在 U+0000 到 U+10FFFF 之间。
    • 创建方式:使用' '" "''' '''""" """创建
    • 常用方法:
      • ord(c):返回字符 c 的 Unicode 码位。
      • chr(i):返回码位 i 对应的字符。
      • str.encode():将字符串编码为字节流(bytes)。
  2. tuple(元组):元组是不可变的序列,可以包含任意类型的对象。由多个元素构成,用逗号分隔。
    • 创建方式:使用()创建元组,单元素元组需要使用逗号来创建,例如 (1,)
  3. bytes(字节串):字节串是不可变的序列,包含 8 位字节,每个字节的取值范围为 0 至 255。
    • 创建方式:使用bytes()创建
    • 常用方法:
      • bytes.decode():将字节串解码为字符串。
可变序列(MutableSequence

可变序列在创建后可以被修改,允许修改、删除元素,甚至改变其长度。

  1. list(列表):列表是可变的序列,可以包含任意类型的对象,支持动态修改。
    • 创建方式:通过[]创建
    • 常用操作:
      • append(x):在列表末尾添加元素 x
      • remove(x):删除列表中的元素 x
      • insert(i, x):在索引 i 处插入元素 x
      • pop(i):移除并返回索引 i 处的元素。
  2. bytearray(字节数组):字节数组是可变的字节序列,功能与字节串类似,但它可以被修改。
    • 创建方式:
    • 常用方法:
      • bytearray():创建一个新的字节数组。
      • bytearray.decode():将字节数组解码为字符串。
      • bytearray.append(x):在字节数组末尾添加字节 x

集合类型(Set)

«abstract»
Set
«abstract»
MutableSet
«abstract»
ImmutableSet
set
frozenset

集合类型是表示由不重复且不可变对象组成的无序有限集合。集合不支持通过下标来索引元素,但可以进行迭代。它们常用于高效的成员检测、去除重复项以及进行数学运算(如交集、并集、差集和对称差集等)。

  • 集合通过 len() 函数可以获取元素的数量。
  • 集合支持快速的成员检测(使用 in 关键字)。

其中:

  1. set(集合): 是一个可变的集合类型,支持添加和删除元素。
    • 创建方式:使用{}set() 函数创建,创建空集合只能使用set() 函数
    • 常用方法:
      • add(x):向集合中添加元素 x
      • remove(x):从集合中移除元素 x,若元素不存在则会抛出 KeyError 异常。
      • discard(x):从集合中移除元素 x,若元素不存在不会抛出异常。
      • clear():移除集合中的所有元素。
      • 集合支持交集、并集、差集、对称差等运算。
  2. frozenset(冻结集合)
    • frozenset 是不可变的集合类型,一旦创建后无法修改。
    • frozensetset 相比,最大的区别是它是不可变的,因此可以作为字典的键或作为其他集合的元素。
    • 创建方式:
      • 使用 frozenset() 函数创建冻结集合。
    • 特性:
      • frozenset 不支持像 add()remove() 等修改操作。
      • 由于不可变性,它是 hashable 的,因此可以作为字典的键或其他集合的元素。

映射类型(Mapping)

«abstract»
Mapping
dict

映射类型对象表示由任意索引集合索引的对象集合。在 Python 中,映射通过键值对存储数据,其中每个键都关联一个值。通过键可以快速访问对应的值,映射中的元素可以通过下标(a[k])进行访问,并可以作为赋值或删除操作的目标。映射的条目数可以通过 len() 函数获取。

  • dict 是 Python目前有唯一的内建映射类型,表示由键值对组成的集合。每个键都与一个值关联,键是唯一的,并且可以是任意不可变类型(如字符串、数字、元组等)。
  • 创建方式:使用 {}dict() 构造函数创建
  • 字典的特点:
    • 字典是可变的,可以通过键值对的方式添加、修改或删除元素。
    • 通过键 k 可以访问字典中的值 a[k]
    • 字典支持键值对的删除操作,可以使用 del a[k] 删除某个键。
    • 字典的键必须是不可变类型(如字符串、数字、元组等),并且需要是可哈希的。
    • 字典中的键按照插入顺序存储,这意味着它们会保持插入时的顺序。在 Python 3.6 之前,字典没有保证顺序。在 Python 3.7 中,字典会保留插入顺序,这成为了语言的正式特性。
  • 字典常用方法:
    • a.keys():返回字典的所有键。
    • a.values():返回字典的所有值。
    • a.items():返回字典的所有键值对。
    • a.get(k):返回键 k 对应的值,如果键不存在,则返回 None(也可以指定默认值)。
    • a.update(b):用字典 b 的键值对更新字典 a
    • a.pop(k):删除并返回键 k 对应的值。
    • a.clear():清空字典。
  • 键的比较规则:
    • 字典的键遵循正常的数字比较规则。例如,如果 11.0 被用作字典的键,它们是相等的,因此它们会索引同一个字典条目。
    • 字典中的键必须是可哈希的对象,因此像列表、字典等可变对象不能作为字典的键。

可调用类型

可调用类型是可以像函数一样通过括号进行调用的对象。函数、方法、生成器函数等类型都属于可调用类型。它们可以用于函数调用操作,具体包含以下几种类型:

  • 用户定义函数:用户定义的函数对象是通过函数定义创建的,并且在调用时,必须传递与函数形参一致的参数。
    • 特殊只读属性
      • function.__globals__:对存放函数全局变量的字典的引用(函数定义所在模块的全局命名空间)。
      • function.__closure__:指向函数闭包的单元对象,包含函数自由变量的绑定。
    • 特殊可写属性
      • function.__doc__:函数的文档字符串。
      • function.__name__:函数的名称。
      • function.__qualname__:函数的完全限定名。
      • function.__module__:函数所在模块的名称。
      • function.__defaults__:函数默认参数值的元组。
      • function.__code__:函数的代码对象。
      • function.__dict__:支持任意函数属性的命名空间。
      • function.__annotations__:函数的参数和返回值类型注解字典。
      • function.__kwdefaults__:仅限关键字参数的默认值字典。
  • 实例方法:实例方法是类的成员方法,它将类实例作为第一个参数传递给函数。
    • 特殊只读属性
      • method.__self__:指向方法绑定的类实例对象。
      • method.__func__:指向原始的函数对象。
      • method.__doc__:方法的文档字符串。
      • method.__name__:方法的名称。
      • method.__module__:方法定义所在模块的名称。
    • 实例方法的特点是
      • 当通过类的实例访问用户定义的函数对象时,方法会自动绑定实例,__self__ 会指向该实例。
      • 绑定方法后,调用方法时,第一个参数会自动传递类实例。
  • 生成器函数:生成器函数是包含 yield 语句的函数。调用生成器函数时,返回的是一个迭代器对象,可以通过该迭代器的 __next__() 方法获取生成的值。直到生成器函数执行到 return 或结束时,抛出 StopIteration 异常。
  • 协程函数:协程函数是通过 async def 定义的函数,返回一个协程对象。它可能包含 await 表达式,并且支持异步操作。
  • 异步生成器函数:异步生成器函数结合了协程和生成器的特性,使用 async defyield 语句。调用时返回一个异步迭代器对象,可以在 async for 中使用。异步生成器可以通过 __anext__() 方法返回可等待对象,直到生成器结束,抛出 StopAsyncIteration 异常。
  • 内置函数:内置函数是由 Python 提供的、为特定 C 函数封装的函数。例如 len()math.sin() 都是内置函数。它们的参数数量和类型由 C 函数决定。
    • 特殊只读属性
      • __doc__:函数的文档字符串。
      • __name__:函数的名称。
      • __self__:通常为 None,表示函数本身没有实例绑定。
      • __module__:函数定义所在模块的名称。
  • 内置方法:内置方法是内置函数的特殊形式,通常作为对象的方法存在。例如列表的 append() 方法,它是列表对象的一个方法,隐式地将对象作为第一个参数传入。
  • :类也是可调用的对象,通常用于创建类的实例。类的调用会触发 __new__()__init__() 方法。
  • 类实例:如果在类实例中定义了 __call__() 方法,那么该实例可以变成可调用对象,允许像函数一样调用实例。

模块类型

在 Python 中,模块是代码的基本组织单元,通过导入系统创建,通常可以通过 import 语句或其他方式(如 importlib.import_module())来导入。模块对象的命名空间由字典对象管理,即模块中定义的所有函数的 __globals__ 属性指向的字典。

  • 模块对象上与导入相关的属性
    1. module.__name__
      • 用于在导入系统中唯一地标识模块的名称。对于直接执行的模块,这将被设为 __main__。如果模块属于某个包,__name__ 将是模块的完整限定名称(例如 package.module)。
    2. module.__spec__
      • 记录与模块导入相关的状态,包括模块的规范。这是 Python 3.4 版本引入的特性,表示了模块的导入细节(如加载器、路径等)。通过 module.__spec__ 可以访问模块的加载信息,如:
        • __spec__.name: 模块的名称
        • __spec__.loader: 模块的加载器
        • __spec__.parent: 模块所属的父级包
        • __spec__.submodule_search_locations: 子模块的搜索路径等
    3. module.__package__
      • 模块所属的包的名称。如果模块是顶层模块(即不属于任何包),则该属性为 ""(空字符串)。如果是子模块,则该属性为其包的名称(例如对于 package.submodule__package__package)。
      • 如果模块是顶级模块(即不属于任何包),则 __package__ 默认是空字符串。
    4. module.__loader__
      • 模块加载器对象,表示导入该模块的具体实现。该属性通常用于调试和进一步的加载功能。
    5. module.__path__
      • 用于包的模块,表示包内子模块的搜索路径。对于非包模块,此属性不存在。
    6. module.__file__module.__cached__
      • __file__ 表示从文件加载的模块的文件路径。
      • __cached__ 表示模块的缓存文件路径(如字节编译后的文件)。这两个属性通常在文件加载的模块中存在,但某些类型的模块可能没有这些属性(例如 C 扩展模块)。
  • 模块对象上的其他可写属性
    1. module.__doc__
      • 模块的文档字符串,如果没有文档字符串,则为 None
    2. module.__annotations__
      • 包含在模块体执行期间收集的所有变量注释(如类型注解)。它是一个字典,存储了所有在模块内定义的类型注解。
  • 模块字典
    • module.__dict__
      • 模块的命名空间,它是一个字典对象,存储模块中定义的所有变量、函数和类。module.__dict__ 允许我们动态访问和修改模块内的属性。

自定义类

在 Python 中,自定义类通常通过类定义来创建,每个类都有一个通过字典对象实现的命名空间。类属性引用会被转化为在类的 __dict__ 字典中查找。如果未在该字典中找到某个属性,会继续在基类中查找。基类查找遵循 C3 方法解析顺序 (C3 MRO),即使存在 ‘钻石形’ 继承结构,多个继承路径指向同一个共同祖先,依然能够保持正确的行为。

  • 类的属性与行为:

    1. 类的属性查找
      • 当通过 C.x 访问类的属性时,Python 实际上会去 C.__dict__ 字典中查找 x。如果没有找到,搜索会继续进行到基类(如果有的话)。
    2. 类方法与静态方法
      • 通过类访问方法时,类方法(如 @classmethod)会被转化为一个实例方法,实例方法的 __self__ 属性会绑定到类本身。静态方法(如 @staticmethod)则会转换为该静态方法所封装的对象。
    3. 类属性赋值
      • 类属性的赋值会更新类的 __dict__,但不会影响基类的字典。换句话说,基类的属性不会受到子类对同名属性的修改影响。
    4. 类对象可被调用
      • 类本身可以作为对象调用,从而创建该类的实例。类实例的创建是通过调用类对象的 __new____init__ 方法来完成的。
  • 特殊属性:Python 类有几个特殊属性,用于获取类的相关信息:

属性含义
type.__name__类的名称。
type.__qualname__类的 qualified name。
type.__module__类定义所在模块的名称。
type.__dict__提供类的命名空间的只读视图的映射代理。
type.__bases__类的基类组成的元组,例如:X.__bases__ == (A, B, C)
type.__doc__类的文档字符串,如果未定义则为 None
type.__annotations__包含在类体执行期间收集的变量注释的字典。
type.__type_params__包含泛型类的类型参数的元组(从 Python 3.12 开始)。
type.__static_attributes__包含类内所有通过 self.X 在函数体内赋值的属性名元组(从 Python 3.13 开始)。
type.__firstlineno__类定义的第一行的行号,包括装饰器(从 Python 3.13 开始)。
type.__mro__方法解析顺序(MRO),即查找基类时考虑的类的元组。

特殊方法:除了特殊属性外,Python 类还具有两个特殊方法:

  1. type.mro()
    • 该方法可以被元类重写,以便定制其实例的 MRO(方法解析顺序)。它会在类实例化时被调用,并且结果存储在 __mro__ 属性中。
  2. type.__subclasses__()
    • 每个类都有一个保存直接子类的弱引用的列表。该方法返回一个包含所有当前有效子类引用的列表,列表项按子类的定义顺序排列。

类实例

类实例是通过调用类对象来创建的。每个类实例都有一个独立的命名空间,该命名空间通过字典对象实现。属性查找首先会在该字典中进行,若找不到,则会继续在类属性中查找。

  • 属性查找
    • 类实例会先在其自身的 __dict__ 字典中查找属性。
    • 如果找不到该属性,会继续在类中查找。如果类属性是一个自定义函数对象,它会被转化为一个实例方法对象,并将该实例作为 __self__ 属性。
    • 类方法和静态方法也会被转化成相应的方法对象。详细信息可以参考“类”部分。
  • __getattr__ 方法
    • 如果在实例和类中都找不到所需的属性,而类具有 __getattr__() 方法,则会调用该方法来处理属性查找。
  • 属性赋值与删除
    • 属性赋值和删除会更新实例的 __dict__ 字典,而不会影响类的 __dict__ 字典。
    • 如果类定义了 __setattr__()__delattr__() 方法,这些方法会被调用来处理属性赋值和删除,而不会直接操作实例的 __dict__
  • 特殊方法
    • 类实例可以通过实现一些特殊方法(如 __add____getitem__ 等)来伪装成数字、序列或映射等类型。详细信息可参考“特殊方法名称”部分。
  • 特殊属性:类实例具有一些特殊属性,可以用于获取对象的相关信息:
属性含义
object.__class__类实例所属的类。
object.__dict__一个用于存储对象的(可写)属性的字典或其他映射对象。并非所有实例都具有该属性;详情请参阅 __slots__

I/O 对象 (文件对象)

文件对象表示一个打开的文件。在 Python 中,有多种方式可以创建文件对象,主要包括以下几种:

  1. open() 内置函数
    • 用于打开文件并返回一个文件对象。
    • 语法:open(filename, mode)filename 为文件名,mode 为文件打开模式(如 'r', 'w', 'b' 等)。
  2. os.popen()
    • 用于打开一个管道并返回一个文件对象,该文件对象可以用于与子进程进行通信。
  3. os.fdopen()
    • 用于通过文件描述符(文件的整数标识符)返回一个文件对象。通常与低级 I/O 操作一起使用。
  4. socket.makefile()
    • 用于将一个套接字(socket)对象包装成文件对象,以便通过标准文件接口进行读写操作。

sys.stdinsys.stdoutsys.stderr 会初始化为对应于解释器标准输入、输出和错误流的文件对象;它们都会以文本模式打开,因此都遵循 io.TextIOBase 抽象类所定义的接口。
在 Python 中,内部类型包括一些为解释器使用的对象,这些类型也可以暴露给用户,供调试、反射等用途。以下是一些关键的内部类型:

内部类型

在 Python 中,内部类型包括一些为解释器使用的对象,这些类型也可以暴露给用户,供调试、反射等用途。以下是一些关键的内部类型:

  • 代码对象 (Code Objects):代码对象表示已经编译成字节码的可执行 Python 代码。它们包含执行 Python 程序时所需的信息。代码对象通常与函数对象一起使用,但它们不包含上下文或可变对象的引用,且不可变。
    • 重要属性:
      • co_name: 函数名
      • co_qualname: 完整限定函数名
      • co_argcount: 位置形参的总数
      • co_posonlyargcount: 仅限位置形参的数量
      • co_kwonlyargcount: 仅限关键字形参的数量
      • co_nlocals: 使用的局部变量的数量
      • co_varnames: 局部变量名的元组
      • co_cellvars: 被内嵌作用域引用的局部变量名的元组
      • co_freevars: 闭包变量的名称
      • co_code: 字节码指令的字符串
      • co_consts: 字面值的元组
      • co_names: 使用的名称的元组
      • co_filename: 编译代码所在文件的名称
      • co_firstlineno: 函数的第一行号
      • co_flags: 解释器使用的标志
    • 方法:
      • co_positions(): 返回字节码指令的源代码位置
      • co_lines(): 返回字节码范围的行号信息
      • replace(): 返回代码对象的副本,更新指定的字段
  • 帧对象 (Frame Objects):帧对象表示正在执行的代码帧,它们可能出现在回溯对象中,也可通过跟踪函数注册。帧对象包含当前正在执行的代码对象以及其局部、全局、内建字典等信息。
    • 重要属性:
      • f_back: 前一个栈帧
      • f_code: 当前执行的代码对象
      • f_locals: 局部变量的映射
      • f_globals: 全局变量的字典
      • f_builtins: 内建名称的字典
      • f_lasti: 当前的字节码指令
    • 方法:
      • clear(): 清除对局部变量的引用,打破循环引用
  • 回溯对象 (Traceback Objects):回溯对象代表异常的栈跟踪信息,它们帮助调试程序中的错误,提供异常发生时的上下文信息。
    • 重要属性:
      • tb_frame: 当前层级的执行帧
      • tb_lineno: 异常发生的行号
      • tb_lasti: 最后一条指令的位置
      • tb_next: 下一个回溯对象
  • 切片对象 (Slice Objects):切片对象用于表示对序列进行切片操作的范围。它们包含开始、结束和步长值,并通过 slice() 构造器创建。
    • 重要属性:
      • start: 切片的开始索引
      • stop: 切片的结束索引
      • step: 步长值
    • 方法:
      • indices(length): 根据给定长度计算切片相关的信息
  • 静态方法和类方法对象 (Static and Class Method Objects):静态方法和类方法对象提供了一种将普通方法转换为特定类型的方法对象的方式。静态方法对象由 staticmethod() 构造器创建,类方法对象由 classmethod() 构造器创建。

特殊方法名称

在 Python 中,特殊方法(也称为魔法方法或 dunder 方法)允许开发者通过定义方法来实现特定的操作,例如算术运算、索引访问、迭代等。通过这些方法,类能够模拟内置类型的行为,或是为自定义的类提供一些特殊操作。这种特性被称为运算符重载。

基本定制

以下特殊方法可以帮助你控制对象的行为,使它们能与 Python 内置的操作符和函数(如 str(), print(), 比较操作符等)兼容。

  • __new__(cls[, ...]):用于创建类的新实例。常见的做法是调用 super().__new__(cls) 来创建实例,并进行修改。特别用于不可变类型的子类,或自定义元类。
  • __init__(self[, ...]):在实例创建后初始化它。此方法会在 __new__() 创建实例后被调用。返回值必须是 None
  • __del__(self):在实例销毁时调用。这个方法并不总是被保证执行,特别是在解释器退出时。
  • __repr__(self):返回对象的“官方”字符串表示,通常用于调试。希望返回一个有效的 Python 表达式,能够重建对象。
  • __str__(self):返回对象的“非正式”字符串表示,通常是用户可读的格式,适用于打印和格式化。
  • __bytes__(self):通过 bytes() 转换对象为字节字符串,返回值应为 bytes 对象。
  • __format__(self, format_spec):通过 format() 或格式化字符串调用,生成对象的“格式化”字符串表示。
  • __lt__(self, other)__le__(self, other)__eq__(self, other)__ne__(self, other)__gt__(self, other)__ge__(self, other):这些是“富比较”方法,用于实现 <<===!=>>= 运算符的行为。
  • __hash__(self):返回对象的哈希值,用于支持集合和字典操作。
  • __bool__(self):实现布尔值测试,通常与 bool() 函数一起使用。

自定义属性访问

可以定义下列方法来自定义对类实例属性访问的具体含义。

  1. __getattr__(self, name):当属性无法通过正常机制找到时被调用,返回计算的属性值或引发 AttributeError
  2. __getattribute__(self, name):每次访问属性时都会调用此方法。若定义了 __getattr__(),则必须显式调用以避免递归。
  3. __setattr__(self, name, value):在给属性赋值时被调用,用于替代默认的赋值机制。需要通过基类调用 object.__setattr__() 来设置属性。
  4. __delattr__(self, name):在删除属性时被调用。只有当删除属性 del obj.name 有意义时才实现。
  5. __dir__(self):当调用 dir() 时被调用,返回可迭代对象,供 dir() 排序使用。

实现描述器

调用描述器

导入模型

Python 把各种定义存入一个文件,在脚本或解释器的交互式实例中使用。这个文件就是模块 ,模块是包含 Python 定义和语句的文件。其文件名是模块名加后缀名 .py 。在模块内部,通过全局变量 __name__ 可以获取模块名,模块中的定义可以 导入到其他模块中。

import fibo

此操作不会直接把 fibo 中定义的函数名称添加到当前命名空间中,它只是将模块名称 fibo 添加到那里, 使用该模块名称你可以访问其中的函数。

模块

模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在 import 语句 第一次遇到模块名时执行。(文件作为脚本运行时,也会执行这些语句)。每个模块都有自己的私有命名空间,它会被用作模块中定义的所有名称的全局命名空间。

模块可以导入其他模块。 根据惯例可以将所有 import 语句都放在模块的开头但这并非强制要求。 如果被放置于一个模块的最高层级,则被导入的模块名称会被添加到该模块的全局命名空间。还有一种 import 语句的变化形式可以将来自某个模块的名称直接导入到导入方模块的命名空间中。 例如:

from fibo import fib, fib2

还有一种变体可以导入模块内定义的所有名称:

from fibo import *

这种方式会导入所有不以下划线(_)开头的名称。大多数情况下,不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称。

模块搜索路径

当导入一个名为 spam 的模块时,解释器首先会搜索具有该名称的内置模块。 这些模块的名称在 sys.builtin_module_names 中列出。 如果未找到,它将在变量 sys.path 所给出的目录列表中搜索名为 spam.py 的文件。 sys.path 是从这些位置初始化的:

  • 被命令行直接运行的脚本所在的目录(或未指定文件时的当前目录)。
  • PYTHONPATH (目录列表,与 shell 变量 PATH 的语法一样)。
  • 依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。

初始化后,Python 程序可以更改 sys.path。脚本所在的目录先于标准库所在的路径被搜索。这意味着,脚本所在的目录如果有和标准库同名的文件,那么加载的是该目录里的,而不是标准库的。这一般是一个错误,除非这样的替换是你有意为之。

标准模块

Python 自带一个标准模块的库, 一些模块是内嵌到解释器里面的, 它们给一些虽并非语言核心但却内嵌的操作提供接口,要么是为了效率,要么是给操作系统基础操作例如系统调入提供接口。 这些模块集是一个配置选项, 并且还依赖于底层的操作系统。

dir() 函数

内置函数 dir() 用于查找模块定义的名称。返回结果是经过排序的字符串列表,没有参数时,dir() 列出当前已定义的名称,不会列出内置函数和变量的名称。这些内容的定义在标准模块 builtins 中。

包是通过使用“带点号模块名”来构造 Python 模块命名空间的一种方式。 例如,模块名 A.B 表示名为 A 的包中名为 B 的子模块。 就像使用模块可以让不同模块的作者不必担心彼此的全局变量名一样。导入包时,Python 搜索 sys.path 里的目录,查找包的子目录。

需要有 __init__.py 文件才能让 Python 将包含该文件的目录当作包来处理。 这可以防止重名的目录如 string 在无意中屏蔽后继出现在模块搜索路径中的有效模块。 在最简单的情况下,__init__.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__ 变量。

从包中导入 *

使用 from sound.effects import * 时会发生什么?你可能希望它会查找并导入包的所有子模块,但事实并非如此。因为这将花费很长的时间,并且可能会产生你不想要的副作用,如果这种副作用被你设计为只有在导入某个特定的子模块时才应该发生。

唯一的解决办法是提供包的显式索引。import 语句使用如下惯例:如果包的 __init__.py 代码定义了列表 __all__,运行 from package import * 时,它就是被导入的模块名列表。发布包的新版本时,包的作者应更新此列表。如果包的作者认为没有必要在包中执行导入 * 操作,也可以不提供此列表。

如果没有定义 __all__from sound.effects import * 语句不会把包 sound.effects 中的所有子模块都导入到当前命名空间;它只是确保包 sound.effects 已被导入(可能还会运行 __init__.py 中的任何初始化代码),然后再导入包中定义的任何名称。 这包括由 __init__.py 定义的任何名称(以及显式加载的子模块)。

请注意子模块可能会受到本地定义名称的影响。 例如,如果你在 sound/effects/__init__.py 文件中添加了一个 reverse 函数,from sound.effects import * 将只导入 echosurround 这两个子模块,但 不会 导入 reverse 子模块,因为它被本地定义的 reverse 函数所遮挡。

多目录中的包

包还支持一个特殊的属性, __path__ 。 在执行该文件中的代码之前,它被初始化为字符串的 sequence,其中包含包的 __init__.py 的目录名称。这个变量可以修改;修改后会影响今后对模块和包中包含的子包的搜索。这个功能虽然不常用,但可用于扩展包中的模块集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

比特流梭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值