Python從菜鳥到高手(17):改變函數參數的值

如果將一個變量作爲參數傳入函數,並且在函數內部改變這個變量的值,那麼結果會怎麼樣呢?我們不妨做一個實驗。

x = 20
s = "世界您好"
def test(x,s):
    x = 40
    s = "hello world"
test(x,s)
print(x,s)

執行這段代碼,會輸出如下圖所示的內容。

image.png

在上面的代碼中,首先定義了兩個變量:x和s,然後將其傳入test函數,並在該函數中修改這兩個變量的值。最後在函數外部輸出這兩個變量,得到的結果是他們的值並沒有改變。所以說,對於數值類型、字符串類型等一些簡單類型,在函數內部可以修改變量的值,但不會影響到原始變量的值。也就是說,函數內部操作的參數變量實際上是x和s的一個副本。將變量傳入函數,並修改變量值的過程與下面的代碼類似。

x = 20
s = "世界您好"
# 下面的代碼相當於函數內部的操作
x1 = x                  # x1是x的副本,相當於將x傳入函數
s1 = s                  # s1是s的副本,相當於將s傳入函數
x1 = 40
s1 = "hello world"
# 這裏相當於退出函數,在函數外部輸出x變量和s變量
print(x,s)

執行這段代碼的輸出結果與上圖完全一致。

現在讓我們再來看看下面的代碼。在這段代碼中,變量x和變量y的數據類型分別是字典和列表。

x = {"a":30, "b":20}
y = ["a","b","c"]

def test(x,y):
    x["a"] = 100
    y[1] = "abcd"
test(x,y)
print(x,y)

程序運行結果如下圖所示。

image.png

我們可以看到,如果將字典和列表變量傳入函數,在函數內部修改字典和列表變量的值,是可以影響x變量和y變量的。這就涉及到一個值傳遞和引用傳遞的問題了。如果傳遞的變量類型是數值、字符串、布爾等類型,那麼就是值傳遞,如果傳遞的變量類型是序列、對象(後面的章節介紹)等複合類型,就是引用傳遞。

值傳遞就是在傳遞時,將自身複製一份,而在函數內部接觸到的參數實際上是傳遞給函數的變量的副本,修改副本的值自然不會影響到原始變量了。而像序列、對象這樣的複合類型的變量,在傳入函數時,實際上也將其複製了一份,但複製的不是變量中的數據,而是變量的引用。因爲這些複合類型在內存是用一塊連續或不連續的內存空間保存中,要想找到這些複合類型的數據,必須得到這些內存空間的首地址,而這個首地址就是複合類型數據的引用。因此,如果將複合類型的變量傳入函數,複製的是內存空間的首地址,而不是首地址指向的內存空間本身。對於本例來說,在函數內部訪問的x和y與在函數外部定義的x和y指向同一個內存空間,所以修改內存空間中的數據,自然會影響到函數外部的x變量和y變量中的值。

現在我們已經知道了,如果要想在函數內部修改參數變量的值,從而在函數退出時,仍然保留修改痕跡,那麼就要向函數傳入複合類型的變量。這一點非常有用,我們利用函數的這個特性對某些經常使用的代碼進行抽象,這樣會使代碼更簡潔,也更容易維護。例7.3將代碼抽象演繹到了極致。

本例定義了一個名爲data的字典類型變量,字典data有3個key:d、names和products。其中d對應的值類型是一個字典,names和products對應的值類型都是列表。要求從控制檯輸入這3個key對應的值。多個值之間用逗號分隔。如“Bill,Mike,John”,在輸入完數據後,通過程序將由逗號分隔的字符串轉換成字典或列表。如果要轉換爲字典,列表偶數位置的元素爲key,奇數位置的元素爲value。如“a,10,b,20”轉換爲字典後的結果是“{a:10,b:20}”。最後輸出字典data,要將每一個key和對應的值在同一行輸出,不同的key和對應的值在不同行輸出。可能這個描述看着有點複雜,不過不要緊,我們還是先看代碼吧!

# 未使用函數抽象的代碼實現
data = {}
# 下面的代碼初始化字典data和key的值
data["d"] = {}
data["names"] = []
data["products"] = []
print("請輸入字典數據,key和value之間用逗號分隔")
# 從控制檯輸入key爲d的值
dictStr = input(":")
# 將以逗號分隔的字符串轉換爲列表
list = dictStr.split(",")
keys = []
values = []
# 將列表拆分成keys和values的兩個列表
for i in range(len(list)):
    # key
    if i % 2 == 0:
        keys.append(list[i])
    else:
        values.append(list[i])
# 利用zip和dict函數將keys和values兩個列表合併成一個字典,
# 並利用update方法將該字典追加到key爲d的值的後面。
data["d"].update(dict(zip(keys,values)))

print("請輸入姓名,多個姓名之間用逗號分隔")
# 從控制檯輸入key爲names的值
nameStr = input(":")
# 將以逗號分隔的字符串轉換爲列表
names = nameStr.split(",")
# 將列表names追加到key爲names的值的後面
data["names"].extend(names)

print("請輸入產品,多個產品之間用逗號分隔")
# 從控制檯輸入key爲products的值
productStr = input(":")
# 將以逗號分隔的字符串轉換爲列表
products = productStr.split(",")
# 將列表products追加到key爲products的值的後面
data["products"].extend(products)
# 輸出字典data中的數據,每一個key和對應的值是一行
for key in data.keys():
    print(key,":",data[key])

程序運行結果如下圖所示。

image.png

如果從功能上看,上面的代碼實現的很完美。不過問題是,如果要對多個字典進行同樣操作呢?是不是要將這些代碼複製多份?這太麻煩了,而且會造成代碼極大的冗餘。那麼接下來,我們就用函數對這段代碼進行抽象,將經常使用的代碼段提煉處理封裝在函數中。

image

在抽象代碼之前,我們要先看看有哪些代碼可以被抽象出來。本例可以抽象出來的代碼有如下幾種。

• 初始化字典data

• 從控制檯輸入以逗號分隔的字符串,並將其轉換爲列表或字典

• 輸出字典data

其中初始化字典data和輸出字典data這兩段代碼都很簡單,也很容易抽象,而第2點需要費電腦子,由於字典data中有的value是字典類型,有的value是列表類型,所以就要求這個函數既可以將字符串轉換爲列表,又可以將字符串轉換爲字典。本例採用了一個flag參數進行控制,flag是布爾類型,如果該變量的值爲True,表示將字符串轉換爲列表,如果爲False,表示將字符串轉換爲字典。

爲了一步到位,乾脆將這些抽象出來的函數放到一個單獨的Python腳本文件中,然後通過import作爲模塊導入這些函數。下面先來實現這些函數。

# 初始化函數
def init(data):
    data["d"] = {}
    data["names"] = []
    data["products"] = []
# 從控制檯採集數據,並轉化爲列表或字典的函數,flag爲True將字符串轉換爲列表,爲False,轉換爲字典
# msg表示提示文本,爲了方便,這裏假設輸入的數據以逗號分隔,也可以將分隔符通過函數參數傳入
def inputListOrDict(flag,msg):
print(msg)
# 從控制檯輸入字符串
inputStr = input(":")
# 將字符串用逗號拆分成列表
    list = inputStr.split(",")
    # 返回列表
    if flag:
        return list
    # 下面的代碼將list轉換爲字典,並返回這個字典
    keys = []
    values = []
    result = {}
    for i in range(len(list)):
        # key
        if i % 2 == 0:
            keys.append(list[i])
        else:
            values.append(list[i])
    # 返回字典
return dict(zip(keys,values))

# 輸出字典中的數據
def outDict(data):
    for key in data.keys():
        print(key,":",data[key])

在上面的代碼中定義了3個函數:init、inputListOrDict和outDict,分別用來初始化字典、從控制檯輸入字符串,並將其轉換爲列表或字典、以及在控制檯輸出字典。下面我們利用這3個函數處理兩個字典:data1和data2。

# 導入dataman.py中的所有函數
from dataman import *
# 定義字典data1
data1 = {}
# 定義字典data2
data2 = {}
# 初始化data1
init(data1)
# 初始化data2
init(data2)
# 從控制檯輸入字符串,並將其轉換爲字典,最後追加到key爲d的值的後面
data1["d"].update(inputListOrDict(False, "請輸入字典數據,key和value之間用逗號分隔"))
# 從控制檯輸入字符串,並將其轉換爲列表,最後追加到key爲names的值的後面
data1["names"].extend(inputListOrDict(True, "請輸入姓名,多個姓名之間用逗號分隔"))
# 從控制檯輸入字符串,並將其轉換爲列表,最後追加到key爲products的值的後面
data1["products"].extend(inputListOrDict(True, "請輸入產品,多個產品之間用逗號分隔"))

# 下面的代碼與對data1的操作類似
data2["d"].update(inputListOrDict(False, "請輸入字典數據,key和value之間用逗號分隔"))
data2["names"].extend(inputListOrDict(True, "請輸入姓名,多個姓名之間用逗號分隔"))
data2["products"].extend(inputListOrDict(True, "請輸入產品,多個產品之間用逗號分隔"))
# 輸出data1
outDict(data1)
# 輸出data2
outDict(data2)

程序運行結果如下圖所示。

image.png

怎麼樣,利用函數將經常使用的代碼抽象成了3個函數,是不是在使用起來很方便呢?尤其在處理多個字典的情況下更是如此。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章