草根學Python(六) 函數

前言

前天創了個 Python 微信討論羣,覺得沒人進的,哈哈,想不到還真有小夥伴進羣學習討論。若是想進羣,能夠加我微信: androidwed ,拉進羣,就不貼微信羣二維碼了,一是會失效,二影響文章。python

目錄

草根學Python(六)  函數
草根學Python(六) 函數

1、Python 自定義函數的基本步驟

函數是組織好的,可重複使用的,用來實現單一,或相關聯功能的代碼段。android

自定義函數,基本有如下規則步驟:c++

  • 函數代碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括號()
  • 任何傳入參數和自變量必須放在圓括號中間。圓括號之間能夠用於定義參數
  • 函數的第一行語句能夠選擇性地使用文檔字符串(用於存放函數說明)
  • 函數內容以冒號起始,而且縮進
  • return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的 return 至關於返回 None。

語法示例:express

def functionname( parameters ):
   "函數_文檔字符串"
   function_suite
   return [expression]複製代碼

實例:編程

  1. def 定義一個函數,給定一個函數名 sum
  2. 聲明兩個參數 num1 和 num2
  3. 函數的第一行語句進行函數說明:兩數之和
  4. 最終 return 語句結束函數,並返回兩數之和
def sum(num1,num2):
    "兩數之和"
    return num1+num2

# 調用函數
print(sum(5,6))複製代碼

輸出結果:微信

11複製代碼

2、函數傳值問題

先看一個例子:app

# -*- coding: UTF-8 -*-
def chagne_number( b ):
    b = 1000

b = 1
chagne_number(b)
print( b )複製代碼

最後輸出的結果爲:編程語言

1複製代碼

這裏可能有些人會有疑問,爲啥不是經過函數chagne_number更改了 b
的值嗎?爲啥沒有變化,輸出的結果仍是 1 ,這個問題不少編程語言都會講到,原理解釋也是差很少的。函數

這裏主要是函數參數的傳遞中,傳遞的是類型對象,以前也介紹了 Python 中基本的數據類型等。而這些類型對象能夠分爲可更改類型和不可更改的類型學習

在 Python 中,字符串,整形,浮點型,tuple 是不可更改的對象,而 list , dict 等是能夠更改的對象。

例如:

不可更改的類型:變量賦值 a = 1,其實就是生成一個整形對象 1 ,而後變量 a 指向 1,當 a = 1000 其實就是再生成一個整形對象 1000,而後改變 a 的指向,再也不指向整形對象 1 ,而是指向 1000,最後 1 會被丟棄

可更改的類型:變量賦值 a = [1,2,3,4,5,6] ,就是生成一個對象 list ,list 裏面有 6 個元素,而變量 a 指向 list ,a[2] = 5則是將 list a 的第三個元素值更改,這裏跟上面是不一樣的,並非將 a 從新指向,而是直接修改 list 中的元素值。

指向問題
指向問題

這也將影響到函數中參數的傳遞了:

不可更改的類型:相似 c++ 的值傳遞,如 整數、字符串、元組。如fun(a),傳遞的只是 a 的值,沒有影響 a 對象自己。好比在 fun(a)內部修改 a 的值,只是修改另外一個複製的對象,不會影響 a 自己。

可更改的類型:相似 c++ 的引用傳遞,如 列表,字典。如 fun(a),則是將 a 真正的傳過去,修改後 fun 外部的 a 也會受影響

所以,在一開始的例子中,b = 1,建立了一個整形對象 1 ,變量 b 指向了這個對象,而後經過函數 chagne_number 時,按傳值的方式複製了變量 b ,傳遞的只是 b 的值,並無影響到 b 的自己。具體能夠看下修改後的實例,經過打印的結果更好的理解。

# -*- coding: UTF-8 -*-
def chagne_number( b ):
    print('函數中一開始 b 的值:{}' .format( b ) )
    b = 1000
    print('函數中 b 賦值後的值:{}' .format( b ) )


b = 1
chagne_number( b )
print( '最後輸出 b 的值:{}' .format( b )  )複製代碼

