Python中的函數(二)


                        Python中的函數(二)

  在上一篇文章中提到了Python中函數的定義和使用,在這篇文章里我們來討論下關於函數的一些更深的話題。在學習C語言函數的時候,遇到的問題主要有形參實參的區別、參數的傳遞和改變、變量的作用域。同樣在Python中,關於對函數的理解和使用也存在這些問題。下面來逐一講解。

一.函數形參和實參的區別

  相信有過編程語言經驗的朋友對形參和實參這兩個東西並不陌生。形參全稱是形式參數,在用def關鍵字定義函數時函數名后面括號里的變量稱作為形式參數。實參全稱為實際參數,在調用函數時提供的值或者變量稱作為實際參數。舉個例子:

#這里的a和b就是形參
def add(a,b):
    return a+b

#這里的1和2是實參
add(1,2)
x=2
y=3
#這里的x和y是實參
add(x,y)

二.參數的傳遞和改變

  在大多數高級語言當中,對參數的傳遞方式這個問題的理解一直是個難點和重點,因為它理解起來並不是那么直觀明了,但是不理解的話在編寫程序的時候又極其容易出錯。下面我們來探討一下Python中的函數參數的傳遞問題。

  首先在討論這個問題之前,我們需要明確一點就是在Python中一切皆對象,變量中存放的是對象的引用。這個確實有點難以理解,“一切皆對象”?對,在Python中確實是這樣,包括我們之前經常用到的字符串常量,整型常量都是對象。不信的話可以驗證一下:

print id(5)
print id('python')
x=2
print id(x)
y='hello'
print id(y)

 

  這段代碼的運行結果是:

  

  先解釋一下函數id( )的作用。下面這段話是官方文檔對id()函數的解釋:

  

  顯而易見,id(object)函數是返回對象object在其生命周期內位於內存中的地址,id函數的參數類型是一個對象,因此對於這個語句

id(2)沒有報錯,就可以知道2在這里是一個對象。下面再看幾個例子:

x=2
print id(2)
print id(x)
y='hello'
print id('hello')
print id(y)

  其運行結果為:

  

  從結果可以看出,id(x)和id(2)的值是一樣的,id(y)和id('hello')的值也是一樣的。

  在Python中一切皆對象,像2,'hello'這樣的值都是對象,只不過5是一個整型對象,而'hello'是一個字符串對象。上面的x=2,在Python中實際的處理過程是這樣的:先申請一段內存分配給一個整型對象來存儲整型值2,然后讓變量x去指向這個對象,實際上就是指向這段內存(這里有點和C語言中的指針類似)。而id(2)和id(x)的結果一樣,說明id函數在作用於變量時,其返回的是變量指向的對象的地址。因為變量也是對象,所以在這里可以將x看成是對象2的一個引用。

  下面再看幾個例子:

x=2
print id(x)
y=2
print id(y)
s='hello'
print id(s)
t=s
print id(t)

  其運行結果為:

  

  從運行結果可以看到id(x)和id(y)的結果是相同的,id(s)和id(t)的結果也是相同的。這說明x和y指向的是同一對象,而t和s也是指向的同一對象。x=2這句讓變量x指向了int類型的對象2,而y=2這句執行時,並不重新為2分配空間,而是讓y直接指向了已經存在的int類型的對象2.這個很好理解,因為本身只是想給y賦一個值2,而在內存中已經存在了這樣一個int類型對象2,所以就直接讓y指向了已經存在的對象。這樣一來不僅能達到目的,還能節約內存空間。t=s這句變量互相賦值,也相當於是讓t指向了已經存在的字符串類型的對象'hello'(這個原理和C語言中指針的互相賦值有點類似)。

  看這幅圖就理解了:

  

  下面再看幾個例子:

x=2
print id(2)
print id(x)
x=3
print id(3)
print id(x)
L=[1,2,3]
M=L
print id(L)
print id(M)
print id(L[2])
L[0]=2
print id(L)
print M

  運行結果為:

  

  下面來分析一下這個結果,兩次的id(x)的值不同,這個可能讓人有點難以理解。注意,在Python中,單一元素的對象是不允許更改的,比如整型數據、字符串、浮點數等。x=3這句的執行過程並不是先獲取x原來指向的對象的地址,再把內存中的值更改為3,而是新申請一段內存來存儲對象3,再讓x去指向對象3,所以兩次id(x)的值不同。然而為何改變了L中的某個子元素的值后,id(L)的值沒有發生改變?在Python中,復雜元素的對象是允許更改的,比如列表、字典、元組等。Python中變量存儲的是對象的引用,對於列表,其id()值返回的是列表第一個子元素L[0]的存儲地址。就像上面的例子,L=[1,2,3],這里的L有三個子元素L[0],L[1],L[2],L[0]、L[1]、L[2]分別指向對象1、2、3,id(L)值和對象3的存儲地址相同,看下面這個圖就明白了:

  

  因為L和M指向的是同一對象,所以在更改了L中子元素的值后,M也相應改變了,但是id(L)值並沒有改變,因為這句L[0]=2只是讓L[0]重新指向了對象2,而L[0]本身的存儲地址並沒有發生改變,所以id(L)的值沒有改變( id(L)的值實際等於L[0]本身的存儲地址)。

 

  下面就來討論一下函數的參數傳遞和改變這個問題。

  在Python中參數傳遞采用的是值傳遞,這個和C語言有點類似。先看幾個例子:

def modify1(m,K):
    m=2
    K=[4,5,6]
    return 
    
def modify2(m,K):
    m=2
    K[0]=0
    return

n=100
L=[1,2,3]
modify1(n,L)
print n
print L
modify2(n,L)
print n
print L
    

  程序運行結果為:

  

  從結果可以看出,執行modify1( )之后,n和L都沒有發生任何改變;執行modify2( )后,n還是沒有改變,L發生了改變。因為在Python中參數傳遞采用的是值傳遞方式,在執行函數modify1時,先獲取n和L的id( )值,然后為形參m和K分配空間,讓m和K分別指向對象100和對象[1,2,3]。m=2這句讓m重新指向對象2,而K=[4,5,6]這句讓K重新指向對象[4,5,6]。這種改變並不會影響到實參n和L,所以在執行modify1之后,n和L沒有發生任何改變;在執行函數modify2時,同理,讓m和K分別指向對象2和對象[1,2,3],然而K[0]=0讓K[0]重新指向了對象0(注意這里K和L指向的是同一段內存),所以對K指向的內存數據進行的任何改變也會影響到L,因此在執行modify2后,L發生了改變。

三.變量的作用域

  在Python中,也存在作用域這個問題。在Python中,會為每個層次生成一個符號表,里層能調用外層中的變量,而外層不能調用里層中的變量,並且當外層和里層有同名變量時,外層變量會被里層變量屏蔽掉。舉個例子:

def function():
    x=2
    count=2
    while count>0:
        x=3
        print x
        count=count-1

function()        
    

  在函數function中,while循環外面和while循環里面都有變量x,此時,while循環外面的變量x會被屏蔽掉。注意在函數內部定義的變量作用域都僅限於函數內部,在函數外部是不能夠調用的,一般稱這種變量為局部變量。

  還有一種變量叫做全局變量,它是在函數外部定義的,作用域是整個文件。全局變量可以直接在函數里面應用,但是如果要在函數內部改變全局變量,必須使用global關鍵字進行聲明。

x=2

def fun1():
    print x
    
def fun2():
    global x
    x=3
    print x

fun1()
fun2()
print x

  關於函數的形參和實參、參數的傳遞以及變量的作用域問題這里就討論這么多了,關於函數參數的類型問題會在《Python中的函數(三)》中繼續講解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM