Python实现Web UI自动化测试实战:Selenium 3/4+unittest/Pytest+GitLab+Jenkins
上QQ阅读APP看书,第一时间看更新

3.3 Python函数、模块

定义函数时,我们需要声明函数名和参数。定义函数的好处是,对函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无须了解。

3.3.1 创建函数

先来看一个函数,该函数的函数名为hello,有一个参数name,该函数能够实现输出“Hello+name”的效果。示例代码:test3_8.py。

def hello(name):
     """Print Hello + name"""
     print("Hello {}".format(name))

if __name__ == '__main__':
     hello('Storm')

执行结果如下。

Hello Storm

对代码的分析如下。

●关键字(在计算机语言中又称关键词)def用来定义一个函数,后面必须跟函数名称。

●函数名称后面是括号,括号中为参数列表(可以是0个参数或多个参数),括号后面需要加上英文冒号。

●函数体语句从下一行开始,并且必须缩进。

●函数体的第一行语句是该函数的文档信息(前后用3个英文双引号引起来),为可选项。

●后面是该函数的主体功能。

3.3.2 函数参数

Python的函数定义非常简单,灵活度非常大。除了正常定义的必选参数外,函数还可以使用默认参数、可变参数和关键字参数。如果能灵活运用,定义出来的函数不但能处理复杂的操作,还可以简化调用者的代码。

(1)位置参数

我们在Python命令行写一个计算x2的函数。

>>> def power(x):
...     return x*x

对于power函数,参数x就是一个位置参数。当我们调用power函数时,必须传入且仅能传入一个参数x

>>> power(3)
9
>>> power(6)
36

现在,如果我们要计算x3该怎么办?当然,你可以再定义一个power3函数,但是如果要计算x4x5,…怎么办?我们不可能定义无限多个函数。你也许想到了,可以把power(x)修改为power(x, n),用来计算xn。示例代码:test3_9.py。

def power(x, n):
     '''定义一个函数,计算x的n次方'''
     s = 1
     while n > 0:
          n = n - 1
          s = s * x
     return s

if __name__ == '__main__':
     print(power(3,2))
     print(power(2,3))

修改后的power函数可以计算某个数值的任意n次方。

修改后的power函数有两个参数:xn。这两个参数都是位置参数,调用函数时,传入的两个值需要按照顺序依次赋给参数xn

(2)默认参数

新的power函数定义没有问题,但是旧的调用代码失败了。原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用。

power(3)

执行结果如下。

Traceback (most recent call last):
  File "D:/Love/Chapter_3/test3_2.py", line 11, in <module>
     power(3)
TypeError: power() missing 1 required positional argument: 'n'

执行结果的错误信息很明确:调用函数power时缺少了一个位置参数n。假如我们希望参数未传递的时候,默认计算平方(在日常生活中计算平方的场景更多些),该如何做呢?这时候,默认参数就派上用场了。由于我们经常计算x2,因此我们可以把第二个参数n的默认值设定为2。示例代码:test3_10.py。

def power(x, n=2):
     '''定义一个函数,计算x的n次方,当n未传递时,n=2'''
 s = 1
     while n > 0:
          n = n - 1
          s = s * x
     return s

if __name__ == '__main__':
     # print(power(3,2))
     # print(power(2,3))
     print(power(3))
     print(power(3,2))

这样,当我们调用函数,只传递一个参数时,n就取默认值。例如,power(3)相当于power(3, 2),如图3-1所示。

图3-1 power(3)和power(3,2)

而对于n≠2的其他情况,就必须明确地传入n,如power(3, 3)。

从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有以下几点要注意。

●必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面)。

●当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面,变化小的参数就可以作为默认参数。

使用默认参数有什么好处?默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

(3)可变参数

在Python中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个或任意多个,还可以是0个。

我们以数学题为例子,给定一组数字abc,…,请计算a2+b2+c2+…。

要定义一个函数,必须确定输入的参数。但这里的参数的个数不确定,我们首先想到可以把abc,…作为一个列表(list)或元组(tuple)传进来,这样,函数可以定义如下。

"""这里是交互式命令行代码"""
>>> def calc(numbers):
...     sum = 0
...     for n in numbers:
...           sum = sum + n * n
...     return sum

调用的时候,需要先组装出一个列表或元组。

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

如果使用可变参数,调用函数的方式可以简化如下。

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

所以,我们把函数的参数改为可变参数。

"""这里是交互式命令行代码"""
>>> def calc(*numbers):
...     sum = 0
...     for n in numbers:
...           sum = sum + n * n
...     return sum

定义可变参数和定义一个列表或元组参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个元组,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。

>>> calc(1, 2)
5
>>> calc()
0

如果已经有一个列表或者元组,要调用一个可变参数怎么办?解决方法如下。

>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14

这种写法当然是可行的,不过太烦琐了。Python允许在列表或元组前面加一个*号,把列表或元组中的元素变成可变参数传进去。

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*nums表示把nums这个列表中的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

(4)关键字参数

