Skip to content

前言

通过提问的方式来街道python的疑惑

python 中命名空间、作用域, 包、模块的关系

命名空间是名字到变量的映射,全局(模块)命名空间、局部(函数)命名空间和内置命名空间(各种内置变量),通过locals()、globals() 获取命名空间(会将所有的变量映射成字典)。内置命名空间不能直接获取,只能通过dir(buildIn)获取变量列表

作用域在很多语言都有,各个语言也大差不差。通俗理解就是你想看的代码文本区块能够访问命名空间有哪些。在查找某个变量是总是按照局部作用域->闭包作用域->全局作用域->内置作用域来查找。

包和模块都是一个对象,所有申明在模块的变量其实都是模块的属性,模块中内置了很多属性和方法,都是在模块加载解释器注入到其中的。

模块的初始化: 当碰到Import语句时,解释器会生成一个模块对象(实际是一个命名空间),包括全局空间和内置空间。然后添加name到模块对象中,然后依次执行代码,添加局部空间。最后import 就会得到这个对象,注意的是模块默认情况下只会导入一次

变量、对象和值

python 一切都是对象,常见的数字、字符串、函数、类、集合、列表等都是对象。对象在内存会占据一块区域,下面是对象的一个列子。

type: "int"
value: 1,
id: "xxasfsafas1231"

其中对象有区分可变对象和不可变对象,不可变对象有数字、字符串、元组等。意思是创建成功后值不可变化,可变对象值得是创建成功后值是可变的。通过 id(xxx)可以获取对象的内存空间值

值其实就是对象的一个属性,第一种是对象的值空间直接保存原始的值,另外就是值空间保存其他若干对象的引用。

变量实际一个指针,保存在栈空间,总是会指向对象的内存空间上。

举一些列子来理解这些概念

值 1 和 对象 1 有啥区别, 代码中 a = 1 是啥

值 1 就是简单表示值是 1,对象 1 值得是一个对象,对象是有上面的特性,值 1 只是对象 1 的一个特性。 a = 1 首先创建了一个对象 1(如果之前没有创建过),把值设置成 1,type 是 int ,id 设置对象当前的内存地址,同时创建一个变量,变量保存在栈空间,变量的值指向了对象 1 的 id。 如果再写 b = 1,这个时候不会再创建对象 1,而是直接复用,应为是不可变类型

为什么 1 == 1, 1 is 1 == true。

弄清这个问题首先要搞清 == 是计算符,计算符的行为是可以被定义的。1 == 1 只是判断的值是不是相同,显然同一个对象的值是相同的, 1 is 1 是判断内存地址是否相同。 对于对象来说,总是全局唯一的,所以也是相同。

弄清 python 中标识符、保留标识符、关键字

曾经我以为 type 是关键字,除了常见的关键字,列一些容易搞错的。

  • in
  • is
  • nonlocal
  • global
  • None
  • yield
  • with

保留的标识符有

  • __ 不会被 import _ 导入(不过我们从不用 import * 导入)

  • __ 双下划线用在类中会被解析器重命名成 classname__name,避免子类覆盖,但是__*|_除外(__init__)

  • __*__ 表示魔法方法,作为未来 python 的扩展,比如 __call__, __init__, __class__

格式化字符串

现在只推荐 f 函数,之前 format 等等都不要再使用。repr 用于输出 python 解释器识别的,str 用于输出用户识别的内容, 比如 repr("1")会输出 "1"(带引号),而 str 只会输出 1。 举例一些少见而有用的技巧

  1. f"{a=}" 或者 {sum(2)=} // 输出 a=1 sum(2)=22

  2. :表示格式化内容 :2f 保留两位小数(不四舍五入):.3% 保留 3 位小数的% :_ 分隔符,除了前面保留的几个特殊字符都可以。当然也能自由组合 :_.4f, :e 科学计数法 :.2e 保留两位小数的科学计数法

  3. 格式化时间 f{a:%Y/%m/%d}

  4. 文本对齐 f"a:>n" 文本前面插入空格,文本后面补齐空格

推导式

推导式通过执行一系列的循环用于快速的创建序列,按照语法糖来理解。我们要求无脑使用推导式,当然表达式性能也是有问题,会全部计算出来。不如 推导式总是按照 表达式 + 一层一层的 for 语句或者 if 语句。执行过程是 for if 执行完后执行 表示表达式,通过是

  • 列表推导
    • 创建 [x * 2 for x in range(5)] [x for x in range(10) if x % 2 == 0]
  • 字典推导式 {x: x + 1 for x in range(5) }

切片

用的最多在列表, [start🔚step] 每个参数都可以忽略返回 start 默认列表初始 end 默认列表 len, step 默认 1,负数时需要翻转。始终是 start+step 获取下一个索引,如果 start+step 不在区间内,始终返回[] 切片总是返回一个浅拷贝,同时切片也能作为一个独立对象,表示原对象的一部分,比如 a[:0] 表示的是列表开头 a[:0] = [1,2] 这样就能插入 1,2 到列表最开始

替换元素的执行逻辑是把起始索引到解锁索引的元素删除,再添加到起始位置。

序列

字符串、元组、字节串(不可变序列),列表、字节数组(不可变序列),怎么理解不可变序列, 比如你改动 a = "abc",想把"abc"改成了"abb",一定是创建了一个新的对象,而不是在原有的对象上改动的

描述器

可迭代对象

hash 和字典的关系

字典内部使用了 hash 表的方式来查询键。

函数

函数是怎么创建出来的

函数也是一个对象,这个对象有call方法,同理函数的生成也是元类在处理

函数中有用的特殊属性,为什么会有这些特殊的属性,怎么来的

  • doc 获取文档字符串, 也能获取模块的
  • name 函数名称
  • qualname 函数在调用时的层级名字 xx.xx.xx.name
  • defaults 默认参数组元组,
  • dict 命名空间中属性
  • annotations 获取参数的注解

class

super 详解

class 怎么来的

当解释器碰到class的申明时,会创建一个命名空间,同时添加默认的变量name class base等,同时会添加class的元类,用于如何实例化这个类,可以看到class本身也就是一个稍微特殊的对象,我们可以自由的定义class定义和最终实例的各个步骤

type

type是所有的class默认的元类,type 本身也是一个对象,同时也是一个可执行的对象,通常type(xxx)获取对象的类型,总是会调用这个对象的类对象的元类型的call方法。按照下面来执行

  1. 检查obj是否有一个名为class的属性,并且这个属性是一个类对象。
  2. 如果满足条件,直接返回obj.class
  3. 如果不满足条件,继续执行下一步。
  4. 检查obj的类对象是否有一个名为class的属性,并且这个属性是一个类对象。
  5. 如果满足条件,直接返回obj.class.class
  6. 如果不满足条件,继续执行下一步。
  7. 返回type(obj),即obj的类对象的元类。

执行帧、代码块和小数据池

python 中的对象是怎么来的 a = 1 干了嘛

添加一个a到命名空间,直接在小数据块中获取一个对象1,然后将a执行a对应的内存空间,简单理解成a -> id(1)

with 详解

上下文管理器是一个对象,包含了__enter__ 和__exit 方法。with 语句能够自动的调用这些方法

类型系统

typing.get_type_hints 能够获取 class,function 和模块中注解,当然也默认给其提供了__annotations__属性

模拟容器

官方内置了一些容器对象,比如元组、列表、字典、

常见的变量和属性

  • __dict__
  • __name__
  • __module__

全局函数