歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Python之函數與變量

Python之函數與變量

日期:2017/3/1 9:05:45   编辑:Linux編程

本節內容


  1. 函數介紹及其作用
  2. 函數的定義與調用
  3. 函數的參數說明
  4. 全局變量與局部變量
  5. 值傳遞和引用傳遞

一、函數的介紹及其作用


編程語言中的函數與數學中的函數是有區別的:數學中的函數有參數(輸入),就會有相應的結果(輸出)。編程語言中的函數有輸入,不一定會返回結果。編程語言中的函數其實就是一個用於完成某個特定功能的相關代碼的代碼段 。那麼哪些代碼語句應該被整合到一起定義為一個函數呢?這取決於你想讓這個函數完成的功能是什麼。

為什麼要將這個代碼段定義成一個函數呢?這其實就是函數的作用。假設我們在編寫一個可供用戶選擇的菜單程序,程序啟動時需要打印一遍菜單列表,而且程序運行過程中用戶也可以隨時打印菜單列表,也就是說打印菜單列表的代碼段可能要多次被用到,假設每次打印的菜單列表都是一樣的,而且列表很長,那麼我們是否應該每次在需要打印菜單的時候重復執行相同的代碼呢?那麼當我們需要增加或者減少一個菜單項時怎麼辦呢?顯然我們需要在每個打印菜單的代碼點都進行修改。如果我們把打印菜單的相關代碼拿出來定義為一個函數,又會出現這樣的場景呢?我們只需要在需要打印菜單列表的地方使用這個函數;當需要添加或減少一個菜單項時,只需要修改這個函數中的內容即可,程序的維護和擴展成本大大降低;同時,我們這個程序的代碼會更加簡潔,而且有條理性更加便於閱讀,而不是一坨亂糟糟的讓人看著就想重寫的東西。當然,如果你要打印的是多級菜單,你可以通過函數的參數或全部變量通知該函數要打印的是幾級菜單。總結一下,編程語言中的函數的作用就是實現代碼的可重用性,提高代碼可維護性、擴展性和可讀性

二、函數的定義與調用


1. 函數的定義

高級編程語言通常會提供很多內置的函數來屏蔽底層差異,向上暴露一些通用的接口,比如我們之前用到的print()函數和open()函數。除此之外,我們也可以自定義我們需要的函數。由於函數本身也是程序代碼的一部分,因此為了標識出這段代碼是一個函數通常需要使用特定的格式或關鍵字。另外還涉及到參數、方法名稱、返回值等相關問題的約束。

Python中定義函數的規則:
  • 函數代碼塊以def關鍵字開頭,後接函數標識符(函數名稱)和圓括號();
  • 函數名稱以數字、小寫字母和下劃線組成並且不能以數字開頭;
  • 圓括號中可用於定義可接收的參數;
  • 函數內容以圓括號()之後的冒號換行後起始,並且縮進;
  • 函數的第一行通常用於寫一個字符串--函數使用方式、參數說明等文檔信息
  • 函數中可以用return關鍵字返回一個值給函數調用方--return [表達式],如果不寫return相當於返回None。

說明: 函數名稱可以使用大寫字母,但是不符合PEP8規范;另外Python3中函數名可以使用中文,但是還是不要給自己找麻煩為好。另外return語句不一定要寫在函數末尾,而可以寫在函數體的任意位置。return語句代表著函數的結束,函數在執行過程中只要遇到return語句,就會停止執行並返回結果。

Python中定義函數的語法:
def 函數名稱( 參數 ):
    """
    函數使用說明、參數介紹等文檔信息
    """
    代碼塊
    return [表達式]
實例: 寫一個求和函數
def add(a, b):
    """
    計算並返回兩個數的和
    a: 被加數
    b: 加數
    """
    c = a + b
    return c

通常寫成這個樣子:

def add(a, b):
    """
    計算並返回兩個數的和
    a: 被加數
    b: 加數
    """
    return a + b

2. 函數的調用

Python中函數的調用方式與其他大部分編程語言都一樣(其實我目前使用過的編程語言當中,只有shell是個另類;好吧,其實它只是個腳本語言):函數名(參數)

def add(a, b):
    """
    計算並返回兩個數的和
    a: 被加數
    b: 加數
    """
    return a + b


sum = add(1, 9)

三、函數的參數說明


先來說下形參和實參的概念:

  • 形參:即形式參數,函數定義時指定的可以接受的參數即為形參,比如上面定義的add(a, b)函數中的a和b就是形參;
  • 實參:即實際參數,調用函數時傳遞給函數參數的實際值即為實參,比如上面都用add(1, 9)函數中的1和9就是實參;

重點需要說下函數的各種不同種類的參數。函數的參數可以分為以下幾種:

  • 位置參數
  • 默認參數
  • 關鍵字參數
  • 可變(長)參數

說明: 這裡說的位置參數,其實是指“必選參數”,也就是函數調用時必須要傳遞的參數,而默認參數是一種有默認值的特殊的位置參數。通常情況下位置參數和默認參數的傳遞順序是不能變化的,但是當以指定參數名的方式(如: name='Tom')傳遞時參數位置時可以變化的。

不同編程語言對以上幾種函數參數的支持各不相同,但是位置參數是最基本的參數類型,基本上所有的編程語言都支持。以下是一個常見編程語言的對比表格(Y表示支持,N表示不支持):

可見只有Python支持全部參數類型,而且只有Python支持關鍵字參數;另外,C、Java和Go都不支持默認參數,其中Java和Go與它們支持的方法重載特性有關(具體可以看下這個帖子),並且它們可以通過方法重載實現默認參數的功能。

下面我們以一個自定義的打印函數來對以上各種參數進行說明:

1. 位置參數

位置參數,顧名思義是和參數的順序位置和數量有關的。函數調用時,實參的位置和個數要與形參對應,不然會報錯。

函數定義:兩個位置參數
def my_print(name, age):
    print('NAME: %s' % name)
    print('AGE: %d' % age)
正確調用:參數位置和個數都正確
>>> my_print('Tom', 18)
NAME: Tom
AGE: 18
錯誤調用:參數位置不正確
>>> my_print(18, 'Tom')
NAME: 18
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in my_print
TypeError: %d format: a number is required, not str
錯誤調用:參數個數不正確
>>> my_print('Tom')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: my_print() missing 1 required positional argument: 'age'

2. 默認參數

默認參數:是指給函數的形參賦一個默認值,它是一個有默認值的位置參數。當調用函數時,如果為該參數傳遞了實參則該形參取實參的值,如果沒有為該參數傳遞實參則該形參取默認值。

默認參數的應用場景:參數值在大部分情況下是固定/相同的。比如這裡打印一個班中學生的姓名和年齡,這個班大部分為同齡人(年齡相同),這時我們就可以給“年齡”這個形參賦一個默認的值。

說明: 默認參數只是一個有默認值的位置參數,因此它還是受到位置參數的限制。默認參數可以避免位置參數的一個限制:傳遞實參的個數,但是參數位置(順序)仍然還是要一一對應。另外,默認參數必須放在位置參數後面(自己想想為什麼)。

函數定義:兩個位置參數,後面一個���默認參數(有默認值)
def my_print(name, age=12):
    print('NAME: %s' % name)
    print('AGE: %d' % age)
正確調用:按照位置參數傳值
>>> my_print('Tom', 18)
NAME: Tom
AGE: 18

age取的是函數調用時傳遞過來的實參

正確調用:不給age形參傳值,age將取默認值
>>> my_print('Tom')
NAME: Tom
AGE: 12

函數調用時沒有給形參age傳值,因此age取的是默認值

錯誤調用:試圖跳過前面的位置參數直接給後面的默認參數傳值
>>> my_print(18)
NAME: 18
AGE: 12

可見,我們明明是想傳遞18給形參age的,結果18被賦給了name,而age仍然取得是默認值。上面已經提到過,位置參數只是可以讓我們少傳一些參數,但是不能改變參數的位置和順序。另外,這也說明了默認參數為什麼一定要放在後面:因為實參與形參是從前到後一一有序的對應關系,也就是說在給後面參數傳值的時候,不論前面的參數是否有默認值,必須要先給前面的參數先賦值。

