一 函數是什么?
函數一詞來源於數學,但編程中的「函數」概念,與數學中的函數是有很大不同的,具體區別,我們后面會講,編程中的函數在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子過程或子程序),在Pascal中叫做procedure(過程)和function,在C中只有function,在Java里面叫做method。
函數能提高應用的模塊性,和代碼的重復利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。
定義: 函數是指將一組語句的集合通過一個名字(函數名)封裝起來,要想執行這個函數,只需調用其函數名即可
特性:
1.代碼重用
2.保持一致性
3.可擴展性
二 函數的創建
2.1 格式:
Python 定義函數使用 def 關鍵字,一般格式如下:
def 函數名(參數列表): 函數體
def hello():
print('hello')
hello()#調用
2.2 函數名的命名規則:
- 函數名必須以下划線或字母開頭,可以包含任意字母、數字或下划線的組合。不能使用任何的標點符號;
- 函數名是區分大小寫的。
- 函數名不能是保留字。
2.3 形參和實參
形參:形式參數,不是實際存在,是虛擬變量。在定義函數和函數體的時候使用形參,目的是在函數調用時接收實參(實參個數,類型應與實參一一對應)
實參:實際參數,調用函數時傳給函數的參數,可以是常量,變量,表達式,函數,傳給形參
區別:形參是虛擬的,不占用內存空間,.形參變量只有在被調用時才分配內存單元,實參是一個變量,占用內存空間,數據傳送單向,實參傳給形參,不能形參傳給實參
import time times=time.strftime('%Y--%m--%d') def f(time): print('Now time is : %s'%times) f(times)
2.4 實例
實例1:
def show_shopping_car(): saving=1000000 shopping_car=[ ('Mac',9000), ('kindle',800), ('tesla',100000), ('Python book',105), ] print('您已經購買的商品如下'.center(50,'*')) for i ,v in enumerate(shopping_car,1): print('\033[35;1m %s: %s \033[0m'%(i,v)) expense=0 for i in shopping_car: expense+=i[1] print('\n\033[32;1m您的余額為 %s \033[0m'%(saving-expense)) show_shopping_car()
實例2:
現在我們就用一個例子來說明函數的三個特性:

def action1(n): print ('starting action1...') with open('日志記錄','a') as f: f.write('end action%s\n'%n) def action2(n): print ('starting action2...') with open('日志記錄','a') as f: f.write('end action%s\n'%n) def action3(n): print ('starting action3...') with open('日志記錄','a') as f: f.write('end action%s\n'%n) action1(1) action2(2) action3(3) ##***************代碼重用 def logger(n): with open('日志記錄','a') as f: f.write('end action%s\n'%n) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() ##***************可擴展和保持一致 ##為日志加上時間 import time def logger(n): time_format='%Y-%m-%d %X' time_current=time.strftime(time_format) with open('日志記錄','a') as f: f.write('%s end action%s\n'%(time_current,n)) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3()
三 函數的參數
- 必備參數
- 關鍵字參數
- 默認參數
- 不定長參數
必需的參數:
def f(name,age): print('I am %s,I am %d'%(name,age)) f('alex',18) f('alvin',16)
關鍵字參數:
關鍵字參數和函數調用關系緊密,函數調用使用關鍵字參數來確定傳入的參數值。使用關鍵字參數允許函數調用時參數的順序與聲明時不一致,因為 Python 解釋器能夠用參數名匹配參數值。
def f(name,age): print('I am %s,I am %d'%(name,age)) # f(16,'alvin') #報錯 f(age=16,name='alvin')
缺省參數(默認參數):
調用函數時,缺省參數的值如果沒有傳入,則被認為是默認值。下例會打印默認的age,如果age沒有被傳入:
def print_info(name,age,sex='male'): print('Name:%s'%name) print('age:%s'%age) print('Sex:%s'%sex) return print_info('alex',18) print_info('鐵錘',40,'female')
不定長參數
你可能需要一個函數能處理比當初聲明時更多的參數。這些參數叫做不定長參數,和上述2種參數不同,聲明時不會命名。
# def add(x,y): # return x+y def add(*tuples): sum=0 for v in tuples: sum+=v return sum print(add(1,4,6,9)) print(add(1,4,6,9,5))
加了星號(*)的變量名會存放所有未命名的變量參數。而加(**)的變量名會存放命名的變量參數
def print_info(**kwargs): print(kwargs) for i in kwargs: print('%s:%s'%(i,kwargs[i]))#根據參數可以打印任意相關信息了 return print_info(name='alex',age=18,sex='female',hobby='girl',nationality='Chinese',ability='Python') ###########################位置 def print_info(name,*args,**kwargs):#def print_info(name,**kwargs,*args):報錯 print('Name:%s'%name) print('args:',args) print('kwargs:',kwargs) return print_info('alex',18,hobby='girl',nationality='Chinese',ability='Python') # print_info(hobby='girl','alex',18,nationality='Chinese',ability='Python') #報錯 #print_info('alex',hobby='girl',18,nationality='Chinese',ability='Python') #報錯
注意,還可以這樣傳參:
def f(*args): print(args) f(*[1,2,5]) def f(**kargs): print(kargs) f(**{'name':'alex'})
補充(高階函數):
高階函數是至少滿足下列一個條件的函數:
-
-
- 接受一個或多個函數作為輸入
- 輸出一個函數
-
def add(x,y,f): return f(x) + f(y) res = add(3,-6,abs) print(res) ############### def foo(): x=3 def bar(): return x return bar
四 函數的返回值
要想獲取函數的執行結果,就可以用return語句把結果返回
注意:
- 函數在執行過程中只要遇到return語句,就會停止執行並返回結果,so 也可以理解為 return 語句代表着函數的結束
- 如果未在函數中指定return,那這個函數的返回值為None
- return多個對象,解釋器會把這多個對象組裝成一個元組作為一個一個整體結果輸出。
五 作用域
5.1 作用域介紹
python中的作用域分4種情況:
- L:local,局部作用域,即函數中定義的變量;
- E:enclosing,嵌套的父級函數的局部作用域,即包含此函數的上級函數的局部作用域,但不是全局的;
- G:globa,全局變量,就是模塊級別定義的變量;
- B:built-in,系統固定模塊里面的變量,比如int, bytearray等。 搜索變量的優先級順序依次是:作用域局部>外層作用域>當前模塊中的全局>python內置作用域,也就是LEGB。
x = int(2.9) # int built-in g_count = 0 # global def outer(): o_count = 1 # enclosing def inner(): i_count = 2 # local print(o_count) # print(i_count) 找不到 inner() outer() # print(o_count) #找不到
當然,local和enclosing是相對的,enclosing變量相對上層來說也是local。
5.2 作用域產生
在Python中,只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如if、try、for等)是不會引入新的作用域的,如下代碼:
if 2>1: x = 1 print(x) # 1
這個是沒有問題的,if並沒有引入一個新的作用域,x仍處在當前作用域中,后面代碼可以使用。
def test(): x = 2 print(x) # NameError: name 'x2' is not defined
def、class、lambda是可以引入新作用域的。
5.3 變量的修改
################# x=6 def f2(): print(x) x=5 f2() # 錯誤的原因在於print(x)時,解釋器會在局部作用域找,會找到x=5(函數已經加載到內存),但x使用在聲明前了,所以報錯: # local variable 'x' referenced before assignment.如何證明找到了x=5呢?簡單:注釋掉x=5,x=6 # 報錯為:name 'x' is not defined #同理 x=6 def f2(): x+=1 #local variable 'x' referenced before assignment. f2()
5.4 global關鍵字
當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字了,當修改的變量是在全局作用域(global作用域)上的,就要使用global先聲明一下,代碼如下:
count = 10 def outer(): global count print(count) count = 100 print(count) outer() #10 #100
5.5 nonlocal關鍵字
global關鍵字聲明的變量必須在全局作用域上,不能嵌套作用域上,當要修改嵌套作用域(enclosing作用域,外層非全局作用域)中的變量怎么辦呢,這時就需要nonlocal關鍵字了
def outer(): count = 10 def inner(): nonlocal count count = 20 print(count) inner() print(count) outer() #20 #20
5.6 小結
(1)變量查找順序:LEGB,作用域局部>外層作用域>當前模塊中的全局>python內置作用域;
(2)只有模塊、類、及函數才能引入新作用域;
(3)對於一個變量,內部作用域先聲明就會覆蓋外部變量,不聲明直接使用,就會使用外部作用域的變量;
(4)內部作用域要修改外部作用域變量的值時,全局變量要使用global關鍵字,嵌套作用域變量要使用nonlocal關鍵字。nonlocal是python3新增的關鍵字,有了這個 關鍵字,就能完美的實現閉包了。
六 遞歸函數
定義:在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。
實例1(階乘)
def factorial(n): result=n for i in range(1,n): result*=i return result print(factorial(4)) #**********遞歸********* def factorial_new(n): if n==1: return 1 return n*factorial_new(n-1) print(factorial_new(3))
實例2(斐波那契數列)
def fibo(n): before=0 after=1 for i in range(n-1): ret=before+after before=after after=ret return ret print(fibo(3)) #**************遞歸********************* def fibo_new(n):#n可以為零,數列有[0] if n <= 1: return n return(fibo_new(n-1) + fibo_new(n-2)) print(fibo_new(3))
print(fibo_new(30000))#maximum recursion depth exceeded in comparison
遞歸函數的優點: 是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
遞歸特性:
1. 必須有一個明確的結束條件
2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少
3. 遞歸效率不高,遞歸層次過多會導致棧溢出(在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返 回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。)
七 內置函數(Py3.5)
py2內置函數:https://docs.python.org/3.5/library/functions.html#repr
重要的內置函數:
1 filter(function, sequence)
str = ['a', 'b','c', 'd'] def fun1(s): if s != 'a': return s ret = filter(fun1, str) print(list(ret))# ret是一個迭代器對象
對sequence中的item依次執行function(item),將執行結果為True的item做成一個filter object的迭代器返回。可以看作是過濾函數。
2 map(function, sequence)
str = [1, 2,'a', 'b'] def fun2(s): return s + "alvin" ret = map(fun2, str) print(ret) # map object的迭代器 print(list(ret))# ['aalvin', 'balvin', 'calvin', 'dalvin']
對sequence中的item依次執行function(item),將執行結果組成一個map object迭代器返回.
map也支持多個sequence,這就要求function也支持相應數量的參數輸入:
ef add(x,y): return x+y print (list(map(add, range(10), range(10))))##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
3 reduce(function, sequence, starting_value)
from functools import reduce def add1(x,y): return x + y print (reduce(add1, range(1, 101)))## 4950 (注:1+2+...+99) print (reduce(add1, range(1, 101), 20))## 4970 (注:1+2+...+99+20)
對sequence中的item順序迭代調用function,如果有starting_value,還可以作為初始值調用.
4 lambda
普通函數與匿名函數的對比:
#普通函數 def add(a,b): return a + b print add(2,3) #匿名函數 add = lambda a,b : a + b print add(2,3) #========輸出=========== 5 5
匿名函數的命名規則,用lamdba 關鍵字標識,冒號(:)左側表示函數接收的參數(a,b) ,冒號(:)右側表示函數的返回值(a+b)。
因為lamdba在創建時不需要命名,所以,叫匿名函數
八 函數式編程
學會了上面幾個重要的函數后,我們就可以來聊一聊函數式編程到底是個什么鬼
一 概念(函數式編程)
函數式編程是一種編程范式,我們常見的編程范式有命令式編程(Imperative programming),函數式編程,常見的面向對象編程是也是一種命令式編程。
而函數式編程是面向數學的抽象,將計算描述為一種 表達式求值,一句話,函數式程序就是一個 表達式。
函數式編程中的 函數這個術語不是指計算機中的函數,而是指數學中的函數,即自變量的映射。也就是說一個函數的值僅決定於函數參數的值,不依賴其他狀態。比如y=x*x函數計算x的平方根,只要x的平方,不論什么時候調用,調用幾次,值都是不變的。
純函數式編程語言中的 變量也不是命令式編程語言中的變量,即存儲狀態的單元,而是代數中的變量,即一個值的名稱。變量的值是 不可變的 (immutable),也就是說不允許像命令式編程語言中那樣多次給一個變量賦值。比如說在命令式編程語言我們寫“x = x + 1”,這依賴可變狀態的事實,拿給程序員看說是對的,但拿給數學家看,卻被認為這個等式為假。
函數式語言的如條件語句,循環語句也不是命令式編程語言中的 控制語句,而是函數的語法糖,比如在Scala語言中, if else不是語句而是三元運算符,是有返回值的。
嚴格意義上的函數式編程意味着不使用可變的變量,賦值,循環和其他命令式控制結構進行編程。
函數式編程關心數據的映射,命令式編程關心解決問題的步驟,這也是為什么“函數式編程”叫做“函數式編程”。
二 實例
假如,現在你來到 baidu面試,面試官讓你把number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]中的正數的平均值,你肯定可以寫出:#計算數組中正整數的平均值 number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8] count = 0 sum = 0 for i in range(len(number)): if number[i]>0: count += 1 sum += number[i] print sum,count if count>0: average = sum/count print average #========輸出=========== 30 6 5
首先循環列表中的值,累計次數,並對大於0的數進行累加,最后求取平均值。
這就是命令式編程——你要做什么事情,你得把達到目的的步驟詳細的描述出來,然后交給機器去運行。
這也正是命令式編程的理論模型——圖靈機的特點。一條寫滿數據的紙帶,一條根據紙帶內容運動的機器,機器每動一步都需要紙帶上寫着如何達到。
那么,不用這種方式如何做到呢?
number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8] positive = filter(lambda x: x>0, number) average = reduce(lambda x,y: x+y, positive)/len(positive) print average #========輸出=========== 5
這段代碼最終達到的目的同樣是求取正數平均值,但是它得到結果的方式和 之前有着本質的差別:通過描述一個列表->正數平均值 的映射,而不是描述“從列表得到正數平均值應該怎樣做”來達到目的。
再比如,求階乘
通過Reduce函數加lambda表達式式實現階乘是如何簡單:
from functools import reduce print (reduce(lambda x,y: x*y, range(1,6)))
又比如,map()函數加上lambda表達式(匿名函數)可以實現更強大的功能:
squares = map(lambda x : x*x ,range(9)) print (squares)# <map object at 0x10115f7f0>迭代器 print (list(squares))#[0, 1, 4, 9, 16, 25, 36, 49, 64]
三 函數式編程有什么好處呢?
1)代碼簡潔,易懂。
2)無副作用
由於命令式編程語言也可以通過類似函數指針的方式來實現高階函數,函數式的最主要的好處主要是不可變性帶來的。沒有可變的狀態,函數就是引用透明(Referential transparency)的和沒有副作用(No Side Effect)。