1 filter() 函数
filter(function, iterable)
filter函数是python中的高阶函数, 第一个参数是一个筛选函数, 第二个参数是一个可迭代对象, 返回的是一个生成器类型, 可以通过next获取值。
filter() 函数是 Python 内置的高阶函数,其主要功能是对可迭代对象中的每个元素运用筛选函数进行判断,然后把符合条件的元素以生成器的形式返回。下面为你详细介绍它的用法和特性:
基础语法
python
运行
            
            
              python
              
              
            
          
          filter(function, iterable)这里的参数 function 是一个用于筛选元素的函数,它会返回布尔值;iterable 则是一个可迭代对象,像列表、元组、集合等都属于此类。
核心特性
- 返回生成器 :filter()返回的是一个生成器对象,这意味着它是惰性求值的,只有在需要的时候才会生成值,这样能有效节省内存。
- 筛选逻辑 :当 function返回True时,对应的元素会被保留;若返回False,则该元素会被过滤掉。要是function为None,filter()会保留iterable中所有布尔值为True的元素。
示例
示例 1:过滤偶数
python
运行
            
            
              python
              
              
            
          
          numbers = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))  # 输出: [2, 4, 6]示例 2:过滤空字符串
python
运行
            
            
              python
              
              
            
          
          words = ["hello", "", "world", "  ", None, "python"]
valid_words = filter(None, words)  # 不传筛选函数时,会过滤掉布尔值为 False 的元素
print(list(valid_words))  # 输出: ['hello', 'world', 'python']示例 3:使用生成器特性
python
运行
            
            
              python
              
              
            
          
          numbers = [1, 2, 3, 4, 5]
filtered = filter(lambda x: x > 3, numbers)
print(next(filtered))  # 输出: 4
print(next(filtered))  # 输出: 5替代实现
你也可以通过列表推导式或者生成器表达式来实现相同的筛选功能:
python
运行
            
            
              python
              
              
            
          
          # 列表推导式
evens = [x for x in numbers if x % 2 == 0]
# 生成器表达式(更节省内存)
evens_gen = (x for x in numbers if x % 2 == 0)总结
filter() 函数特别适合用于数据筛选场景,它返回的生成器能高效处理大规模数据。不过在 Python 3 中,需要注意 filter() 返回的不再是列表,而是生成器,这一点和 Python 2 有所不同。
filter返回符合后面表达式为真的部分
filter() 函数会保留 function 返回值为 True 的元素 ,过滤掉返回 False 的元素。具体规则如下:
- 
当 function是自定义函数或 lambda 时 :只有当函数对元素的计算结果为 True时,该元素才会被保留。python 运行 pythonnumbers = [1, 2, 3, 4] result = filter(lambda x: x > 2, numbers) # 保留大于 2 的数 print(list(result)) # 输出: [3, 4]
- 
当 function为None时 :会自动过滤掉任何布尔值为 False的元素(如None、0、空字符串""、空列表[]等)。python 运行 pythonvalues = [0, "", None, "hello", 5] result = filter(None, values) # 保留布尔值为 True 的元素 print(list(result)) # 输出: ['hello', 5]
底层逻辑
可以将 filter() 函数等价理解为一个生成器表达式:
python
运行
            
            
              python
              
              
            
          
          # 以下两种写法等价
filter(function, iterable)
(x for x in iterable if function(x))  # 当 function 不为 None 时
(x for x in iterable if x)            # 当 function 为 None 时注意事项
返回值是生成器 :需要用 list() 转换为列表(或用 next() 逐个获取值)。
函数参数的顺序 :function 在前,iterable 在后,不要混淆。
            
            
              python
              
              
            
          
          以下代码运行结果为:
func = lambda x:x%2
result = filter(func, [1, 2, 3, 4, 5])
print(list(result))
正确答案:[1,3,5]2 在 Python 中,没有单独的 char(字符)类型
Python的主要内置数据类型包括:
- 
数值类型:int(整数)、float(浮点数)、complex(复数) 
- 
序列类型:list(列表)、tuple(元组)、range(范围) 
- 
字符串类型:str - 映射类型:dict(字典) 
- 
集合类型:set(集合)、frozenset(不可变集合) 
- 
布尔类型:bool 
- 
空值类型:None 
所以char并不在Python的数据类型体系中。
而是用长度为 1 的字符串 str 来表示单个字符。例如:
python
运行
            
            
              python
              
              
            
          
          char = 'A'  # 这是一个长度为 1 的字符串,而非 char 类型
print(type(char))  # 输出: <class 'str'>为什么 Python 没有 char 类型?
- 字符串不可变:Python 的字符串是不可变序列,单个字符本质上就是长度为 1 的字符串,无需额外类型。
- Unicode 支持 :Python 3 的 str类型默认支持 Unicode,直接用str表示字符更符合国际化需求。
- 简化设计 :避免引入多余类型,保持语言简洁性(如 Java 中 char是基本类型,而 Python 一切皆对象)。
字符操作示例
虽然没有 char 类型,但可以通过索引访问字符串中的字符:
python
运行
            
            
              python
              
              
            
          
          s = "hello"
first_char = s[0]  # 'h'
print(type(first_char))  # <class 'str'>与其他语言的对比
| 语言 | 字符类型 | 字符串类型 | 
|---|---|---|
| Python | str(长度 = 1) | str | 
| Java | char | String | 
| C/C++ | char | char[]或std::string | 
如果你需要处理字符编码(如 ASCII/Unicode 值),可以使用 ord() 和 chr() 函数:
python
运行
            
            
              python
              
              
            
          
          char = 'A'
code = ord(char)  # 65 (Unicode 码点)
back_to_char = chr(code)  # 'A'这种设计让 Python 在处理文本时更加统一和灵活,无需担心字符与字符串的类型转换问题。 😊
3 生成一个PyCodeObject对象是什么意思?
在 Python 中,生成一个 PyCodeObject 对象 意味着将源代码编译为 Python 虚拟机(VM)可以执行的中间表示形式,也称为 字节码(bytecode)。这个过程是 Python 执行代码的关键步骤,理解它有助于掌握 Python 的运行机制。
1. 什么是 PyCodeObject?
PyCodeObject 是 CPython 解释器(用 C 语言实现的 Python 官方解释器)中的一个核心数据结构,它存储了编译后的代码信息,包括:
- 字节码指令 :如 LOAD_CONST、CALL_FUNCTION等,类似于汇编语言。
- 常量池:存储数字、字符串等常量。
- 局部变量信息:变量名、位置等。
- 源代码位置:用于调试(如错误堆栈跟踪)。
可以将 PyCodeObject 看作是源代码的 编译产物 ,类似于 Java 的 .class 文件或 C 的目标文件(.o)。
2. 为什么需要 PyCodeObject?
**Python 是 解释型语言,但执行前需要先将源代码编译为字节码,**原因如下:
- 跨平台性:字节码可以在任何 Python 解释器上运行,无需重新编译。
- 性能优化 :编译一次,多次执行(字节码可以缓存,如 .pyc文件)。
- 简化解释器逻辑:解释器只需执行字节码,无需处理源代码的语法解析。
3. 生成 PyCodeObject 的过程
当你运行 Python 代码时:
- 词法分析 :将源代码转换为 token(如 def、class、变量名)。
- 语法分析:构建抽象语法树(AST)。
- 代码生成 :将 AST 转换为 PyCodeObject(包含字节码)。
这个过程由 Python 解释器自动完成,例如:
python
运行
            
            
              python
              
              
            
          
          def add(a, b):
    return a + b