錯誤調用:實參個數超過形參個數
>>> my_print('Tom', 18, 'F')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: my_print() takes from 1 to 2 positional arguments but 3 were given

這裡要說明的是:默認參數只能相應的減少實參的個數,但是不能增加實參的個數。這個很容易想明白,不做過多解釋,只是為下面的可變長(參數)做鋪墊。

3. 可變(長)參數

可變(長)參數:顧名思義,是指長度可以改變的參數。通俗點來講就是,可以傳任意個參數(包括0個)。

可變(長)參數的應用場景:通常在寫一個需要對外提供服務的方法時,為了避免將來添加或減少什麼新的參數使得所有調用該方法的代碼點都要進行修改的情況發生,此時就可以用一個可變長的形式參數。

說明: 默認參數允許我們調用函數時,可以少傳遞一些實參;而可變(長)參數則允許我們調用函數時,可以多傳遞任意個實參。另外,可變長參數應該定義在默認參數之後,因為調用函數時傳遞的實參會按照順序一一賦值給各個形參,如果可變(長)參數定義在前面,那麼後面的參數將永遠無法取得傳遞的值。可變(長)參數名稱通常用args,且參數名稱前要有個"*"號,表示這是一個可變長參數。

函數定義:一個位置參數、一個默認參數、一個可變長參數
def my_print(name, age=12, *args):
    print('NAME: %s' % name)
    print('AGE: %d' % age)
    print(args)

再次強調:位置參數、默認參數、可變長參數在函數定義中的位置不能變。

正確調用:只傳遞一個實參
>>> my_print('Tom')
NAME: Tom
AGE: 12
()

方法調用時,只傳遞了一個實參,該實參會按照函數中參數的定義位置賦值給形參name,因此name的值為‘Tom’;而形參age沒有接收到實參,但是它有默認值,因此它取的是默認值12;需要注意的是可變參數args也沒有接收到傳遞值,但是打印出來的是一對小括號(),說明args參數在函數內部會被轉換成tuple(元祖)類型,當沒有接收到實參時便是一個空tuple。

正確調用:傳遞兩個實參
>>> my_print('Tom', 18)
NAME: Tom
AGE: 18
()

與值傳遞一個實參的情況基本相同,只是默認參數接收到了傳遞值,不再取默認值。

正確調用:傳遞兩個以上的實參

比如,現在需要多接收並打印一個人的性別(F: 表示女,M: 表示男),可以這樣用:

>>> my_print('Tom', 18, 'F')
NAME: Tom
AGE: 18
('F',)

比如,現在需要多接收並打印一個人的性別(F: 表示女,M: 表示男)和籍貫信息,可以這樣用:

>>> my_print('Tom', 18, 'F', 'Hebei')
NAME: Tom
AGE: 18
('F', 'Hebei')

當然,我們也可以直接將一個tuple或list實例傳遞給形參args,但是tuple實例前也要加上*號作為前綴:

>>> t = ('F', 'Hebei')
>>> my_print('Tom', 19, *t)
NAME: Tom
AGE: 19
('F', 'Hebei')

你甚至可以將傳遞給形參name和age的實參也放到要傳遞的tuple實例中,但是最好不要這樣做,因為很容易發生混亂:

>>> t = ('Jerry', 10, 'F', 'Hebei')
>>> my_print(*t)
NAME: Jerry
AGE: 10
('F', 'Hebei')
實際應用說明:

由於args接收到實參之後會被轉換成一個tuple(元祖)的實例,而tuple本身是一個序列(有序的隊列),因此我們可以通過下標(args[n])來獲取相應的實參。但是我們需要在函數使用文檔中寫明args中各實參的傳遞順序及意義,並且在獲取args中的元素之前應該對args做非空判斷。因此函數的定義及調用結果應該是這樣的:

函數定義:

def my_print(name, age=12, *args):
    """
    Usage: my_print(name[, age[, sex[, address]]])
    :param name: 姓名
    :param age: 年齡
    :param args: 性別、籍貫
    :return: None
    """
    print('NAME: %s' % name)
    print('AGE: %d' % age)
    if len(args) >= 1:
        print('SEX: %s' % args[0])
    if len(args) >= 2:
        print('ADDRESS: %s' % args[1])

函數調用及結果:

>>> my_print('Tom')
NAME: Tom
AGE: 12
>>> my_print('Tom', 18)
NAME: Tom
AGE: 18
>>> my_print('Tom', 18, 'F')
NAME: Tom
AGE: 18
SEX: F
>>> my_print('Tom', 18, 'F', 'Hebei')
NAME: Tom
AGE: 18
SEX: F
ADDRESS: Hebei
>>> t = ('F', 'Hebei')
>>> my_print('Tom', 19, *t)
NAME: Tom
AGE: 19
SEX: F
ADDRESS: Hebei

4. 關鍵字參數

關鍵字參數:顧名思義,是指調用函數時通過關鍵字來指定是為哪個形參指定的實參,如name="Tom", age=10。

說明: 這個地方很容易發生思維混淆,所以需要特別說明一下:這裡所說的關鍵字參數可以理解為以key=value的形式傳遞給函數的實參,注意是實參不是函數定義時聲明的形參。而且在函數調用時可以通過關鍵字參數給函數定義時所聲明的位置參數和默認參數傳值(但是不能通過關鍵參數給可變長參數*args傳值)。如果想實現像可變長參數那樣在函數調用時傳遞任意個關鍵字參數給函數,則需要在函數定義時聲明一個接受“可變長關鍵詞參數”的形參,該形參名稱通常為kwargs,且前面需要帶"**"前綴--**kwargs

關鍵字參數應用場景:關鍵字參數一方面可以允許函數調用時傳遞實參的順序與函數定義時聲明形參的順序不一致,提高靈活性;另一方面,它彌補了可變長參數的不足。想一下,如果想為上面定義了可變長參數的函數只傳遞“籍貫”參數就必須同時傳遞“性別”參數;另外還要不斷地判斷tuple的長度,這是相當不方便的。而關鍵參數可以通過關鍵字來判斷某個參數是否有傳遞值並獲取該參數的實參值。

函數定義:位置參數、默認參數、可變(長)參數、關鍵字參數
def my_print(name, age=12, *args, **kwargs):
    print('NAME: %s' % name)
    print('AGE: %d' % age)
    print(args)
    print(kwargs)

正確調用:只傳遞一個實參

>>> my_print('Tom')
NAME: Tom
AGE: 12
()
{}

方法調用時,只傳遞了一個實參,該實參會按照函數中參數的定義位置賦值給形參name,因此name的值為‘Tom’;而形參age沒有接收到實參,但是它有默認值,因此它取的是默認值12;可變參數args也沒有接收到傳遞值,因此args的值是一個空元組;重點需要注意的是關鍵字參數kwargs也沒有接收到傳遞值,但是其打印值為一個空字典(dict)實例。

正確調用:傳遞兩個實參
>>> my_print('Tom', 18)
NAME: Tom
AGE: 18
()
{}

與值傳遞一個實參的情況基本相同,只是默認參數接收到了傳遞值,不再取默認值。

>>> my_print(age=18, name='Tom')
NAME: Tom
AGE: 18
()
{}

可以不按照形參聲明的順序傳遞實參

正確調用:傳遞兩個以上的實參

以非key=value的形式傳遞所有參數:

>>> my_print('Tom', 18, 'F', 'Hebei')
NAME: Tom
AGE: 18
('F', 'Hebei')
{}

可見後面多余的兩個實參都傳遞給了可變長參數args

最後一個addr參數以key=value的形式傳遞:

>>> my_print('Tom', 18, 'F', addr='Hebei')
NAME: Tom
AGE: 18
('F',)
{'addr': 'Hebei'}
>>>

最後兩個參數sex和addr都以key=value的形式傳遞:

>>> my_print('Tom', 18, sex='F', addr='Hebei')
NAME: Tom
AGE: 18
()
{'sex': 'F', 'addr': 'Hebei'}