打印的結果:

函數中一開始 b 的值:1
函數中 b 賦值後的值:1000
最後輸出 b 的值:1複製代碼

固然,若是參數中的是可更改的類型,那麼調用了這個函數後,原來的值也會被更改,具體實例以下:

# -*- coding: UTF-8 -*-

def chagne_list( b ):
    print('函數中一開始 b 的值:{}' .format( b ) )
    b.append(1000)
    print('函數中 b 賦值後的值:{}' .format( b ) )


b = [1,2,3,4,5]
chagne_list( b )
print( '最後輸出 b 的值:{}' .format( b )  )複製代碼

輸出的結果:

函數中一開始 b 的值:[1, 2, 3, 4, 5]
函數中 b 賦值後的值:[1, 2, 3, 4, 5, 1000]
最後輸出 b 的值:[1, 2, 3, 4, 5, 1000]複製代碼

3、函數返回值

經過上面的學習,能夠知道經過 return [表達式] 語句用於退出函數,選擇性地向調用方返回一個表達式。不帶參數值的 return 語句返回 None。

具體示例:

# -*- coding: UTF-8 -*-

def sum(num1,num2):
    # 兩數之和
    if not (isinstance (num1,(int ,float)) or isinstance (num2,(int ,float))):
        raise TypeError('參數類型錯誤')
    return num1+num2

print(sum(1,2))複製代碼

返回結果:

3複製代碼

這個示例,還經過內置函數isinstance()進行數據類型檢查,檢查調用函數時參數是不是整形和浮點型。若是參數類型不對,會報錯,提示 參數類型錯誤,如圖:

檢查函數參數是否正確
檢查函數參數是否正確

固然,函數也能夠返回多個值,具體實例以下:

# -*- coding: UTF-8 -*-

def division ( num1, num2 ):
    # 求商與餘數
         a = num1 % num2
         b = (num1-a) / num2
         return b , a 

num1 , num2 = division(9,4)
tuple1 = division(9,4)

print (num1,num2)
print (tuple1)複製代碼

輸出的值:

2.0 1
(2.0, 1)複製代碼

認真觀察就能夠發現,儘管從第一個輸出值來看,返回了多個值,其實是先建立了一個元組而後返回的。回憶一下,元組是能夠直接用逗號來建立的,觀察例子中的 ruturn ,能夠發現實際上咱們使用的是逗號來生成一個元組。

4、函數的參數

一、默認值參數

有時候,咱們自定義的函數中,若是調用的時候沒有設置參數,須要給個默認值,這時候就須要用到默認值參數了。

# -*- coding: UTF-8 -*-

def print_user_info( name , age , sex = '男' ):
    # 打印用戶信息
    print('暱稱:{}'.format(name) , end = ' ')
    print('年齡:{}'.format(age) , end = ' ')
    print('性別:{}'.format(sex))
    return;

# 調用 print_user_info 函數

print_user_info( '兩點水' , 18 , '女')
print_user_info( '三點水' , 25 )複製代碼

輸出結果:

暱稱:兩點水 年齡:18 性別:女
暱稱:三點水 年齡:25 性別:男複製代碼

能夠看到,當你設置了默認參數的時候,在調用函數的時候,不傳該參數,就會使用默認值。可是這裏須要注意的一點是:只有在形參表末尾的那些參數能夠有默認參數值,也就是說你不能在聲明函數形參的時候,先聲明有默認值的形參然後聲明沒有默認值的形參。這是由於賦給形參的值是根據位置而賦值的。例如,def func(a, b=1) 是有效的,可是 def func(a=1, b) 是 無效 的。

默認值參數就這樣結束了嗎?尚未的,細想一下,若是參數中是一個可修改的容器好比一個 lsit (列表)或者 dict (字典),那麼咱們使用什麼來做爲默認值呢?咱們可使用 None 做爲默認值。就像下面這個例子同樣:

# 若是 b 是一個 list ,可使用 None 做爲默認值
def print_info( a , b = None ):
    if b is None :
        b=[]
    return;複製代碼

