如果将一个变量作为参数传入函数,并且在函数内部改变这个变量的值,那么结果会怎么样呢?我们不妨做一个实验。

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

执行这段代码,会输出如下图所示的内容。

在上面的代码中,首先定义了两个变量:x和s,然后将其传入test函数,并在该函数中修改这两个变量的值。最后在函数外部输出这两个变量,得到的结果是他们的值并没有改变。所以说,对于数值类型、字符串类型等一些简单类型,在函数内部可以修改变量的值,但不会影响到原始变量的值。也就是说,函数内部操作的参数变量实际上是x和s的一个副本。将变量传入函数,并修改变量值的过程与下面的代码类似。

x = 20s = "世界您好"# 下面的代码相当于函数内部的操作x1 = x # x1是x的副本,相当于将x传入函数s1 = s # s1是s的副本,相当于将s传入函数x1 = 40s1 = "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)

程序运行结果如下图所示。

我们可以看到,如果将字典和列表变量传入函数,在函数内部修改字典和列表变量的值,是可以影响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])

程序运行结果如下图所示。

如果从功能上看,上面的代码实现的很完美。不过问题是,如果要对多个字典进行同样操作呢?是不是要将这些代码复制多份?这太麻烦了,而且会造成代码极大的冗余。那么接下来,我们就用函数对这段代码进行抽象,将经常使用的代码段提炼处理封装在函数中。

在抽象代码之前,我们要先看看有哪些代码可以被抽象出来。本例可以抽象出来的代码有如下几种。

• 初始化字典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 *# 定义字典data1data1 = {}# 定义字典data2data2 = {}# 初始化data1init(data1)# 初始化data2init(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, "请输入产品,多个产品之间用逗号分隔"))# 输出data1outDict(data1)# 输出data2outDict(data2)

程序运行结果如下图所示。

怎么样,利用函数将经常使用的代码抽象成了3个函数,是不是在使用起来很方便呢?尤其在处理多个字典的情况下更是如此。