Python入门学习---第三天
函數
函數
基本上所有的高級語言都支持函數,Python也不例外。Python不但能非常靈活地定義函數,而且本身內置了很多有用的函數,可以直接調用。
抽象是數學中非常常見的概念。寫計算機程序也是一樣,函數就是最基本的一種代碼抽象的方式。
調用函數
Python內置了很多有用的函數,我們可以直接調用(這也正是吸引眾多程序員來學習python的原因)。
例如:abs函數,可以在交互式命令行通過help(abs)查看abs函數的幫助信息。
>>> abs(100) 100 >>> abs(-20) 20 >>> abs(12.34) 12.34# 調用函數的時候,如果傳入的參數數量不對,會報TypeError的錯誤, # 并且Python會明確地告訴你:abs()有且僅有1個參數,但給出了兩個: >>> abs(1, 2) Traceback (most recent call last):File "<stdin>", line 1, in <module> TypeError: abs() takes exactly one argument (2 given)# 而max函數max()可以接收任意多個參數,并返回最大的那個: >>> max(1, 2) 2 >>> max(2, 3, 1, -5) 3數據類型轉換
Python內置的常用函數還包括數據類型轉換函數。
# 比如int()函數可以把其他數據類型轉換為整數: >>> int('123') 123 >>> int(12.34) 12>>> float('12.34') 12.34 >>> str(1.23) '1.23' >>> str(100) '100' >>> bool(1) True >>> bool('') False函數名其實就是指向一個函數對象的引用。
# 完全可以把函數名賦給一個變量,相當于給這個函數起了一個“別名”: >>> a = abs # 變量a指向abs函數 >>> a(-1) # 所以也可以通過a調用abs函數 1練習
請利用Python內置的hex()函數把一個整數轉換成十六進制表示的字符串:
# -*- coding: utf-8 -*- n1 = 255 n2 = 10000xff 0x3e8調用Python的函數,需要根據函數定義,傳入正確的參數。如果函數調用出錯,一定要學會看錯誤信息,所以英文很重要!
定義函數
在Python中,定義一個函數要使用def語句,依次寫出函數名、括號、括號中的參數和冒號 : ,然后,在縮進塊中編寫函數體,函數的返回值用return語句返回。
# -*- coding: utf-8 -*- # 我們以自定義一個求絕對值的my_abs函數為例: def my_abs(x):if x >= 0:return xelse:return -x如果你已經把 my_abs() 的函數定義保存為abstest.py文件了,那么,可以在該文件的當前目錄下啟動Python解釋器,用 from abstest import my_abs 來導入 my_abs() 函數,注意abstest是文件名(不含.py擴展名):
如下:
>>> from abstest import my_abs >>> my_abs(-9) 9import的用法在后續模塊一節中會詳細介紹。
空函數
如果想定義一個什么事也不做的空函數,可以用 pass語句:
def nop():passpass語句什么都不做,那有什么用?實際上pass可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個pass,讓代碼能運行起來。
pass還可以用在其他語句里,比如:
# 缺少了pass,代碼運行就會有語法錯誤。 if age >= 18:pass參數檢查
調用函數時,如果參數個數不對,Python解釋器會自動檢查出來,并拋出TypeError:
但是如果參數類型不對,Python解釋器就無法幫我們檢查。試試my_abs和內置函數abs的差別:
>>> my_abs('A') Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 2, in my_abs TypeError: unorderable types: str() >= int() >>> abs('A') Traceback (most recent call last):File "<stdin>", line 1, in <module> TypeError: bad operand type for abs(): 'str'當傳入了不恰當的參數時,內置函數abs會檢查出參數錯誤,而我們定義的my_abs沒有參數檢查,會導致if語句出錯,出錯信息和abs不一樣。所以,這個函數定義不夠完善。
讓我們修改一下my_abs的定義,對參數類型做檢查,只允許整數和浮點數類型的參數。數據類型檢查可以用內置函數isinstance()實現:
def my_abs(x):if not isinstance(x, (int, float)):raise TypeError('bad operand type')if x >= 0:return xelse:return -x添加了參數檢查后,如果傳入錯誤的參數類型,函數就可以拋出一個錯誤:
>>> my_abs('A') Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 3, in my_abs TypeError: bad operand type返回多個值
函數可以返回多個值嗎?答案是肯定的。
比如在游戲中經常需要從一個點移動到另一個點,給出坐標、位移和角度,就可以計算出新的新的坐標:
import mathdef move(x, y, step, angle=0):nx = x + step * math.cos(angle)ny = y - step * math.sin(angle)return nx, nyimport math 語句表示導入math包,并允許后續代碼引用math包里的sin、cos等函數。
然后,我們就可以同時獲得返回值:
>>> x, y = move(100, 100, 60, math.pi / 6) >>> print(x, y) 151.96152422706632 70.0但其實這只是一種假象,Python函數返回的仍然是單一值:
>>> r = move(100, 100, 60, math.pi / 6) >>> print(r) (151.96152422706632, 70.0)原來返回值是一個tuple!但是,在語法上,返回一個tuple可以省略括號,而多個變量可以同時接收一個tuple,按位置賦給對應的值,所以,Python的函數返回多值其實就是返回一個tuple,但寫起來更方便。
小結
- 定義函數時,需要確定函數名和參數個數;
- 如果有必要,可以先對參數的數據類型做檢查;
- 函數體內部可以用return隨時返回函數結果;
- 函數執行完畢也沒有return語句時,自動return None。
- 函數可以同時返回多個值,但其實就是一個tuple。
練習
請定義一個函數quadratic(a, b, c),接收3個參數,返回一元二次方程:
ax2+bx+c=0ax2+bx+c=0 的兩個解。 # 提示:計算平方根可以調用math.sqrt()函數: # -*- coding: utf-8 -*-import math def quadratic(a, b, c):x1 = (-b + math.sqrt(b*b-4*a*c))/(2*a)x2 = (-b - math.sqrt(b*b-4*a*c))/(2*a)return x1,x2print('quadratic(2, 3, 1) =', quadratic(2, 3, 1)) print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))if quadratic(2, 3, 1) != (-0.5, -1.0):print('測試失敗') elif quadratic(1, 3, -4) != (1.0, -4.0):print('測試失敗') else:print('測試成功')函數的參數
與其他語言不同點太多
遞歸函數
在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。
舉個例子,我們來計算 階乘n! = 1 x 2 x 3 x … x n ,用函數fact(n)表示,可以看出:
def fact(n):if n==1:return 1return n * fact(n - 1)>>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000使用遞歸函數需要注意防止棧溢出。在計算機中,函數調用是通過棧(stack)這種數據結構實現的(其他語言也是如此,包括匯編語言,也有兩個寄存器實現棧的功能),每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。
由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。可以試試fact(1000):
>>> fact(1000) Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 4, in fact...File "<stdin>", line 4, in fact RuntimeError: maximum recursion depth exceeded in comparison解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
def fact(n):return fact_iter(n, 1)def fact_iter(num, product):if num == 1:return productreturn fact_iter(num - 1, num * product)我們來看看遞歸跟尾遞歸的區別:
遞歸過程:
===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。
尾遞歸過程:
===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數本身,num - 1和num * product在函數調用前就會被計算,不影響函數調用。
尾遞歸調用時,如果做了優化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。
遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python解釋器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞歸方式,也會導致棧溢出。
小結
使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。
練習
遞歸函數
閱讀: 387346
在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。
舉個例子,我們來計算階乘n! = 1 x 2 x 3 x … x n,用函數fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示為n x fact(n-1),只有n=1時需要特殊處理。
于是,fact(n)用遞歸的方式寫出來就是:
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
上面就是一個遞歸函數。可以試試:
fact(1)
1
fact(5)
120
fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
如果我們計算fact(5),可以根據函數定義看到計算過程如下:
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
使用遞歸函數需要注意防止棧溢出。在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。可以試試fact(1000):
>>> fact(1000) Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 4, in fact...File "<stdin>", line 4, in fact RuntimeError: maximum recursion depth exceeded in comparison解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
尾遞歸是指,在函數返回的時候,調用自身本身,并且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況。
上面的fact(n)函數由于return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:
def fact(n):return fact_iter(n, 1)def fact_iter(num, product):if num == 1:return productreturn fact_iter(num - 1, num * product)可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數本身,num - 1和num * product在函數調用前就會被計算,不影響函數調用。
fact(5)對應的fact_iter(5, 1)的調用如下:
===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120
尾遞歸調用時,如果做了優化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。
遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python解釋器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞歸方式,也會導致棧溢出。
小結
使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。
練習
漢諾塔的移動可以用遞歸函數非常簡單地實現。
請編寫move(n, a, b, c)函數,它接收參數n,表示3個柱子A、B、C中第1個柱子A的盤子數量,然后打印出把所有盤子從A借助B移動到C的方法,例如:
# -*- coding: utf-8 -*- def move(n, a, b, c): if n == 1: # 如果a只有1盤子print(a, '-->', c); # 直接把盤子從a移到celse: # 如果a有n個盤子(n > 1),那么分三步move(n-1, a, c, b) # 先把上面n-1個盤子,借助c,從a移到bmove(1, a, b, c) # 再把最下面的1個盤子,借助b,從a移到cmove(n-1, b, a, c) # 最后把n-1個盤子,借助a,從b移到cmove(4,'A','B','C') # 測試轉載處:廖雪峰Python教程
總結
以上是生活随笔為你收集整理的Python入门学习---第三天的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 企业内部自建用户行为分析平台全过程
- 下一篇: 互联网日报 | 华为Mate40系列国内