1?filter()
?函數
filter(function, iterable)
filter函數是python中的高階函數, 第一個參數是一個篩選函數, 第二個參數是一個可迭代對象, 返回的是一個生成器類型, 可以通過next獲取值。
filter()
?函數是 Python 內置的高階函數,其主要功能是對可迭代對象中的每個元素運用篩選函數進行判斷,然后把符合條件的元素以生成器的形式返回。下面為你詳細介紹它的用法和特性:
基礎語法
python
運行
filter(function, iterable)
這里的參數?function
?是一個用于篩選元素的函數,它會返回布爾值;iterable
?則是一個可迭代對象,像列表、元組、集合等都屬于此類。
核心特性
- 返回生成器:
filter()
?返回的是一個生成器對象,這意味著它是惰性求值的,只有在需要的時候才會生成值,這樣能有效節省內存。 - 篩選邏輯:當?
function
?返回?True
?時,對應的元素會被保留;若返回?False
,則該元素會被過濾掉。要是?function
?為?None
,filter()
?會保留?iterable
?中所有布爾值為?True
?的元素。
示例
示例 1:過濾偶數
python
運行
numbers = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, numbers)print(list(evens)) # 輸出: [2, 4, 6]
示例 2:過濾空字符串
python
運行
words = ["hello", "", "world", " ", None, "python"]
valid_words = filter(None, words) # 不傳篩選函數時,會過濾掉布爾值為 False 的元素print(list(valid_words)) # 輸出: ['hello', 'world', 'python']
示例 3:使用生成器特性
python
運行
numbers = [1, 2, 3, 4, 5]
filtered = filter(lambda x: x > 3, numbers)print(next(filtered)) # 輸出: 4
print(next(filtered)) # 輸出: 5
替代實現
你也可以通過列表推導式或者生成器表達式來實現相同的篩選功能:
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
運行
numbers = [1, 2, 3, 4] result = filter(lambda x: x > 2, numbers) # 保留大于 2 的數 print(list(result)) # 輸出: [3, 4]
當?
function
?為?None
?時:
會自動過濾掉任何布爾值為?False
?的元素(如?None
、0
、空字符串?""
、空列表?[]
?等)。python
運行
values = [0, "", None, "hello", 5] result = filter(None, values) # 保留布爾值為 True 的元素 print(list(result)) # 輸出: ['hello', 5]
底層邏輯
可以將?filter()
?函數等價理解為一個生成器表達式:
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
?在后,不要混淆。
以下代碼運行結果為: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
運行
char = 'A' # 這是一個長度為 1 的字符串,而非 char 類型
print(type(char)) # 輸出: <class 'str'>
為什么 Python 沒有?char
?類型?
- 字符串不可變:Python 的字符串是不可變序列,單個字符本質上就是長度為 1 的字符串,無需額外類型。
- Unicode 支持:Python 3 的?
str
?類型默認支持 Unicode,直接用?str
?表示字符更符合國際化需求。 - 簡化設計:避免引入多余類型,保持語言簡潔性(如 Java 中?
char
?是基本類型,而 Python 一切皆對象)。
字符操作示例
雖然沒有?char
?類型,但可以通過索引訪問字符串中的字符:
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
運行
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
運行
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_VALUE
4. 哪些代碼會生成?PyCodeObject
?
以下情況會生成獨立的?PyCodeObject
:
- 模塊(
.py
?文件):每個文件有一個全局的?PyCodeObject
。 - 函數定義:每個?
def
?語句生成一個(包括 lambda 函數)。 - 類定義:每個?
class
?語句生成一個(類體中的代碼會被編譯)。 - 生成器表達式:如?
(x for x in range(10))
。 - 嵌套的代碼塊:如方法、嵌套函數等。
5. 如何查看字節碼?
可以使用?dis
?模塊反匯編?PyCodeObject
:
python
運行
import disdef add(a, b):return a + bdis.dis(add) # 打印字節碼指令
輸出:
plaintext
2 0 LOAD_FAST 0 (a)2 LOAD_FAST 1 (b)4 BINARY_ADD6 RETURN_VALUE
總結
生成?PyCodeObject
?是 Python 執行代碼的必經之路,它將源代碼轉換為可執行的字節碼,讓 Python 兼具解釋型語言的靈活性和編譯型語言的部分性能優勢。理解這一點有助于解釋一些 Python 特性,如:
- 為什么函數定義是運行時執行的(因為需要生成?
PyCodeObject
)。 - 為什么修改源代碼后需要重新運行(除非使用?
.pyc
?緩存)。 - 為什么嵌套函數可以捕獲外部變量(
PyCodeObject
?保存了閉包信息)。
4?Python 作用域與 PyCodeObject
題目
Python 代碼經過編譯后,一共生成多少個 PyCodeObject 對象?
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
運行
# 作用域1:模塊級別(全局命名空間) class A: # 作用域2:類 A 的命名空間passdef Fun(): # 作用域3:函數 Fun 的命名空間passa = 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(): # 作用域1x = 10class Inner: # 作用域2passdef inner(): # 作用域3y = 20return inner
上述代碼包含?3 個 PyCodeObject(
outer
、Inner
、inner
?各一個)。
📝 總結
- 明確作用域邊界:類、函數、模塊是作用域的核心劃分單位。
- 區分編譯時與運行時:
- 編譯時生成 PyCodeObject(如類 / 函數定義)。
- 運行時執行代碼(如實例化 / 函數調用)不生成新的 PyCodeObject。
下次遇到同類題目的思考步驟
- 找出代碼中的模塊、類、函數定義。
- 統計獨立作用域的數量。
- 忽略實例化、函數調用等運行時操作。
5?Python中淺拷貝和深拷貝的區別,以及列表復制時的引用特性
執行以下程序,輸出結果為()
a = [['1','2'] for i in range(2)]b = [['1','2']]*2a[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']]答案:B
1. 列表初始化方式對比
方式 | 語法示例 | 對象關系 | 內存特性 |
---|---|---|---|
列表推導式 | 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
運行
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
運行
a = [['1','2'] for i in range(2)]
b = [['1','2']]*2print(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 中常見的引用陷阱,提升代碼的健壯性。