自定义函数

Excel VBA和Python除了使用内部提供的函数和第三方提供的函数外,还可以自己定义函数来实现一定的功能。自定义函数同样可以被反复调用,从而节省代码量并提高编程效率。

函数定义和调用

【Excel VBA】

在Excel VBA中,有两种形式的函数,一种是没有返回值的,称为过程;另一种是可以有返回值的,称为函数。

过程由Sub…End Sub结构定义。其语法格式为:

code.vba
[ | Private | Public ] _
Sub name[([param[, ...]])]
    执行语句…
End Sub

其中,Private和Public为可选项,定义过程是私有的还是公共的,即定义过程的作用范围。使用Private关键字时说明该过程只在本模块中使用,一般是为模块中的其他函数服务的;使用Public关键字时该过程可被其他模块中的函数调用。Sub…End Sub结构定义过程的主体,name为过程名称,param定义过程的参数,Sub行和End Sub行之间为需要执行的语句行。

下面的过程运行时在立即窗口输出一段字符串:

code.vba
Private Sub Output()
  Debug.Print "Hello,VBA!"
End Sub

在同一模块中编写过程代码,调用该过程。

code.vba
Sub Test()
  Output
End Sub

运行该过程,在立即窗口中输出结果:

code.vba
Hello,VBA!

Excel VBA中,函数由Function…End Function结构定义。其语法格式为:

code.vba
 [ | Private | Public ] _
Function name[type][([param[, ...]])] [As type]
    执行语句…
End Function

其中,Function…End Function结构定义函数的主体,As type表示返回值的数据类型,其他关键字和名称的说明与过程的相同。

下面的函数Sum计算两个给定浮点数的和,结果以浮点数返回:

code.vba
Private Function Sum(sngA As Single,sngB As Single) As Single
  Sum=sngA+sngB
End Function

在同一模块中编写过程代码,调用该函数。

code.vba
Sub Test()
  Debug.Print Sum(1.2, 8.3)
End Sub

运行过程,在立即窗口中输出1.2和8.3的和,即9.5。

【Python】

Python中自定义函数的语法格式为:

code.python
def functionname(parameters):
   "函数说明文档"
   函数体
   return [表达式]

其中,def和return是关键字,functionname为函数名,parameters为参数列表。注意小括号后面有1个冒号。冒号后面第1行添加注释,说明函数的功能,可以使用help函数进行查看。函数体各语句用代码定义函数的功能。def关键字打头,return语句结束,有表达式时返回函数的返回值,没有表达式时返回None。

函数定义好后,可以在模块中其他位置进行调用,调用时指定函数名和参数,如果有返回值,指定引用返回值的变量。

函数可以没有参数,也可以没有返回值。下面定义1个函数,用一连串的星号作为输出内容的分隔行。定义该函数后进行3种运算,并在输出结果时调用该函数绘制星号分隔行分隔各种运算结果。该文件路径为Samples\ch10\Python\sam10-001.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = 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。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-002.py
3+6=9
12+9=21

有多个返回值的情况

【Excel VBA】

在Excel VBA中函数有多个返回值时,可以使用两种方法返回它们。一种是利用过程传入参数,完成计算后用参数传出返回值;另一种是将返回值保存到数组中,返回数组。

下面编写Sum过程计算两个给定参数的和和差,然后将得到的和和差用参数传回。

code.vba
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过程,输入参数完成计算后将返回值用参数传回。

code.vba
Sub Test()
  Dim sngA As Single, sngB As Single
  sngA = 8
  sngB = 3
  Sum sngA, sngB
  Debug.Print sngA; sngB
End Sub

运行过程,在立即窗口中输出计算结果。

code.vba
11  5

注意,Test过程中调用Sum过程前后参数sngA和sngB的值发生了改变。这个是有说法的,将在后面讲传址还是传值时进行介绍。

传递多个返回值的另一种方法是将它们保存到数组中返回。下面的Sum函数将两个给定参数的和和差保存到数组中,然后返回数组。

code.vba
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函数并将计算结果返回到一个数组中,然后在立即窗口输出数组中的值。

code.vba
Sub Test2()
  Dim sngR
  sngR = Sum2(8, 3)
  Debug.Print sngR(0); sngR(1)
End Sub

运行过程,在立即窗口中输出8和3的和和差。

code.vba
11  5

【Python】

Python中,函数有多个返回值时可以用return语句直接返回,也可以将各返回值写入列表,然后返回列表。

下面定义1个函数,指定2个参数值,返回它们的和和差。该文件路径为Samples\ch10\Python\sam10-003.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-003.py
2+3=5
2-3=-1

有多个返回值时,也可以将这多个返回值添加到列表中,用return语句返回该列表。下面改写上例。该文件路径为Samples\ch10\Python\sam10-004.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-004.py
 [5, -1]

10.3.3可选参数和默认参数

可选参数是非必需参数,可以有,也可以没有。默认参数是定义了默认值的参数,它们必须是可选参数,不能给必需参数定义默认值。对于可选参数而言,调用函数时如果没有赋值,就使用预先定义的默认值,如果赋了新值,就覆盖默认值。

【Excel VBA】

Excel VBA中用Optional关键字定义可选参数。下面定义一个Para过程,该过程有3个参数,后面两个参数为可选参数。

code.vba
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过程并使用不同数目的参数。

code.vba
Sub Test()
  Para "ID001"
  Para "ID001", "姜林"
  Para "ID001", "徐庶", 95
  Para "ID001", , 95  '没有给第2个参数赋值