可变参数允许传入0个或任意多个参数,这些可变参数在函数调用时会自动组装为一个元组。而关键字参数允许传入0个或任意多个含参数名的参数,这些关键字参数在函数内部会自动组装为一个字典(dictionary)。请看以下示例。

def person(name, age, **kw):
     print('name:', name, 'age:', age, 'other:', kw)

函数person除了接收必选参数name和age外,还接收关键字参数kw。在调用该函数时,可以只传入必选参数。

>>> person("Storm", 30)
name: Storm age: 30 other: {}

当然,我们也可以传入任意个关键字参数。

"""这里是交互式命令行代码"""
>>> person('SK', 35, city='Beijing')
name: SK age: 35 other: {'city': 'Beijing'}
>>> person('UG', 45, gender='M', job='Engineer')
name: UG age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数有什么用呢?它可以扩展函数的功能。例如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,程序也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

和可变参数类似,用户也可以先组装出一个字典,然后把该字典转换为关键字参数传进去。

"""这里是交互式命令行代码"""
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

当然,上面的复杂调用可以用简化的写法。

"""这里是交互式命令行代码"""
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个字典中的所有键-值对用关键字参数传入函数的**kw参数,kw将获得一个字典。注意,kw获得的字典是extra的一份复制结果,对kw的改动不会影响到函数外的extra。

(5)命名关键字参数

函数的调用者可以传入任意不受限制的关键字参数,至于到底传入了哪些,就需要在函数内部通过kw检查。

仍以person函数为例,我们希望检查是否有city和job参数。

def person(name, age, **kw):
     if 'city' in kw:
            # 有city参数
            pass
     if 'job' in kw:
            # 有job参数
            pass
     print('name:', name, 'age:', age, 'other:', kw)

但是调用者仍可以传入任意不受限制的关键字参数。

"""这里是交互式命令行代码"""
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)

如果要限制关键字参数的名字,就可以用命名关键字参数。例如,只接收city和job作为关键字参数,函数定义如下。

def person(name, age, *, city, job):
     print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数将被视为命名关键字参数。调用方式如下。

"""这里是交互式命令行代码"""
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。

def person(name, age, *args, city, job):
     print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。

"""这里是交互式命令行代码"""
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person函数仅接收两个位置参数,所以报错。

命名关键字参数可以有默认值,从而简化调用。

def person(name, age, *, city='Beijing', job):
     print(name, age, city, job)

由于命名关键字参数city具有默认值,因此在调用时可不传入city参数。

"""这里是交互式命令行代码"""
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法区分位置参数和命名关键字参数。

def person(name, age, city, job):
     '''缺少*,city和job被视为位置参数'''
     pass

(6)参数组合

在Python中定义函数时,可以使用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数可以组合使用。但是请注意,参数定义的顺序必须是必选参数、默认参数、可变参数、命名关键字参数、关键字参数。

例如定义一些函数,包含上述若干种参数,代码如下。

"""这里是交互式命令行代码"""
def f1(a, b, c=0, *args, **kw):
     print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
     print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

在函数调用的时候,Python解释器会自动按照参数位置和参数名把对应的参数传进去。

"""这里是交互式命令行代码"""
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

比较灵活的用法是,可以通过元组或字典来调用上述函数。

"""这里是交互式命令行代码"""
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

注意▶ 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。

本节小结如下。

●Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

●默认参数一定要用不可变对象,如果是可变对象,程序运行时会出现逻辑错误。

●要注意定义可变参数和关键字参数的语法:*args是可变参数,args接收的是一个元组;**kw是关键字参数,kw接收的是一个字典。

●注意调用函数时传入可变参数和关键字参数的语法:可变参数既可以直接传入[如func(1, 2, 3)],也可以先组装列表或元组,再通过*args传入[如func(*(1, 2, 3))];关键字参数既可以直接传入[如func(a=1, b=2)],也可以先组装字典,再通过**kw传入[如func(**{'a': 1, 'b': 2})]。

●*args和**kw是Python的惯用写法,当然也可以用其他参数名,但最好使用该惯用写法。

●命名关键字参数是为了限制调用者可以传入的参数名,同时还可以提供默认值。

●定义命名关键字参数时在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

3.3.3 Python模块

Python模块(module)是指以“.py”结尾的Python文件。模块用来定义函数、类和变量。把相关的代码放到一个模块中,能让代码变得更有逻辑性。文件test3_11.py包含以下内容,你可以将它看作一个模块。

def say_hello(name):
    print('Hello: {}'.format(name))

模块定义好后,我们可以使用import语句来导入。

●方法一:导入整个模块,导入格式为“模块名。方法名”。

import module1, module2,…

例如我们要导入math模块(test3_12.py)。

# 导入模块
import math

# 然后就可以调用math模块提供的sqrt方法
print(math.sqrt(9))

●方法二:导入模块的某个方法,调用的时候直接使用方法名(test3_13.py)。

# 从math模块导入sqrt方法
from math import sqrt

# 直接使用sqrt方法
print(sqrt(9))

●方法三:使用通配符*一次导入模块的所有方法(test3_14.py)。

# 从math模块导入所有方法
from math import *

# 直接使用sqrt、sin方法
print(sqrt(9))
print(sin(180))