由以上兩個示例可見,對於除去傳遞給位置參數和默認參數之外多余的參數,如果是直接以value的形式提供實參,則會被傳遞給可變長參數args而成為一個元組中的元素;如果是以key=value的形式提供實參,則會被傳遞給關鍵字參數kwargs而成為一個字典中的元素。

納尼?你還想試試其他傳參方式?看看下面有沒有你想要的
>>> t=('Jerry', 19, 'F', 'Hebei')
>>> my_print(*t)
NAME: Jerry
AGE: 19
('F', 'Hebei')
{}
>>> d={'name':'Tom', 'age':18, 'sex':'F', 'addr':'Hebei'}
>>> my_print(**d)
NAME: Tom
AGE: 18
()
{'sex': 'F', 'addr': 'Hebei'}
>>> d={'sex':'F', 'addr':'Hebei'}
>>> my_print(age=18, name='Tom', **d)
NAME: Tom
AGE: 18
()
{'sex': 'F', 'addr': 'Hebei'}
>>> t=('Tom', 18, 'abc')
>>> d={'sex':'F', 'addr':'Hebei'}
>>> my_print(*t, **d)
NAME: Tom
AGE: 18
('abc',)
{'sex': 'F', 'addr': 'Hebei'}
>>> my_print(name='Tom', 18, sex='F', addr='Hebei')
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

4. 總結

關於Python中的函數參數說了這麼多,我覺得很多必要來個總結:

  • Python中函數的參數有4中:位置參數、默認參數(有默認值的位置參數)、可變(長)參數、關鍵字參數(特殊的、優化過的可變長參數);
  • 無論是函數定義時聲明形參,還是函數調用時傳遞實參,都必須按照上面的順序進行(允許只包含一種或幾種不同種類的參數);簡單點來說就是,調用函數時key=value形式的關鍵參數必須在value形式的參數後面;
  • Python函數調用時,傳遞的實參會對應的傳遞給相應的形參,同一個形參接收到的實參不能多也不能少;

四、全局變量與局部變量


一個程序中的變量是有作用域的,作用域的大小會限制變量可訪問的范圍。根據作用域范圍的大小不同可以分為:全局變量和局部變量。顧名思義,全局變量表示變量在全局范圍內都可以被訪問,而局部變量只能在一個很小的范圍內生效。這就好比國家主席與各省的省長:在全國范圍內國家主席都是同一個人,因此國家主席就是個全局變量;而各省的省長只能在某個省內生效,河北省省長是一個人,河南省省長又是另外一個人,因此省長就是個局部變量。對於Python編程語言而言,定義在一個函數內部的變量就是一個局部變量,局部變量只能在其被聲明的函數內訪問;定義在函數外部的變量就是全局變量,全局變量可以在整個程序范圍內訪問。

來看個示例:

#!/usr/bin/env python
# -*- encoding:utf-8 -*-

name = 'Tom'

def func1():
    age = 10
    print(name)
    print(age)

def func2():
    sex = 'F'
    print(name)
    print(sex)

print(name)
func1()
func2()

輸出結果:

Tom
Tom
10
Tom
F

上面的示例中,name是一個全局變量,因此它在程序的任何地方都可以被訪問;而func1函數中的age變量和func2函數中的sex變量都是局部變量,因此它們只能在各自定義的函數中被訪問。

問題1:如果在函數內定義一個與全局變量同名的變量會怎樣?
#!/usr/bin/env python
# -*- encoding:utf-8 -*-

name = 'Tom'

def func3():
    name = 'Jerry'
    print(name)

print(name)
func3()
print(name)

輸出結果:

Tom
Jerry
Tom

通過上面兩個示例的輸出結果我們可以得出這樣的結論:

  • 函數內引用一個變量時,會先查找該函數內部是否存在這樣一個局部變量,如果存在則直接引用該局部變量,否則將查找並引用全局變量;
  • 對局部變量的賦值並不會對全局變量產生什麼影響,因為它們本來就是兩個不相關的變量。
問題2:如果想在上面示例中的函數內部為全局變量重新賦值怎麼辦?

可以在函數內部通過global關鍵字聲明該局部變量就是全局變量:

#!/usr/bin/env python
# -*- encoding:utf-8 -*-

name = 'Tom'

def func4():
    global name
    name = 'Jerry'
    print(name)

print(name)
func4()
print(name)

輸出結果:

Tom
Jerry
Jerry

可見全局name的值的確被func4函數內部的操作改變了。

問題3:能不能將全局變量通過傳參的方式傳遞給函數,然後在函數內部對全局變量做修改呢?

變量值的改變通常有兩種方式:(1) 重新賦值 (2) 改變原有值。要想在函數內部通過重新賦值來改變全局變量的值,則只能通過上面介紹的使用global關鍵字來完成,通過傳參是無法實現的。而要想在函數內部改變全局變量的原有值的屬性就要看該參數是值傳遞還是引用傳遞了,如果是引用傳遞則可以在函數內部對全局變量的值進行修改,如果是值傳遞則不可以實現。具體請看下面的分析。

Python中的變量回收機制:

  • 變量存放在內存中
  • 內存是有地址的(門牌號)
  • 變量是對內存地址的引用
  • Python對內存地址引用次數是有記數的
  • Python解釋器會定期將引用次數為0的內存地址清空--釋放

函數名也是變量,函數體就是這個變量的值:
calc = lambda x: x*3

五、值傳遞與引用傳遞


這個話題在幾乎所有的編程語言中都會涉及,之所以把它放到最後是因為覺得這個問題對於編程新手來說比較難理解。與 “值傳遞與引用傳遞” 相似的概念是 “值拷貝與引用拷貝”。前者主要是指函數調用時傳遞參數的時候,後者是指把一個變量賦值給其他變量或其他一些專門的拷貝操作(如深拷貝和淺拷貝)的時候。

這裡我們需要先來說明下定義變量的過程是怎樣的。首先,我們應該知道變量的值是保存在內存中的;以name='Tom'為例,定義變量name的過程是這樣的:

  1. 在內存中分配一塊內存空間;
  2. 將變量的值(字符串“Tom”)存放到這塊內存空間;
  3. 將這塊內存空間的地址(門牌號)賦值給變量name;

也就是說變量保存的不是真實的值,而是存放真實值的內存空間的地址。

“值拷貝”和“值傳遞”比較好理解,就是直接把變量的值在內存中再復制一份;也就是說會分配並占用新的內存空間,因此變量指向的內存空間是新的,與之前的變量及其指向的內存空間沒有什麼關聯了。而“引用拷貝”和“引用傳遞”僅僅是把變量對內存空間地址的引用復制了一份,也就是說兩個變量指向的是同一個內存空間,因此對一個變量的值的修改會影響其他指向這個相同內存空間的變量的值。實際上,向函數傳遞參數時傳遞的也是實參的“值拷貝或引用拷貝”。

因此當我們判斷一個變量是否被修改時,只需要搞明白該變量所指向的內存地址以及該內存地址對應的內存空間中的值是否發生了改變即可。

示例1:
name1 = 'Tom'
name2 = name1
name2 = 'Jerry'

print('name1: %s' % name1)
print('name2: %s' % name2)

思考:name1被改變了嗎?

分析下上面操作的過程:

  • 定義變量name1:在內存中開辟一塊空間,將字符串'Tom'保存到該內存空間,然後name1指向該內存空間的地址;

  • 定義變量name2,並將name1賦值給它:實際上就是讓name2也指向name1所指定的內存空間;

  • 為變量name2重新賦一個新值:在內存中開辟一塊新的空間,將字符串‘Jerry’保存到該內存空間,然後name2指向該內存空間的地址;

name1指向的內存地址發生改變了嗎?-- 沒有,因為name1並沒有被重新進行賦值操作。

name1所指向的內存空間中的內容改變了嗎? -- 沒有,並沒有對它做什麼,並且字符串本就是個常量,是不可能被改變的。

So, 答案已經有了,name1並沒有被改變,因此輸出結果是:

name1: Tom
name2: Jerry
示例2:
num1 = 10
num2 = num1
num2 += 1

