Python之函數

一 函數是什麼?

函數一詞來源於數學,但編程中的「函數」概念,與數學中的函數是有很大不一樣的,具體區別,咱們後面會講,編程中的函數在英文中也有不少不一樣的叫法。在BASIC中叫作subroutine(子過程或子程序),在Pascal中叫作procedure(過程)和function,在C中只有function,在Java裏面叫作method。html

函數能提升應用的模塊性,和代碼的重複利用率。你已經知道Python提供了許多內建函數,好比print()。但你也能夠本身建立函數,這被叫作用戶自定義函數。python

定義: 函數是指將一組語句的集合經過一個名字(函數名)封裝起來,要想執行這個函數,只需調用其函數名便可程序員

特性:面試

1.代碼重用編程

2.保持一致性數組

3.可擴展性數據結構

二 函數的建立

2.1 格式:

Python 定義函數使用 def 關鍵字,通常格式以下:閉包

1
2
def  函數名(參數列表):
     函數體
def hello():
print('hello')

hello()#調用

2.2 函數名的命名規則

  • 函數名必須如下劃線或字母開頭,能夠包含任意字母、數字或下劃線的組合。不能使用任何的標點符號;
  • 函數名是區分大小寫的。
  • 函數名不能是保留字。

2.3 形參和實參

形參:形式參數,不是實際存在,是虛擬變量。在定義函數和函數體的時候使用形參,目的是在函數調用時接收實參(實參個數,類型應與實參一一對應)編程語言

實參:實際參數,調用函數時傳給函數的參數,能夠是常量,變量,表達式,函數,傳給形參   ide

區別:形參是虛擬的,不佔用內存空間,.形參變量只有在被調用時才分配內存單元,實參是一個變量,佔用內存空間,數據傳送單向,實參傳給形參,不能形參傳給實參

1
2
3
4
5
import  time
times = time.strftime( '%Y--%m--%d' )
def  f(time):
     print ( 'Now  time is : %s' % times)
f(times)

2.4 實例

實例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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  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()
函數的特性展現

 

三 函數的參數

  • 必備參數
  • 關鍵字參數
  • 默認參數
  • 不定長參數

必需的參數:

必需參數須以正確的順序傳入函數。調用時的數量必須和聲明時的同樣。
1
2
3
4
5
6
def  f(name,age):
 
     print ( 'I am %s,I am %d' % (name,age))
 
f( 'alex' , 18 )
f( 'alvin' , 16 )

關鍵字參數:

關鍵字參數和函數調用關係緊密,函數調用使用關鍵字參數來肯定傳入的參數值。使用關鍵字參數容許函數調用時參數的順序與聲明時不一致,由於 Python 解釋器可以用參數名匹配參數值。

1
2
3
4
5
6
def  f(name,age):
 
     print ( 'I am %s,I am %d' % (name,age))
 
# f(16,'alvin') #報錯
f(age = 16 ,name = 'alvin' )

缺省參數(默認參數):

調用函數時,缺省參數的值若是沒有傳入,則被認爲是默認值。下例會打印默認的age,若是age沒有被傳入:

1
2
3
4
5
6
7
8
9
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種參數不一樣,聲明時不會命名。

1
2
3
4
5
6
7
8
9
10
11
12
# def add(x,y):
#     return x+y
 
def  add( * tuples):
     sum = 0
     for  in  tuples:
         sum + = v
 
     return  sum
 
print (add( 1 , 4 , 6 , 9 ))
print (add( 1 , 4 , 6 , 9 , 5 ))

加了星號(*)的變量名會存放全部未命名的變量參數。而加(**)的變量名會存放命名的變量參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def  print_info( * * kwargs):
 
     print (kwargs)
     for  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')   #報錯

注意,還能夠這樣傳參:

1
2
3
4
5
6
7
8
9
def  f( * args):
     print (args)
 
f( * [ 1 , 2 , 5 ])
 
def  f( * * kargs):
     print (kargs)
 
f( * * { 'name' : 'alex' })

補充(高階函數):

         高階函數是至少知足下列一個條件的函數:

      • 接受一個或多個函數做爲輸入
      • 輸出一個函數
1
2
3
4
5
6
7
8
9
10
11
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語句把結果返回

注意:

  1. 函數在執行過程當中只要遇到return語句,就會中止執行並返回結果,so 也能夠理解爲 return 語句表明着函數的結束
  2. 若是未在函數中指定return,那這個函數的返回值爲None  
  3. return多個對象,解釋器會把這多個對象組裝成一個元組做爲一個一個總體結果輸出。

五 做用域

5.1 做用域介紹 

python中的做用域分4種狀況:

  • L:local,局部做用域,即函數中定義的變量;
  • E:enclosing,嵌套的父級函數的局部做用域,即包含此函數的上級函數的局部做用域,但不是全局的;
  • G:globa,全局變量,就是模塊級別定義的變量;
  • B:built-in,系統固定模塊裏面的變量,好比int, bytearray等。 搜索變量的優先級順序依次是:做用域局部>外層做用域>當前模塊中的全局>python內置做用域,也就是LEGB。
1
2
3
4
5
6
7
8
9
10
11
12
13
=  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等)是不會引入新的做用域的,以下代碼:

1
2
3
if  2 > 1 :
     =  1
print (x)   # 1

這個是沒有問題的,if並無引入一個新的做用域,x仍處在當前做用域中,後面代碼可使用。

1
2
3
def  test():
     =  2
print (x)  # NameError: name 'x2' is not defined

def、class、lambda是能夠引入新做用域的。 

5.3 變量的修改 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#################
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先聲明一下,代碼以下:

1
2
3
4
5
6
7
8
9
count  =  10
def  outer():
     global  count
     print (count) 
     count  =  100
     print (count)
outer()
#10
#100

5.5 nonlocal關鍵字 

global關鍵字聲明的變量必須在全局做用域上,不能嵌套做用域上,當要修改嵌套做用域(enclosing做用域,外層非全局做用域)中的變量怎麼辦呢,這時就須要nonlocal關鍵字了

1
2
3
4
5
6
7
8
9
10
11
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(階乘)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def  factorial(n):
 
     result = n
     for  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(斐波那契數列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def  fibo(n):
 
     before = 0
     after = 1
     for  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 ))
1
print (fibo_new( 30000 )) #maximum recursion depth exceeded in comparison

遞歸函數的優勢:    是定義簡單,邏輯清晰。理論上,全部的遞歸函數均可以寫成循環的方式,但循環的邏輯不如遞歸清晰。

遞歸特性:

1. 必須有一個明確的結束條件

2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減小

3. 遞歸效率不高,遞歸層次過多會致使棧溢出(在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返     回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。)

七 內置函數(Py3.5)

  Built-in Functions    
abs() dict() help() min() setattr()
all() dir() hex() next() slice()
any() divmod() id() object() sorted()
ascii() enumerate() input() oct() staticmethod()
bin() eval() int() open() str()
bool() exec() isinstance() ord() sum()
bytearray() filter() issubclass() pow() super()
bytes() float() iter() print() tuple()
callable() format() len() property() type()
chr() frozenset() list() range() vars()
classmethod() getattr() locals() repr() zip()
compile() globals() map() reversed() __import__()
complex() hasattr() max() round()  
delattr() hash() memoryview() set()  

py2內置函數:https://docs.python.org/3.5/library/functions.html#repr

重要的內置函數:

       1 filter(function, sequence)

1
2
3
4
5
6
7
8
9
10
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) 

1
2
3
4
5
6
7
8
9
10
str  =  [ 1 2 , 'a' 'b' ]
 
def  fun2(s):
 
     return  +  "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也支持相應數量的參數輸入:
1
2
3
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)  

1
2
3
4
5
6
7
8
from  functools  import  reduce
 
def  add1(x,y):
     return  +  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

       普通函數與匿名函數的對比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#普通函數
def  add(a,b):
     return  +  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]中的正數的平均值,你確定能夠寫出:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#計算數組中正整數的平均值
 
number  = [ 2 - 5 9 - 7 2 5 4 - 1 0 - 3 8 ]
count  =  0
sum  =  0
 
for  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的數進行累加,最後求取平均值。  

這就是命令式編程——你要作什麼事情,你得把達到目的的步驟詳細的描述出來,而後交給機器去運行。

這也正是命令式編程的理論模型——圖靈機的特色。一條寫滿數據的紙帶,一條根據紙帶內容運動的機器,機器每動一步都須要紙帶上寫着如何達到。

那麼,不用這種方式如何作到呢?

1
2
3
4
5
6
7
8
9
10
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表達式式實現階乘是如何簡單:

1
2
from  functools  import  reduce
print  ( reduce ( lambda  x,y: x * y,  range ( 1 , 6 )))

 

又好比,map()函數加上lambda表達式(匿名函數)能夠實現更強大的功能:

1
2
3
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)。

相關文章
相關標籤/搜索