認真看下例子,會不會有這樣的疑問呢?在參數中咱們直接 b=[] 不就好了嗎?也就是寫成下面這個樣子:

def print_info( a , b = [] ):
    return;複製代碼

對不對呢?運行一下也沒發現錯誤啊,能夠這樣寫嗎?這裏須要特別注意的一點:默認參數的值是不可變的對象,好比None、True、False、數字或字符串,若是你像上面的那樣操做,當默認值在其餘地方被修改後你將會遇到各類麻煩。這些修改會影響到下次調用這個函數時的默認值。

示例以下:

# -*- coding: UTF-8 -*-

def print_info( a , b = [] ):
    print(b)
    return b ;

result = print_info(1)

result.append('error')

print_info(2)複製代碼

輸出的結果:

[]
['error']複製代碼

認真觀察,你會發現第二次輸出的值根本不是你想要的,所以切忌不能這樣操做。

還有一點,有時候我就是不想要默認值啊,只是想單單判斷默認參數有沒有值傳遞進來,那該怎麼辦?咱們能夠這樣作:

_no_value =object()

def print_info( a , b = _no_value ):
    if b is _no_value :
        print('b 沒有賦值')
    return;複製代碼

這裏的 object 是python中全部類的基類。 你能夠建立 object 類的實例,可是這些實例沒什麼實際用處,由於它並無任何有用的方法, 也沒有任何實例數據(由於它沒有任何的實例字典,你甚至都不能設置任何屬性值)。 你惟一能作的就是測試同一性。也正好利用這個特性,來判斷是否有值輸入。

二、關鍵字參數

在 Python 中,能夠經過參數名來給函數傳遞參數,而不用關心參數列表定義時的順序,這被稱之爲關鍵字參數。使用關鍵參數有兩個優點 :

1、因爲咱們沒必要擔憂參數的順序,使用函數變得更加簡單了。

2、假設其餘參數都有默認值,咱們能夠只給咱們想要的那些參數賦值

# -*- coding: UTF-8 -*-

def print_user_info( name , age , sex = '男' ):
    # 打印用戶信息
    print('暱稱:{}'.format(name) , end = ' ')
    print('年齡:{}'.format(age) , end = ' ')
    print('性別:{}'.format(sex))
    return;

# 調用 print_user_info 函數

print_user_info( name = '兩點水' ,age = 18 , sex = '女')
print_user_info( name = '兩點水' ,sex = '女', age = 18 )複製代碼

輸出的值:

暱稱:兩點水 年齡:18 性別:女
暱稱:兩點水 年齡:18 性別:女複製代碼

三、不定長參數

有時咱們在設計函數接口的時候,可會須要可變長的參數。也就是說,咱們事先沒法肯定傳入的參數個數。Python 提供了一種元組的方式來接受沒有直接定義的參數。這種方式在參數前邊加星號 * 。若是在函數調用時沒有指定參數,它就是一個空元組。咱們也能夠不向函數傳遞未命名的變量。

例如:

# -*- coding: UTF-8 -*-

def print_user_info( name , age , sex = '男' , * hobby):
    # 打印用戶信息
    print('暱稱:{}'.format(name) , end = ' ')
    print('年齡:{}'.format(age) , end = ' ')
    print('性別:{}'.format(sex) ,end = ' ' )
    print('愛好:{}'.format(hobby))
    return;

# 調用 print_user_info 函數
print_user_info( '兩點水' ,18 , '女', '打籃球','打羽毛球','跑步')複製代碼

輸出的結果:

暱稱:兩點水 年齡:18 性別:女 愛好:('打籃球', '打羽毛球', '跑步')複製代碼

經過輸出的結果能夠知道,*hobby是可變參數,且 hobby其實就是一個 tuple (元祖)

可變長參數也支持關鍵參數,沒有被定義的關鍵參數會被放到一個字典裏。這種方式便是在參數前邊加 **,更改上面的示例以下:

# -*- coding: UTF-8 -*-

