Excel VBA和Python除了使用内部提供的函数和第三方提供的函数外,还可以自己定义函数来实现一定的功能。自定义函数同样可以被反复调用,从而节省代码量并提高编程效率。
函数定义和调用
【Excel VBA】
在Excel VBA中,有两种形式的函数,一种是没有返回值的,称为过程;另一种是可以有返回值的,称为函数。
过程由Sub…End Sub结构定义。其语法格式为:
[ | Private | Public ] _
Sub name[([param[, ...]])]
执行语句…
End Sub
其中,Private和Public为可选项,定义过程是私有的还是公共的,即定义过程的作用范围。使用Private关键字时说明该过程只在本模块中使用,一般是为模块中的其他函数服务的;使用Public关键字时该过程可被其他模块中的函数调用。Sub…End Sub结构定义过程的主体,name为过程名称,param定义过程的参数,Sub行和End Sub行之间为需要执行的语句行。
下面的过程运行时在立即窗口输出一段字符串:
Private Sub Output()
Debug.Print "Hello,VBA!"
End Sub
在同一模块中编写过程代码,调用该过程。
Sub Test()
Output
End Sub
运行该过程,在立即窗口中输出结果:
Hello,VBA!
Excel VBA中,函数由Function…End Function结构定义。其语法格式为:
[ | Private | Public ] _
Function name[type][([param[, ...]])] [As type]
执行语句…
End Function
其中,Function…End Function结构定义函数的主体,As type表示返回值的数据类型,其他关键字和名称的说明与过程的相同。
下面的函数Sum计算两个给定浮点数的和,结果以浮点数返回:
Private Function Sum(sngA As Single,sngB As Single) As Single
Sum=sngA+sngB
End Function
在同一模块中编写过程代码,调用该函数。
Sub Test()
Debug.Print Sum(1.2, 8.3)
End Sub
运行过程,在立即窗口中输出1.2和8.3的和,即9.5。
【Python】
Python中自定义函数的语法格式为:
def functionname(parameters):
"函数说明文档"
函数体
return [表达式]
其中,def和return是关键字,functionname为函数名,parameters为参数列表。注意小括号后面有1个冒号。冒号后面第1行添加注释,说明函数的功能,可以使用help函数进行查看。函数体各语句用代码定义函数的功能。def关键字打头,return语句结束,有表达式时返回函数的返回值,没有表达式时返回None。
函数定义好后,可以在模块中其他位置进行调用,调用时指定函数名和参数,如果有返回值,指定引用返回值的变量。
函数可以没有参数,也可以没有返回值。下面定义1个函数,用一连串的星号作为输出内容的分隔行。定义该函数后进行3种运算,并在输出结果时调用该函数绘制星号分隔行分隔各种运算结果。该文件路径为Samples\ch10\Python\sam10-001.py。
def starline(): #定义starline函数,绘制星号分隔行
"星号分隔行" #函数的功能说明
print("*"*40) #输出40个*
return
a=1;b=2
print("a={},b={}".format(1,2))
print("a+b={}".format(a+b)) #对两个数作加法运算
starline() #调用starline函数绘制分隔行
print("a={},b={}".format(1,2))
print("a-b={}".format(a-b)) #对两个数作减法运算
starline() #调用starline函数绘制分隔行
print("a={},b={}".format(1,2))
print("a*b={}".format(a*b)) #对两个数作乘法运算
help(starline) #输出starline函数的功能说明
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-001.py
a=1,b=2
a+b=3
****************************************
a=1,b=2
a-b=-1
****************************************
a=1,b=2
a*b=2
Help on function starline in module __main__:
starline()
星号分隔行
可见,函数定义好以后,可以进行重复调用,提高编程效率。最后显示了starline函数的功能说明。
上面定义的starline函数没有参数,也没有返回值。下面定义一个mysum函数,对两个给定的数求和。所以,该函数有2个输入参数和1个返回值。该文件路径为Samples\ch10\Python\sam10-002.py。
def mysum(a,b): #求和
"求两个数的和"
return a+b
print("3+6={}".format(mysum(3,6))) #计算和输出3和6的和
print("12+9={}".format(mysum(12,9))) #计算和输出12和9的和
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-002.py
3+6=9
12+9=21
有多个返回值的情况
【Excel VBA】
在Excel VBA中函数有多个返回值时,可以使用两种方法返回它们。一种是利用过程传入参数,完成计算后用参数传出返回值;另一种是将返回值保存到数组中,返回数组。
下面编写Sum过程计算两个给定参数的和和差,然后将得到的和和差用参数传回。
Private Sub Sum(sngA As Single, sngB As Single)
'传入计算参数,计算结果用参数返回
Dim sngC As Single, sngD As Single
sngC = sngA + sngB
sngD = sngA - sngB
sngA = sngC
sngB = sngD
End Sub
在同一模块中编写过程代码,调用Sum过程,输入参数完成计算后将返回值用参数传回。
Sub Test()
Dim sngA As Single, sngB As Single
sngA = 8
sngB = 3
Sum sngA, sngB
Debug.Print sngA; sngB
End Sub
运行过程,在立即窗口中输出计算结果。
11 5
注意,Test过程中调用Sum过程前后参数sngA和sngB的值发生了改变。这个是有说法的,将在后面讲传址还是传值时进行介绍。
传递多个返回值的另一种方法是将它们保存到数组中返回。下面的Sum函数将两个给定参数的和和差保存到数组中,然后返回数组。
Private Function Sum2(sngA As Single, sngB As Single)
Dim sngT(1) As Single
sngT(0) = sngA + sngB
sngT(1) = sngA - sngB
Sum2 = sngT
Erase sngT
End Function
在同一模块中编写过程代码,调用Sum函数并将计算结果返回到一个数组中,然后在立即窗口输出数组中的值。
Sub Test2()
Dim sngR
sngR = Sum2(8, 3)
Debug.Print sngR(0); sngR(1)
End Sub
运行过程,在立即窗口中输出8和3的和和差。
11 5
【Python】
Python中,函数有多个返回值时可以用return语句直接返回,也可以将各返回值写入列表,然后返回列表。
下面定义1个函数,指定2个参数值,返回它们的和和差。该文件路径为Samples\ch10\Python\sam10-003.py。
def mycomp(a,b): #计算两个给定值的和和差
c=a+b
d=a-b
return c,d
c,d=mycomp(2,3) #调用mycomp函数,计算2和3的和和差
print("2+3={}".format(c)) #输出和
print(“2-3={}”.format(d)) #输出差
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-003.py
2+3=5
2-3=-1
有多个返回值时,也可以将这多个返回值添加到列表中,用return语句返回该列表。下面改写上例。该文件路径为Samples\ch10\Python\sam10-004.py。
def mycomp(a,b): #计算两个给定值的和和差
data=[]
data.append(a+b)
data.append(a-b)
return data #和和差以列表返回
data=mycomp(2,3) #调用mycomp函数,计算2和3的和和差
print(data) #输出元素为和和差的列表
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-004.py
[5, -1]
10.3.3可选参数和默认参数
可选参数是非必需参数,可以有,也可以没有。默认参数是定义了默认值的参数,它们必须是可选参数,不能给必需参数定义默认值。对于可选参数而言,调用函数时如果没有赋值,就使用预先定义的默认值,如果赋了新值,就覆盖默认值。
【Excel VBA】
Excel VBA中用Optional关键字定义可选参数。下面定义一个Para过程,该过程有3个参数,后面两个参数为可选参数。
Private Sub Para(strID As String, Optional strName As String, _
Optional sngScore As Single)
Debug.Print strID & vbTab;
'如果没有使用可选参数就不输出它们的值
If Not strName = "" Then Debug.Print strName & vbTab;
If Not sngScore = 0 Then Debug.Print sngScore;
Debug.Print
End Sub
在同一模块中编写过程代码,调用Para过程并使用不同数目的参数。
Sub Test()
Para "ID001"
Para "ID001", "姜林"
Para "ID001", "徐庶", 95
Para "ID001", , 95 '没有给第2个参数赋值
End Sub
运行过程,在立即窗口中输入下面的结果。
ID001
ID001 姜林
ID001 徐庶 95
ID001 95
可以给可选参数定义默认值,例如下面给sngScore参数定义默认值80。
Private Sub Para2(strID As String, Optional strName As String, _
Optional sngScore As Single=80)
Debug.Print strID & vbTab;
If Not strName = "" Then Debug.Print strName & vbTab;
Debug.Print sngScore;
Debug.Print
End Sub
编写测试过程。
Sub Test2()
Para2 "ID001"
Para2 "ID001", , 90
End Sub
运行过程,在立即窗口中输出结果。
ID001 80
ID001 90
可见,虽然调用Para2过程时没有给第3个参数赋值,但是因为给该参数定义了默认值,输出时仍然输出了它的默认值80。如果给该参数赋了新值,则新值覆盖默认值。
【Python】
定义函数时,对函数参数使用赋值语句可以指定该参数的默认值。下面定义para函数,该函数有两个参数,即id和score,指定score参数的默认值为80。该文件路径为Samples\ch10\Python\sam10-005.py。
def para(id, score=80): #指定score参数的默认值为80
print("ID: ",id) #输出id
print("Score: ",score) #输出得分
return
para("No001") #调用para函数,只指定id参数的值
para("No002",90) #调用para函数,指定两个参数的值
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-005.py
ID: No001
Score: 80
ID: No002
Score: 90
可见,没有传入score参数的值时,取了默认值80。
可变参数
所谓可变参数,指的是参数的个数是不确定的,可以是0个、1个直至任意个。
【Excel VBA】
Excel VBA中用一个数组指定可变参数,并且在参数前添加ParamArray关键字。包含可变参数的函数的定义如下所示:
Function FunName(ParamArray paras() As Variant)
执行语句…
End Function
注意,可变参数的数据类型必须是变体类型的。可变参数必须是参数列表中的最后一个参数,并且不能与可选参数一起使用。
下面定义函数求取一组数据的和,这组数的个数是不确定的。
Private Function MySum(ParamArray paras()) As Single
Dim para
Dim sngR As Single
sngR = 0
For Each para In paras
sngR = sngR + para
Next
MySum = sngR
End Function
编写过程调用MySum函数累加求和。
Sub Test()
Debug.Print MySum(1, 2, 3)
Debug.Print MySum(1, 2, 3, 4, 5, 6, 7, 8)
End Sub
运行过程,在立即窗口中输出不同个数参数的计算结果。
6
36
【Python】
Python中包含可变参数的函数的定义如下所示:
def functionname([args,] *args_tuple ):
函数体
return [表达式]
其中,[args,]定义必选参数,*args_tuple定义可变参数。*args_tuple是作为1个元组传递进来的。
下面定义1个函数进行求和运算,该运算的第1个数据是确定的,后面的数据不确定,数据个数和数据大小都不确定。该文件路径为Samples\ch10\Python\sam10-006.py。
def mysum(arg1,*vartuple): # arg1为必选参数,*vartuple为可变参数
sum=arg1
for var in vartuple: #累加求和
sum+=var
return sum
a=mysum(10,10,20,30) #调用mysum函数,指定参数求和
print(a)
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-006.py
70
参数为字典
【Excel VBA】
Excel VBA中,字典可以像其他数据类型的数据一样传递。下面的过程OutputData在立即窗口中输出指定字典的键值对数据。
Private Sub OutputData(dicT As Dictionary)
Dim strID
For Each strID In dicT.Keys
Debug.Print strID & vbTab & dicT(strID)
Next
End Sub
编写过程创建字典并调用OutputData过程输出数据。
Sub Test()
Dim dicT As Dictionary
Set dicT = New Dictionary
dicT.Add "NO001", 89
dicT.Add "NO002", 92
dicT.Add "NO003", 79
OutputData dicT
End Sub
运行过程,在立即窗口中输出新创建的字典对象dicT的数据。
NO001 89
NO002 92
NO003 79
【Python】
Python中,如果函数的参数带两个星号,表示该参数为字典。传递字典参数的函数语法格式为:
def functionname([args,] **args_dict):
"函数_文档字符串"
函数体
return [表达式]
其中,[args,]定义必选参数,**args_dict定义字典参数。注意有2个星号。字典参数对应用赋值语句表示的2个实参,对应于字典的键和值。
下面定义1个函数,参数为字典,功能是输出字典数据。该文件路径为Samples\ch10\Python\sam10-007.py。
def paradict(**vdict): #参数为字典
print(vdict)
paradict(id="No001",score=80) #调用函数,注意实参的输入方式
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-007.py
{'id': 'No001', 'score': 80}
传值还是传址
在函数中,对象作为参数传递时,需要搞清楚函数传递的是对象的地址还是对象的值。传址和传值的主要区别在于,如果在函数体中对参数的值进行了修改,调用该函数前后,如果是传址方式传递,该参数的值会改变,如果是传值方式传递,该参数的值不变。
【Excel VBA】
在Excel VBA中,在过程中传递参数有传值和传址两种方式,缺省时按传址方式传递参数。10.3.2小节的Excel VBA示例代码中,Sum过程用两个参数传入数据,然后用这两个参数传出它们的和和差。在测试过程中调用Sum过程前后,参数的值发生了变化,所以默认时Excel VBA函数是按传址方式传递参数的。使用ByVal关键字,可以指定参数按传值方式进行传递。
下面修改Sum过程,设置两个参数按传值方式进行传递。
Private Sub Sum(ByVal sngA As Single, ByVal sngB As Single)
Dim sngC As Single, sngD As Single
sngC = sngA + sngB
sngD = sngA - sngB
sngA = sngC
sngB = sngD
End Sub
在同一模块中编写过程代码,调用Sum过程进行计算。
Sub Test()
Dim sngA As Single, sngB As Single
sngA = 8
sngB = 3
Sum sngA, sngB
Debug.Print sngA; sngB
End Sub
运行过程,在立即窗口中输出计算结果。
8 3
可见,由于Sum过程设置参数按传值方式传递,在测试过程中调用Sum过程前后参数的值没有变化。
【Python】
在Python中,对于不可变类型,包括字符串、元组和数字,作为函数参数时是按传值方式传递的。此时传递的是对象的值,修改的是一个复制的对象,不影响对象本身。对于可变类型,包括列表和字典,作为函数参数时是按传址方式传递的。此时传递对象本身,修改它后在函数外部也会受影响。
下面举例进行说明。对于不可变类型,下面的函数传递1个字符串,查看调用该函数前后参数的值有没有变化。该文件路径为Samples\ch10\Python\sam10-008.py。
def TP(a):
a= "python" #修改参数的值为"python"
b= "hello" #给变量b赋初值"hello"
TP(b) #将变量b作为参数调用函数
print(b) #输出变量b的值
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-008.py
hello
可见,调用函数前后变量的值不变,参数按照传值方式传递。
对于可变类型,下面的函数传递1个列表,在函数体中给列表添加1个列表元素。该文件路径为Samples\ch10\Python\sam10-009.py。
def TP(lst): #参数为列表
lst.append([6,7,8,9]) #给传入的列表添加1个列表元素
return
lst = [1,2,3,4,5]
print(lst)
TP(lst) #将列表作为参数调用函数
print(lst)
在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:
>>> = RESTART: …/Samples/ch10/Python\sam10-009.py
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, [6, 7, 8, 9]]
可见,调用函数前后列表发生了变化,参数按照传址方式传递。