python英文官方文檔:https://docs.python.org/3.8/tutorial/index.html
比較不錯的python中文文檔:https://www.runoob.com/python3/python3-tutorial.html
1. 寫在前面
這幾周從實踐角度又學習了一遍python,溫故而知新,還是有蠻多心得的, 周末再看之前記的python筆記,總覺得零零散散, 未成體系,所以后面這段時間,陸續對之前的python筆記做一次整合, 使得內容更加清晰,成體系,做到簡單可依賴,既是復習,也方便以后回看回練。希望能幫助到更多的伙伴啦。
這是第二篇文章,主要整理python函數相關的內容,函數初識到高級特性再到函數式編程,層層遞進。既有基礎,又有新知識,這樣更有意思一些。
文章很長,內容很多,各取所需即可 ??
大綱如下:
- 1. 寫在前面
- 2. 函數基礎
- 2.1 函數初識
- 2.2 global和nonlocal
- 2.2 參數總結
- 2.2.1 默認參數
- 2.2.2 可變參數
- 2.2.3 關鍵字參數
- 2.2.4 命名關鍵字參數
- 2.2.5 使用總結
- 2.2.6 逆向參數收集
- 2.3 值傳遞和引用傳遞
- 2.4 高級特性
- 2.4.1 切片、迭代、列表生成
- 2.4.2 生成器
- 2.4.3 迭代器
- 2.5 給函數寫說明文檔
- 3. 函數式編程
- 3.1 高階函數
- 3.1.1 map/reduce
- 3.1.2 filter
- 3.1.3 sorted
- 3.1.4 使用小總
- 3.2 函數作為返回值
- 3.2.1 函數懶加載
- 3.2.2 閉包
- 3.3 匿名函數
- 3.4 裝飾器
- 3.5 偏函數
- 4. 常用內置函數
- 4.1 數學運算
- 4.2 邏輯運算
- 4.3 進制轉化
- 4.4 類型相關
- 4.5 類相關
- 4.6 eval和exec
- 5. 小總
Ok, let’s go!
2. 函數基礎
2.1 函數初識
函數,完成特定功能的代碼封裝, 一次編寫多次調用,避免反復寫代碼。這里先整理一些基礎操作。
# 1. 函數起別名: 函數名其實就是指向一個函數對象的引用,完全可以把函數名賦給一個變量
a = abs # 變量a指向abs函數
a(-1) # 所以也可以通過a調用abs函數
1
# 這個在用Pytorch搭建神經網絡的時候經常遇到這種起別名的操作# 2. 空函數 Python中有個比較騷的操作就是可以定義一個函數,但是什么都不寫
# # 那有什么用? 寫程序的時候,我們往往喜歡先搭一個簡單的框架出來, 把各種函數定義好放那,至于作用可能一時半會還沒想好,這時候就可以放一個pass, 讓代碼運行起來再說
def nop():passif age >= 18:pass # 如果這時候缺少pass,就會報語法錯誤# 3. Python貌似可以返回多個值呢? 但是真的是這樣嗎?
def move(x, y, step, angle=0):nx = x + step * math.cos(angle)ny = y - step * math.sin(angle)return nx, ny# 調用
a, b = move(x,y)
# 上面代碼看起來,好像python的return可以同時返回多個值, 其實這是一種假象, python函數返回的仍然是單一值,只不過這個單一值是一個元組。 在語法上,返回一個tuple可以省略括號,而多個變量可以同時接收一個tuple,按位置賦給對應的值,所以,Python的函數返回多值其實就是返回一個tuple,但寫起來更方便
2.2 global和nonlocal
在Python中,global
和nonlocal
關鍵字用于在不同作用域內訪問和修改變量。這兩個關鍵字在處理嵌套函數或全局變量時特別有用
# 1. global關鍵字用于在局部作用域中聲明全局變量。當你在一個函數內部想要修改外部定義的全局變量時,就需要使用global。
x = 5
def func():global x # 聲明x是全局變量x = 10 # 修改全局變量x的值
func()
print(x) # 輸出 10# 2. nonlocal關鍵字用于在閉包或嵌套函數中聲明一個變量指向非全局作用域,例如指向外層(但非全局)作用域的變量。它使得我們能夠修改位于嵌套作用域中的變量。
def outer():y = 5def inner():nonlocal y # 聲明y不是局部變量,而是外層函數的變量y = 10inner()print(y) # 輸出 10outer()# 注意:# 1. global關鍵字使得局部作用域可以修改全局作用域中的變量。# 2. nonlocal關鍵字確保變量指向最近的外層作用域(非全局作用域),并且可以在那個作用域中修改它。# 3. 不應當濫用global和nonlocal關鍵字,因為它們會使得代碼變得難以理解和維護。在可能的情況下,最好使用函數參數和返回值來傳遞和接收數據,而非依賴外部狀態。# 4. nonlocal不能用于訪問全局作用域的變量,它只適用于嵌套的局部作用域中。
如下,函數 f 里嵌套一個函數auto_increase。實現功能:不大于 10 時自增,否則置零后,再從零自增。
# 函數 f 里嵌套一個函數auto_increase。實現功能:不大于 10 時自增,否則置零后,再從零自增。
def f():i = 0def auto_increase():nonlocal i # 使用 nonlocal 告訴編譯器,i 不是局部變量 , 如果沒有這句話, 函數會報錯, 說i沒有在函數里面聲明賦值if i >= 10:i = 0 i += 1ret = []for _ in range(28):auto_increase()ret.append(i)print(ret)f() # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8]
2.2 參數總結
2.2.1 默認參數
python函數定義的時候可以給出默認參數, 這樣簡化了函數調用的難度, 無論是簡單調用還是復雜調用, 函數只需要定義一個即可。 舉個例子體會一下:
# 一個一年級小學生注冊的函數, 需要傳入name和gender兩個參數:
def enroll(name, gender):print(name)print(gender)# 這樣調用的時候只需要傳入兩個參數
enroll('Sarah', 'F') # 就會輸出名字和性別
但是如果我想傳入年齡, 城市等信息怎么辦, 上面那個就不能用了, 所以看看有默認參數的函數:
def enroll(name, gender, age=6, city='Beijing'):print('name:', name)print('gender:', gender)print('age:', age)print('city:', city)
這樣, 大多數學生注冊的時候不需要提供年齡和城市,只提供必須的兩個參數性別和姓名。 依然可以:
enroll('Sarah', 'F') # 但這個還可以更復雜的調用
enroll('Bob', 'M', 7)
enroll('Adam', 'M', city='Tianjin')
所以默認參數的使用還是非常方便的,在定義神經網絡函數的時候,就通常會有默認的學習率, 迭代次數等參數。 但也有一個注意的點: 首先就是設置的時候, 必選參數要放在前面,默認參數要放后面, 否則解釋器不知道你使用的是默認參數還是必選參數。 其次就是默認參數的順序, 變化大的放前面, 變化小的放后面。 最后,有多個默認參數時, 調用的時候既可以按順序提供默認參數,也可以不按順序提供, 但此時要把默認參數的參數名加上。(上面那個例子)
關于默認參數的使用, 還會有坑:默認參數必須指向不變對象!, 這是啥意思, 下面演示一個例子:我定義了一個函數, 傳入一個list
def add_end(L=[]):L.append('END')return L# 我正常調用
add_end([1, 2, 3]) # [1, 2, 3, 'END']
add_end(['x', 'y', 'z']) # ['x', 'y', 'z', 'END']# 我使用默認參數調用, 加了三次end
a = add_end()
b = add_end()
c = add_end()# 你知道執行之后, a, b, c都是什么值了嗎?
print(a) # ['END', 'END', 'END']
print(b) # ['END', 'END', 'END']
print(c) # ['END', 'END', 'END']
為什么默認參數是[]
, 但是函數似乎每次都“記住了”上次添加了’END’后的list呢? 這是因為Python函數在定義的時候, 默認參數L的值就被計算出來了,即[]
, 因為默認參數L也是一個變量, 它指向對象[]
, 每次調用該函數, 如果改變了L的內容, 則下次調用的時候, 默認參數的內容就變了,不再是函數定義時的[]
了。所以, 默認參數必須指向不變對象!。 要修改上面例子,我們可以使用不變對象None來實現:
def add_end(L=None):if L is None:L = []L.append('END')return La = add_end()
b = add_end()
c = add_end()
print(a) # ['END']
print(b) # ['END']
print(c) # ['END']# 可以和上面進行一個對比
為什么要設計str、None
這樣的不變對象呢? 因為不變對象一旦創建, 對象內部的數據就不能修改, 這樣就減少了由于修改數據導致的錯誤。 此外,由于對象不變, 多任務環境下同時讀取對象不需要加鎖,同時讀問題不會產生。 我們編寫程序時,如果可以設計一個不變對象, 那就盡量設計成不變的對象。 如果想查看一個函數使用了什么默認值,可以用fun_name.__defaults__
查看。
2.2.2 可變參數
在Python函數中,還可以定義可變參數。顧名思義,可變參數就是傳入的參數個數是可變的,可以是1個、2個到任意個,還可以是0個。 這個之前確實不知道是啥意思,所以在這里整理一下, 以一個數學題為例子:
給定一組數字a, b, c…, 計算 a 2 + b 2 + c 2 . . . . . a^2+b^2+c^2..... a2+b2+c2.....
要定義出這個函數,我們必須確定輸入的參數。由于參數個數不確定,我們首先想到可以把a,b,c……作為一個list或tuple傳進來
def calc(numbers):sum = 0for n in numbers:sum += n * nreturn sum# 分分鐘搞定, 然后我們調用, 得先組裝成一個數組
calc([1, 2, 3]) # 14
calc((1, 3, 5, 7)) # 84
如果利用可變參數的話,我們就可以不用先組裝成數組往里面傳:
def calc(*numbers):sum = 0for n in numbers:sum = sum + n * nreturn sum# 這時候調用
calc(1, 2, 3)
calc(1, 3, 5, 7)
# 甚至不傳都可以
calc()
定義可變參數和定義一個list或者tuple參數相比,僅僅在參數前面加了一個*
(可千萬別認為這成了指針了, python中就沒有指針一說),在函數內部, 參數numbers接收的是一個tuple, 因此函數代碼完全不變,調用該函數時,可以傳入任意個參數, 包括0個參數。
如果此時有了一個list或者tuple, 要調用一個可變參數怎么辦?
# 可以這樣做
nums = [1, 2, 3]
calc(nums[0], nums[1], nums[2])# 但上面這樣更加繁瑣,所以可以直接在nums前加*, 把list或tuple元素變成可變參數傳入:
calc(*nums)
這種寫法也是非常常見, *nums
表示把nums這個list的所有元素作為可變參數傳進去。
2.2.3 關鍵字參數
可變參數允許傳入0和或者任意個參數, 這些可變參數在調用時自動組裝成一個tuple。 而關鍵字參數允許傳入0個或者任意個含參數名的參數, 這些關鍵字參數在函數內部自動封裝成一個dict, 就是帶名字的參數,可以傳入任意個, 看例子就明白了:
def person(name, age, **kw):print(name, age, kw)person('Michael', 30) # Michael 30 {}
person('Bob', 35, city='Beijing') # Bob 35 {'city':'Beijing'}
person('Adam', 45, gender='M', job='Engineer') # Adam 45 {'gender':'M', 'job':'Engineer'}
關鍵字參數有什么用呢? 它可以擴展函數的功能。比如,在person函數里,我們保證能接收到name和age這兩個參數,但是,如果調用者愿意提供更多的參數,我們也能收到。試想你正在做一個用戶注冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足注冊的需求。和可變參數類似,也可以先組裝出一個dict,然后,把該dict轉換為關鍵字參數傳進去:
extra = {'city':'Beijing', 'job':'Engineer'}
person('Jack', 34, **extra) # name: Jack age: 34 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra
表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw
參數,kw將獲得一個dict,注意kw
獲得的dict是extra
的一份拷貝,對kw
的改動不會影響到函數外的extra
。
2.2.4 命名關鍵字參數
對于關鍵字參數,函數的調用者可以傳入任意不受限制的關鍵字參數,如果要限制關鍵字參數的名字,就可以用命名關鍵字參數:
def person(name, age, *, city, job):print(name, age, city, job)
和關鍵字參數**kw
不同,命名關鍵字參數需要一個特殊分隔符*
,*
后面的參數被視為命名關鍵字參數。調用的時候,必須傳入參數名, 否則,解釋器會報錯。
person('Jack', 24, city='Beijing', job='Engineer')
person('jack', 24, 'Beijing', 'Enginneer') # 這種會報錯, 解釋器會看成四個位置參數, 但其實person函數有兩個位置參數, 后面的兩個叫做命名關鍵字參數
如果函數定義中已經有了一個可變參數,后面跟著的命名關鍵字參數就不再需要一個特殊分隔符*
了:
def person(name, age, *args, city, job):print(name, age, args, city, job)
使用命名關鍵字參數時,要特別注意,如果沒有可變參數,就必須加一個*
作為特殊分隔符。如果缺少*
,Python解釋器將無法識別位置參數和命名關鍵字參數。
在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
def f1(a, b, c=0, *args, **kw):print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)def f2(a, b, c=0, *, d, **kw):print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
雖然可以組合多達5種參數,但不要同時使用太多的組合,否則函數接口的可理解性很差。 多了,自己都不知道咋調用了! 會出現這樣的一些報錯:
SyntaxError: positional argument follows keyword argument
,位置參數位于關鍵字參數后面TypeError: f() missing 1 required keyword-only argument: 'b'
,必須傳遞的關鍵字參數缺失SyntaxError: keyword argument repeated
,關鍵字參數重復TypeError: f() missing 1 required positional argument: 'b'
,必須傳遞的位置參數缺失TypeError: f() got an unexpected keyword argument 'a'
,沒有這個關鍵字參數TypeError: f() takes 0 positional arguments but 1 was given
,不需要位置參數但卻傳遞 1 個
2.2.5 使用總結
在實際編程的時候,最常用的是可變參數系列。可變參數可以讓函數的參數統一起來, 使得代碼更加美觀, 我在編程的時候非常喜歡使用**kwargs
# *args: 可以處理任意數量的位置參數, args這個名字約定俗成
# 這里的args將會是一個元組(tuple),包含了所有傳遞給函數的位置參數
def print_args(*args):for arg in args:print(arg)
nums = [1, 'two', 3.0]
print_args(nums[0], nums[1], nums[2])
print_args(*nums) # *nums表示把nums這個list的所有元素作為可變參數傳進去# **kwargs: 可處理任意數量的關鍵字參數, kwargs這個名字約定俗成
# 這里的kwargs將會是一個字典,其中包含了所有傳遞給函數的關鍵字參數。
#
def print_kwargs(**kwargs):for key, value in kwargs.items():print(f"{key} = {value}")print_kwargs(a=1, b='two', c=3.0)
test = {"a": 1, "b": 'two', "c": 3.0}
print_kwargs(**test) # **test表示把test這個dict的所有key-value用關鍵字參數傳入到函數的**kwargs參數# 實際場景使用# 有一個平臺審批流程有3條, 每一條對應的審批表單有些額外的信息不同,但基礎信息一樣,這時候我就希望把基礎的信息寫成位置參數,額外的信息用可變關鍵字參數使得3條審批流代碼統一
def create_test_artifact_process(app_token: str, artifact: artifact_dal.ArtifactModel, start_user: str, **kwargs) -> dict:
def create_sop_release_process(app_token: str, artifact: artifact_dal.ArtifactModel, start_user: str, **kwargs) -> dict:
def create_sop_father_process(app_token: str, artifact: artifact_dal.ArtifactModel, start_user: str, **kwargs) -> dict:
# 這時候,拿額外參數的時候,只需要kwargs.get("xxx") 去拿就好
2.2.6 逆向參數收集
在Python中,逆向參數收集是指過程中的相反操作:當你有一個序列(比如列表、元組)或字典,并希望將它們的元素作為單獨的參數傳遞給函數時使用。這可以通過*
(對于序列)和**
(對于字典)操作符實現,它們在調用函數時使用,將序列或字典拆解成單獨的參數
# 1. 使用*對序列進行逆向參數收集: 可以將序列中的每個元素都轉化為位置參數傳遞給函數
# 以逆向參數收集的方式,還可以給擁有可變位置參數的函數傳參, 見上面
def add(a, b, c):return a + b + cnumbers = [1, 2, 3]# 使用*操作符傳遞numbers列表中的元素作為單獨的參數
result = add(*numbers)
print(result) # 輸出6# 2. 使用**對字典進行逆向參數收集: 可以將字典中的每個鍵值對轉化為關鍵字參數和對應的值傳遞給函數。
# 以逆向參數收集的方式,還可以給擁有可變關鍵字參數的函數傳參, 見上面
def introduce(first_name, last_name, age):print(f"Hello, my name is {first_name} {last_name} and I am {age} years old.")person_info = {"first_name":