前言

在学习像Java这种静态类型的语言时可以从继承结构上一点一点学习,但如果你还以这种方式学习像Python这种鸭子类型的语言,那可谓是步履维艰,不仅不理解而且还会引发仇恨。如果你是一名Java开发者,那么这篇文章将十分适合你。本文分为以下三块:

  • 执行模型
  • 数据模型(核心)
  • 导入模型

本文内容整理自:

执行模型

Python 程序是怎么被执行的呢?简单来说,它是由一个个 代码块 组成的。每个代码块在执行时都会生成一个 执行帧(frame),就像给代码开了一个独立的小房间,每个房间都有自己的“执行现场”,记录变量、调试信息,以及控制程序下一步行为。理解这些概念,有助于写出高效、可维护的代码。


程序的结构

在 Python 中,代码块是可以独立执行的 Python 程序片段,常见的代码块包括:

  • 模块:一个 .py 文件就是一个模块
  • 函数体def 定义的内容
  • 类定义class 里面的代码
  • 交互式输入:REPL 中每条命令
  • 通过 eval()exec() 执行的字符串代码
  • 命令行参数 -c-m 执行的代码

每个代码块在执行时都会创建一个 执行帧,帧里包含调试信息,并决定代码块执行完后的下一步操作。可以把帧理解成代码的“执行现场”。

示例:

def foo():
    x = 1  # foo 函数代码块里的局部变量
    print(x)

foo()

这里 x 绑定在 foo 的局部命名空间里,foo 执行完后,局部命名空间就会销毁。


名称与绑定

在 Python 中,名称就像名字牌,而对象是你要指代的实体。名称和对象的关联就是 绑定。常见的绑定方式包括:

  • 变量赋值:x = 10
  • 函数定义:def foo(): ...
  • 类定义:class A: ...
  • 导入模块:import math
  • 函数参数:def foo(a, b): ...
  • 删除绑定:del x

提示:

  • 函数内赋值默认是 局部绑定
  • 修改 全局变量,需用 global
  • 修改 外层函数变量,需用 nonlocal

命名空间和作用域

命名空间是名称到对象的映射表,Python 内部用字典实现。常见命名空间类型:

  • 内置命名空间:Python 启动时创建,例如 lenstr
  • 模块全局命名空间:模块顶层定义的变量和函数
  • 函数局部命名空间:函数调用时创建,函数结束销毁
  • 类命名空间:类定义里的属性和方法

每个命名空间有一个 作用域(scope),决定名称在代码里的可见性。Python 查找变量遵循 LEGB 原则

  1. Local:局部作用域
  2. Enclosing:外层函数/闭包作用域
  3. Global:模块全局作用域
  4. Built-in:内置作用域

示例:

x = 10  # 全局变量

def outer():
    y = 20  # 外层函数变量
    def inner():
        z = 30  # 局部变量
        print(x, y, z)
    inner()

outer()  # 输出 10 20 30

Python 会按顺序查找变量:先查 inner 的局部变量 z,没找到再查 outery,再查全局 x,最后查内置命名空间。


local、nonlocal 和 global

  • local:默认绑定在当前局部作用域
  • nonlocal:修改外层函数变量,不在当前局部创建新的绑定
  • global:声明变量为全局变量,修改会影响整个模块

示例:

count = 0  # 全局变量

def outer():
    num = 1  # 外层变量
    def inner():
        nonlocal num
        global count
        num += 1
        count += 1
        print(num, count)
    inner()

outer()  # 输出 2 1
print(count)  # 输出 1

这里 num 是外层函数的变量,通过 nonlocal 修改,而 count 是全局变量,通过 global 修改。


数据模型

可以把 Python 视为一个框架,而数据模型就是对框架的描述,规范语言自身各个组成部分的接口,确立序列、函数、迭代器、协程、类、上下文管理器等部分的行为。Python解释器就是框架的使用者。使用框架要花大量时间编写方法,交给框架调用。利用 Python 数据模
型构建新类也是如此。Python 解释器调用特殊方法(特殊方法的名称前后两端都有双下划线)来执行基本对象操作,通常由特殊句法触发。

对象

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

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

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

基类和元类

在Python中,object是所有类的顶级基类,而上文我们提到:类本身也是对象,那么将类看作一个对象时,用元类表示类的类,Python中的顶级元类就是type,即:

  • 所有类的基类最终链条都指向 object
  • 所有类的元类最终链条都指向 type