print('num1: %d' % num1)
print('num2: %d' % num2)

與示例1過程相似,只是+=操作也是一個賦值的過程,其他不再做過多解釋。
輸出結果:

num1: 10
num2: 11
示例3:
list1 = ['Tom', 'Jerry', 'Peter', 'Lily']
list2 = list1
list2.pop(0)

print('list1: %s' % list1)
print('list2: %s' % list2)

思考: list1被改變了嗎?

分析上面操作的過程:

  • 定義變量list1:在內存中開辟一塊空間,將列表 ['Tom', 'Jerry', 'Peter', 'Lily'] 保存到該內存空間(列表在內存中的保存沒這麼簡單,此處只是為了便於理解),然後list1指向該內存空間的地址;
  • 定義變量list2,並將list1賦值給它:實際上就是讓list2也指向list1所指定的內存空間;
  • 移除list2中的一個元素,就是從list2指向的內存地址所對應的內存空間中的內容中移除一個元素;

list1指向的內存地址發生改變了嗎?-- 沒有,因為list1並沒有被重新進行賦值操作。

list2所指向的內存空間中的內容改變了嗎? -- 是的,因為list1和list2指向的是同一個內存地址,通過list2修改了該內存地址中的內容後就相當於修改了list1。

So, 答案已經有了,list1被改變了,因此輸出結果是:

list1: ['Jerry', 'Peter', 'Lily']
list2: ['Jerry', 'Peter', 'Lily']
示例4:

其實函數參數的傳遞過程也是類似的,比如:

num1 = 10
name1 = 'Tom'
list1 = ['Tom', 'Jerry', 'Peter', 'Lily']

def fun1(num2, name2, list2):
    num2 += 1
    name2 = 'Jerry'
    list2.pop(0)
    print('num2: %d' % num2)
    print('name2: %s' % name2)
    print('list2: %s' % list2)


fun1(num1, name1, list1)
print('num1: %d' % num1)
print('name1: %s' % name1)
print('list1: %s' % list1)

為了跟上面的示例做對比,我故意把func1函數中的形參的名稱寫為num2、name2和list2,實際上他們可以為任意有意義的名稱。

輸出結果:

num2: 11
name2: Jerry
list2: ['Jerry', 'Peter', 'Lily']
num1: 10
name1: Tom
list1: ['Jerry', 'Peter', 'Lily']
那麼Python中變量拷貝是值拷貝還是引用拷貝呢?Python中的參數傳遞是值傳遞還是應用傳遞呢?

其實這是相同的問題,因為上面說過了:參數傳遞的過程實際上就像先拷貝,然後將拷貝傳遞給形參。如果是值拷貝,那麼調用函數傳參時就是值傳遞;如果是引用拷貝,那麼調用函數傳參時就是引用(內存地址)傳遞。其實通過上面的示例,我們大概可以猜測到對於列表類型的變量貌似是引用傳遞,但是數字和字符串類型的變量是值傳遞還是引用傳遞呢?Python中的參數的傳遞都是引用傳遞,關於這個問題我們可以通過Python內置的一個id()函數來進行驗證。id()函數會返回指定變量所指向的內存地址,如果是引用傳遞,那麼實參和被賦值後的形參所指向的內存地址肯定是相同的。事實上,確實如此,如下所示:

num1 = 10
name1 = 'Tom'
list1 = ['Tom', 'Jerry', 'Peter', 'Lily']

def fun1(num2, name2, list2):
    print(id(num2), id(name2), id(list2))

print(id(num1), id(name1), id(list1))
fun1(num1, name1, list1)

輸出結果:

1828586224 1856648389328 1856648385800
1828586224 1856648389328 1856648385800

實參和形參的內存地址一致,說明Python中的參數傳遞確實是“引用傳遞”。

這篇文章寫了很久,想說的東西太多。有時候手放到鍵盤上放了許久,卻不知從何寫起。算是對知識點的梳理,也希望對他人有所幫助。關於Python中關於函數的其它內容,如:函數遞歸、匿名函數、嵌套函數、高階函數等,之後再講。

Copyright © Linux教程網 All Rights Reserved