End Sub

运行过程,在立即窗口中输入下面的结果。

code.vba
ID001
ID001   姜林
ID001   徐庶     95
ID001    95

可以给可选参数定义默认值,例如下面给sngScore参数定义默认值80。

code.vba
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

编写测试过程。

code.vba
Sub Test2()
  Para2 "ID001"
  Para2 "ID001", , 90
End Sub

运行过程,在立即窗口中输出结果。

code.vba
ID001    80
ID001    90

可见,虽然调用Para2过程时没有给第3个参数赋值,但是因为给该参数定义了默认值,输出时仍然输出了它的默认值80。如果给该参数赋了新值,则新值覆盖默认值。

【Python】

定义函数时,对函数参数使用赋值语句可以指定该参数的默认值。下面定义para函数,该函数有两个参数,即id和score,指定score参数的默认值为80。该文件路径为Samples\ch10\Python\sam10-005.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-005.py
ID:  No001
Score:  80
ID:  No002
Score:  90

可见,没有传入score参数的值时,取了默认值80。

可变参数

所谓可变参数,指的是参数的个数是不确定的,可以是0个、1个直至任意个。

【Excel VBA】

Excel VBA中用一个数组指定可变参数,并且在参数前添加ParamArray关键字。包含可变参数的函数的定义如下所示:

code.vba
Function FunName(ParamArray paras() As Variant)
  执行语句…
End Function

注意,可变参数的数据类型必须是变体类型的。可变参数必须是参数列表中的最后一个参数,并且不能与可选参数一起使用。

下面定义函数求取一组数据的和,这组数的个数是不确定的。

code.vba
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函数累加求和。

code.vba
Sub Test()
  Debug.Print MySum(1, 2, 3)
  Debug.Print MySum(1, 2, 3, 4, 5, 6, 7, 8)
End Sub

运行过程,在立即窗口中输出不同个数参数的计算结果。

code.vba
6
36

【Python】

Python中包含可变参数的函数的定义如下所示:

code.python
def functionname([args,] *args_tuple ):
函数体
return [表达式]

其中,[args,]定义必选参数,*args_tuple定义可变参数。*args_tuple是作为1个元组传递进来的。

下面定义1个函数进行求和运算,该运算的第1个数据是确定的,后面的数据不确定,数据个数和数据大小都不确定。该文件路径为Samples\ch10\Python\sam10-006.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-006.py
70

参数为字典

【Excel VBA】

Excel VBA中,字典可以像其他数据类型的数据一样传递。下面的过程OutputData在立即窗口中输出指定字典的键值对数据。

code.vba
Private Sub OutputData(dicT As Dictionary)
  Dim strID
  For Each strID In dicT.Keys
    Debug.Print strID & vbTab & dicT(strID)
  Next
End Sub

编写过程创建字典并调用OutputData过程输出数据。

code.vba
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的数据。

code.vba
NO001   89
NO002   92
NO003   79

【Python】

Python中,如果函数的参数带两个星号,表示该参数为字典。传递字典参数的函数语法格式为:

code.python
def functionname([args,] **args_dict):
"函数_文档字符串"
函数体
return [表达式]

其中,[args,]定义必选参数,**args_dict定义字典参数。注意有2个星号。字典参数对应用赋值语句表示的2个实参,对应于字典的键和值。

下面定义1个函数,参数为字典,功能是输出字典数据。该文件路径为Samples\ch10\Python\sam10-007.py。

code.python
def paradict(**vdict):  #参数为字典
    print(vdict)
paradict(id="No001",score=80)  #调用函数,注意实参的输入方式

在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:

code.python
>>> = 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过程,设置两个参数按传值方式进行传递。

code.vba
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过程进行计算。

code.vba
Sub Test()
  Dim sngA As Single, sngB As Single
  sngA = 8
  sngB = 3
  Sum sngA, sngB
  Debug.Print sngA; sngB
End Sub

运行过程,在立即窗口中输出计算结果。

code.vba
8  3

可见,由于Sum过程设置参数按传值方式传递,在测试过程中调用Sum过程前后参数的值没有变化。

【Python】

在Python中,对于不可变类型,包括字符串、元组和数字,作为函数参数时是按传值方式传递的。此时传递的是对象的值,修改的是一个复制的对象,不影响对象本身。对于可变类型,包括列表和字典,作为函数参数时是按传址方式传递的。此时传递对象本身,修改它后在函数外部也会受影响。

下面举例进行说明。对于不可变类型,下面的函数传递1个字符串,查看调用该函数前后参数的值有没有变化。该文件路径为Samples\ch10\Python\sam10-008.py。

code.python
def TP(a):
    a= "python"  #修改参数的值为"python"
b= "hello"  #给变量b赋初值"hello"
TP(b)  #将变量b作为参数调用函数
print(b)  #输出变量b的值

在Python IDLE文件脚本窗口,在"Run"菜单中单击"Run Module"选项,则IDLE命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-008.py
hello

可见,调用函数前后变量的值不变,参数按照传值方式传递。

对于可变类型,下面的函数传递1个列表,在函数体中给列表添加1个列表元素。该文件路径为Samples\ch10\Python\sam10-009.py。

code.python
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命令行窗口显示下面的结果:

code.python
>>> = RESTART: …/Samples/ch10/Python\sam10-009.py
 [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, [6, 7, 8, 9]]

可见,调用函数前后列表发生了变化,参数按照传址方式传递。