isinstance(object, type)  # True
isinstance(type, object)  # True

理解objecttype是关键,在学习Python数据模型的时候要以继承链为主而不是元类链条。

内置元类

Python 内置的元类其实非常少,本质上 Python 里大部分类的元类都是 type,少数内置抽象基类使用 ABCMeta,还有一些标准库特殊类型使用自定义元类(如 EnumMeta):

object
type
ABCMeta
EnumMeta

为一个类定义确定适当的元类是根据以下规则:

  • 如果没有基类且没有显式指定元类,则使用type
  • 如果给出一个显式元类,则其会被直接用作元类,但如果不继承自type,则会报错;
  • 如果给出一个type的实例作为显式元类,或是定义了基类,则使用最近派生的元类。

内置基本类型

内置的基本类型即不需要导入就可以使用的类型,由于各种因素,它分为两类:

  • 下图中以类名展示的就是在Python中有类定义的
  • 以汉字展示的就是在Python中没有类定义的,这种可以使用type(类实例)使用类型信息
object
int
float
complex
bool
list
tuple
range
str
bytes
bytearray
memoryview
set
frozenset
dict
type
空类型
未实现类型
省略符类型
迭代器类型
生成器类型
模块类型
可调用类型

可调用类型

object
FunctionType
BuiltinFunctionType
BuiltinMethodType
MethodType
GeneratorType
CoroutineType
AsyncGeneratorType
CodeType
FrameType
TracebackType
MappingProxyType
SimpleNamespace
ModuleType
WrapperDescriptorType
MethodWrapperType
MethodDescriptorType
ClassMethodDescriptorType
MemberDescriptorType
Awaitable
AsyncIterator
AsyncIterable
LambdaType
ClassMethodType
StaticMethodType

可调用类型是可以像函数一样通过括号进行调用的对象。具体包含以下几种类型:

类型对象 说明 典型示例 / 用法
FunctionType 普通 Python 函数对象(def 定义的函数) def foo(): pass
LambdaType Lambda 函数(匿名函数) lambda x: x+1(实际上是 FunctionType 的别名)
BuiltinFunctionType 内置函数对象 len, sum
BuiltinMethodType 内置方法对象 [1,2,3].append
MethodType 绑定方法(实例方法) obj.method
ClassMethodType 类方法对象 @classmethod def cm(cls): pass
StaticMethodType 静态方法对象 @staticmethod def sm(): pass
GeneratorType 生成器对象 def gen(): yield 1g = gen()
CoroutineType 协程对象 async def coro(): passc = coro()
AsyncGeneratorType 异步生成器对象 async def agen(): yield 1a = agen()
CodeType 函数的代码对象 foo.__code__
FrameType 栈帧对象 inspect.currentframe() 返回的对象
TracebackType 异常 traceback 对象 try: ... except Exception as e: e.__traceback__
MappingProxyType 不可变字典视图 types.MappingProxyType({'a':1})
SimpleNamespace 简单命名空间对象,类似动态对象 obj = types.SimpleNamespace(x=1, y=2)
  • 用户定义函数:用户定义的函数对象是通过函数定义创建的,并且在调用时,必须传递与函数形参一致的参数。
    • 特殊只读属性
      • 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中有一种抽象基类的类比较特殊,它们的元类都是ABCMeta,可以用它们来定义接口规范或抽象方法,并且不能直接实例化,只能被子类继承或注册为虚拟子类。抽象基类类似于Java中的接口和抽象类,不同的是虚拟子类不是直接继承自ABCMeta类型的类,而是通过ABCMeta.register()注册的。

object
Number
Complex
Real
Rational
Integral
Iterable
Iterator
Container
Collection
Sequence
MutableSequence
Set
MutableSet
Mapping
MutableMapping
Callable
说明 典型虚拟子类
Number 所有数字类型 int, float, complex
Complex 复数 complex
Real 实数 float, int
Rational 有理数 Fraction
Integral 整数 int
Iterable 可迭代对象 list, tuple, dict, str
Iterator 迭代器 文件对象、生成器
Sequence 序列 list, tuple, str
Mapping 映射(字典) dict
Set 集合 set, frozenset
MutableSequence 可变序列 list
MutableMapping 可变映射 dict
MutableSet 可变集合 set
Callable 可调用对象 函数、方法、类