def print_user_info( name , age , sex = '男' , ** hobby ):
    # 打印用戶信息
    print('暱稱:{}'.format(name) , end = ' ')
    print('年齡:{}'.format(age) , end = ' ')
    print('性別:{}'.format(sex) ,end = ' ' )
    print('愛好:{}'.format(hobby))
    return;

# 調用 print_user_info 函數
print_user_info( name = '兩點水' , age = 18 , sex = '女', hobby = ('打籃球','打羽毛球','跑步'))複製代碼

輸出的結果:

暱稱:兩點水 年齡:18 性別:女 愛好:{'hobby': ('打籃球', '打羽毛球', '跑步')}複製代碼

經過對比上面的例子和這個例子,能夠知道,*hobby是可變參數,且 hobby其實就是一個 tuple (元祖),**hobby是關鍵字參數,且 hobby 就是一個 dict (字典)

四、只接受關鍵字參數

關鍵字參數使用起來簡單,不容易參數出錯,那麼有些時候,咱們定義的函數但願某些參數強制使用關鍵字參數傳遞,這時候該怎麼辦呢?

將強制關鍵字參數放到某個*參數或者單個*後面就能達到這種效果,好比:

# -*- coding: UTF-8 -*-

def print_user_info( name , *, age , sex = '男' ):
    # 打印用戶信息
    print('暱稱:{}'.format(name) , end = ' ')
    print('年齡:{}'.format(age) , end = ' ')
    print('性別:{}'.format(sex))
    return;

# 調用 print_user_info 函數
print_user_info( name = '兩點水' ,age = 18 , sex = '女' )

# 這種寫法會報錯,由於 age ,sex 這兩個參數強制使用關鍵字參數
#print_user_info( '兩點水' , 18 , '女' )
print_user_info('兩點水',age='22',sex='男')複製代碼

經過例子能夠看,若是 age , sex 不適用關鍵字參數是會報錯的。

不少狀況下,使用強制關鍵字參數會比使用位置參數表意更加清晰,程序也更加具備可讀性。使用強制關鍵字參數也會比使用 **kw 參數更好且強制關鍵字參數在一些更高級場合一樣也頗有用。

5、匿名函數

有沒有想過定義一個很短的回調函數,但又不想用 def 的形式去寫一個那麼長的函數,那麼有沒有快捷方式呢?答案是有的。

python 使用 lambda 來建立匿名函數,也就是再也不使用 def 語句這樣標準的形式定義一個函數。

匿名函數主要有如下特色:

  • lambda 只是一個表達式,函數體比 def 簡單不少。
  • lambda 的主體是一個表達式,而不是一個代碼塊。僅僅能在 lambda 表達式中封裝有限的邏輯進去。
  • lambda 函數擁有本身的命名空間,且不能訪問自有參數列表以外或全局命名空間裏的參數。

基本語法

lambda [arg1 [,arg2,.....argn]]:expression複製代碼

示例:

# -*- coding: UTF-8 -*-

sum = lambda num1 , num2 : num1 + num2;

print( sum( 1 , 2 ) )複製代碼

輸出的結果:

3複製代碼

注意:儘管 lambda 表達式容許你定義簡單函數,可是它的使用是有限制的。 你只能指定單個表達式,它的值就是最後的返回值。也就是說不能包含其餘的語言特性了, 包括多個語句、條件表達式、迭代以及異常處理等等。

匿名函數中,有一個特別須要注意的問題,好比,把上面的例子改一下:

# -*- coding: UTF-8 -*-

num2 = 100
sum1 = lambda num1 : num1 + num2 ;

num2 = 10000
sum2 = lambda num1 : num1 + num2 ;

print( sum1( 1 ) )
print( sum2( 1 ) )複製代碼

你會認爲輸出什麼呢?第一個輸出是 101,第二個是 10001,結果不是的,輸出的結果是這樣:

10001
10001複製代碼

這主要在於 lambda 表達式中的 num2 是一個自由變量,在運行時綁定值,而不是定義時就綁定,這跟函數的默認值參數定義是不一樣的。因此建議仍是遇到這種狀況仍是使用第一種解法。

相關文章
相關標籤/搜索