# 编译后,add 函数对应一个 PyCodeObject,包含以下字节码:
#   2           0 LOAD_FAST                0 (a)
#               2 LOAD_FAST                1 (b)
#               4 BINARY_ADD
#               6 RETURN_VALUE4. 哪些代码会生成 PyCodeObject?
以下情况会生成独立的 PyCodeObject:
- 模块(.py文件) :每个文件有一个全局的PyCodeObject。
- 函数定义 :每个 def语句生成一个(包括 lambda 函数)。
- 类定义 :每个 class语句生成一个(类体中的代码会被编译)。
- 生成器表达式 :如 (x for x in range(10))。
- 嵌套的代码块:如方法、嵌套函数等。
5. 如何查看字节码?
可以使用 dis 模块反汇编 PyCodeObject:
python
运行
            
            
              python
              
              
            
          
          import dis
def add(a, b):
    return a + b
dis.dis(add)  # 打印字节码指令输出:
plaintext
  2           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE总结
生成 PyCodeObject 是 Python 执行代码的必经之路,它将源代码转换为可执行的字节码,让 Python 兼具解释型语言的灵活性和编译型语言的部分性能优势。理解这一点有助于解释一些 Python 特性,如:
- 为什么函数定义是运行时执行的(因为需要生成 PyCodeObject)。
- 为什么修改源代码后需要重新运行(除非使用 .pyc缓存)。
- 为什么嵌套函数可以捕获外部变量(PyCodeObject保存了闭包信息)。
4 Python 作用域与 PyCodeObject
题目
Python 代码经过编译后,一共生成多少个 PyCodeObject 对象?
python
运行
            
            
              python
              
              
            
          
          class A:
    pass
def Fun():
    pass
a = A()
Fun()选项
A. 1
B. 2
C. 3
D. 4
你的答案
B. 2
正确答案
C. 3
🔍 错误原因分析
- 
混淆作用域的定义 - 误将类实例化(a = A())和函数调用(Fun())视为独立作用域。
- 实际上,只有模块、类定义、函数定义会创建新的作用域。
 