自定义类型

在 Python 中,自定义类通常通过类定义来创建。

  • 类的属性与行为:

    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__()
      • 每个类都有一个保存直接子类的弱引用的列表。该方法返回一个包含所有当前有效子类引用的列表,列表项按子类的定义顺序排列。

特殊方法名称

在 Python 中,一个类可以通过定义具有特殊名称的方法来实现由特殊语法来唤起的特定操作(例如算术运算或抽取与切片)。 这是 Python 实现 运算符重载 的方式,允许每个类自行定义基于该语言运算符的特定行为。 如果将一个特殊方法设为 None 表示对应的操作不可用。

基本定制

以下特殊方法可以帮助你控制对象的行为,使它们能与 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() 函数一起使用。
自定义属性访问

Python 提供了一组特殊方法,可以自定义类实例和模块的属性访问、赋值、删除及列表行为。

  1. 实例属性访问方法

    方法 调用时机 参数 功能 注意事项
    __getattr__(self, name) 当访问不存在的属性时 name: 属性名 返回属性值或抛出 AttributeError 只在默认机制查找失败时调用;不会对已有属性触发
    __getattribute__(self, name) 无条件访问实例属性 name: 属性名 返回属性值或抛出 AttributeError 避免无限递归,通常通过 super().__getattribute__() 调用基类
    __setattr__(self, name, value) 赋值操作时 name: 属性名, value: 值 设置属性 若赋值给实例属性,应调用基类方法:super().__setattr__(name, value)
    __delattr__(self, name) 删除操作时 name: 属性名 删除属性 仅当 del obj.name 有意义时实现
    __dir__(self) 调用 dir(obj) 返回可迭代对象(属性名列表) 返回的可迭代对象会被转换成列表并排序
  2. 模块属性访问

    • module.__getattr__(name):在模块中访问不存在的属性时调用,返回值或抛出 AttributeError。
    • module.__dir__():返回模块可访问属性列表。
    • module.__class__:可以通过修改模块的 __class__types.ModuleType 子类来自定义模块行为。
  3. 描述器:描述器是具有“绑定行为”的对象属性,通过实现 __get__, __set__, __delete__ 方法重载默认属性访问。

    • 描述器方法:

      方法 调用时机 参数 功能
      __get__(self, instance, owner=None) 获取属性值 instance: 对象实例, owner: 所属类 返回计算后的值或抛出 AttributeError
      __set__(self, instance, value) 设置属性值 instance: 对象实例, value: 新值 设置实例属性
      __delete__(self, instance) 删除属性 instance: 对象实例 删除实例属性
      • 描述器对象可能存在 __objclass__ 属性,指示其定义的类。
      • 数据描述器:定义了 __set__ 和/或 __delete__
      • 非数据描述器:只定义 __get__
      • 方法 (@staticmethod, @classmethod) 是非数据描述器。
      • property() 是数据描述器。
    • 描述器调用方式

      调用方式 表达式 实际调用
      直接调用 x.__get__(a) 显式调用描述器方法
      实例绑定 a.x type(a).__dict__['x'].__get__(a, type(a))
      类绑定 A.x A.__dict__['x'].__get__(None, A)
      超绑定 super(A, a).x 在 MRO 中查找紧接 A 的父类的描述器
  4. __slots____slots__ 用于显式声明实例变量,避免创建 __dict____weakref__,节省内存并加快属性访问。

    • 可以是字符串或可迭代对象。
    • 限制未声明的变量赋值,否则抛出 AttributeError。
    • 支持弱引用需在 __slots__ 中声明 '__weakref__'
    • 子类继承父类的 __slots__,需要单独声明附加槽位。
    • 不允许为 int, bytes, tuple 等内置类型定义非空 __slots__
    • 内部通过描述器实现槽位。

自定义类创建

自定义实例及子类检查

模拟泛型类型

模拟可调用对象

模拟容器类型

模拟数字类型

with 语句上下文管理器

定制类模式匹配中的位置参数

模拟缓冲区类型

特殊方法查找

类实例

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

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

协程

导入模型

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 的目录名称。这个变量可以修改;修改后会影响今后对模块和包中包含的子包的搜索。这个功能虽然不常用,但可用于扩展包中的模块集。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