- 误将类实例化(
- 
对 PyCodeObject 的生成规则理解不深 - 
每个独立作用域(名字空间)对应一个 PyCodeObject。 
- 
题目中存在 3 个作用域 : python 运行 python# 作用域1:模块级别(全局命名空间) class A: # 作用域2:类 A 的命名空间 pass def Fun(): # 作用域3:函数 Fun 的命名空间 pass a = A() # 全局命名空间中的语句 Fun() # 全局命名空间中的语句
 
- 
📚 关键知识点
- 
PyCodeObject 生成规则 结构 是否生成 PyCodeObject 示例 模块( .py文件)✅ 整个代码文件 类定义( class)✅ class A: pass函数定义( def)✅ def Fun(): pass实例化对象 ❌ a = A()函数调用 ❌ Fun()普通语句 ❌ 赋值、条件判断、循环等 
- 
验证方法 通过 __code__属性查看对象对应的 PyCodeObject(实际是code对象):python 运行 print(A.__code__) # 类体对应的 code 对象 print(Fun.__code__) # 函数体对应的 code 对象 print(__code__) # 当前模块对应的 code 对象
💡 记忆技巧
- 
作用域划分口诀 模块类函数,作用域三分;实例与调用,作用域不分。 
- 
嵌套作用域示例 python 运行 def outer(): # 作用域1 x = 10 class Inner: # 作用域2 pass def inner(): # 作用域3 y = 20 return inner上述代码包含 3 个 PyCodeObject ( outer、Inner、inner各一个)。
📝 总结
- 明确作用域边界:类、函数、模块是作用域的核心划分单位。
- 区分编译时与运行时 :
- 编译时生成 PyCodeObject(如类 / 函数定义)。
- 运行时执行代码(如实例化 / 函数调用)不生成新的 PyCodeObject。
 
下次遇到同类题目的思考步骤
- 找出代码中的模块、类、函数定义。
- 统计独立作用域的数量。
- 忽略实例化、函数调用等运行时操作。
5 Python中浅拷贝和深拷贝的区别,以及列表复制时的引用特性
            
            
              python
              
              
            
          
          执行以下程序,输出结果为()
a = [['1','2'] for i in range(2)]
b = [['1','2']]*2
a[0][1] = '3'
b[0][0] = '4'
print(a,b) 
A [['1', '3'], ['1', '3']] [['4', '2'], ['4', '2']]
B [['1', '3'], ['1', '2']] [['4', '2'], ['4', '2']]
C [['1', '3'], ['1', '2']] [['4', '2'], ['1', '2']]
D [['1', '3'], ['1', '3']] [['4', '2'], ['1', '2']]
答案:B1. 列表初始化方式对比
| 方式 | 语法示例 | 对象关系 | 内存特性 | 
|---|---|---|---|
| 列表推导式 | a = [['1','2'] for _ in range(2)] | 创建多个独立对象 | 子列表内存地址不同 | 
| 乘法操作 | b = [['1','2']] * 2 | 复制同一对象的引用 | 子列表内存地址相同 | 
2. 浅拷贝与深拷贝的区别
- 
浅拷贝 : 仅复制容器(如列表)本身,内部元素仍为原对象的引用。 
 示例 :b = [['1','2']] * 2中,子列表['1','2']被重复引用。
- 
深拷贝 : 递归复制容器及其所有嵌套对象,生成完全独立的新对象。 
 实现方式 :使用copy.deepcopy()函数。
3. 引用特性导致的修改行为差异
- 
独立对象(列表推导式) : 修改任一子列表仅影响当前对象。 
 示例 :a[0][1] = '3'仅修改a[0]。
- 
共享引用(乘法操作) : 修改任一子列表会影响所有引用同一对象的元素。 
 示例 :b[0][0] = '4'同时修改b[0]和b[1]。
4. 验证对象身份的方法
使用 id() 函数检查内存地址:
python
运行
            
            
              python
              
              
            
          
          a = [['1','2'] for _ in range(2)]
b = [['1','2']] * 2
print(id(a[0]) == id(a[1]))  # False(独立对象)
print(id(b[0]) == id(b[1]))  # True(共享引用)5. 实际应用中的注意事项
- 
多维数组初始化 : 初始化多维列表时,避免使用 [[val]*n]*m(如[[0]*3]*3),应使用列表推导式[[val for _ in range(n)] for _ in range(m)]。
- 
数据处理场景 : 在需要独立操作子元素的场景(如矩阵运算、数据分组)中,确保使用独立对象初始化。 
6. 总结
- 列表推导式适合创建独立的嵌套结构。
- 乘法操作仅复制引用,适用于不可变元素(如整数、字符串),但不适合嵌套可变对象(如列表)。
- 使用 id()或is操作符验证对象身份,避免因引用共享导致的意外修改。
为了更直观地验证这一行为,我们可以通过以下代码检查对象的内存地址:
python
运行
            
            
              python
              
              
            
          
          a = [['1','2'] for i in range(2)]
b = [['1','2']]*2
print(f"a[0] 地址: {id(a[0])}")  # 不同地址
print(f"a[1] 地址: {id(a[1])}")
print(f"b[0] 地址: {id(b[0])}")  # 相同地址
print(f"b[1] 地址: {id(b[1])}")
a[0][1] = '3'
b[0][0] = '4'
print("修改后 a:", a)
print("修改后 b:", b)输出结果:
plaintext
a[0] 地址: 140281423443648
a[1] 地址: 140281423443904
b[0] 地址: 140281423443776
b[1] 地址: 140281423443776
修改后 a: [['1', '3'], ['1', '2']]
修改后 b: [['4', '2'], ['4', '2']]关键点:
- 列表推导式创建独立对象 :a的子列表内存地址不同,说明它们是独立的对象。
- 乘法操作复制引用 :b的子列表内存地址相同,说明它们指向同一个对象。
- 修改行为验证 :对 b的任一子列表的修改会反映到所有引用中,而a的子列表相互独立。
这一特性在处理嵌套数据结构时尤为重要,例如矩阵操作或多维数组的初始化。理解引用机制可以避免潜在的逻辑错误。
理解这些特性可以有效避免 Python 中常见的引用陷阱,提升代码的健壮性。