使用doctest单元测试方式培训讲解:Python函数基础
# coding = utf-8 ''' 函数声明: def name([arg,... arg = value,... *arg, **kwarg]): suite 1. 当编译器遇到 def,会生成创建函数对象指令。 也就是说 def 是执⾏行指令,⽽不仅仅是个语法关键字。 可以在任何地⽅方动态创建函数对象。 2. 可以使用默认参数、可变参数和关键字参数 arg = value 是默认参数 *args是可变参数,args接收的是一个tuple; **kwargs是关键字参数,kwargs接收的是一个dict。 lambda函数 不同于⽤用 def 定义复杂函数,lambda 只能是有返回值的简单的表达式。使⽤用赋值语句会引发语法 错误,可以考虑⽤用函数代替。 ''' ''' ***************************************************** 1. 函数创建 函数是第一类对象,可作为其他函数的实参或返回值。 函数总是有返回值。就算没有 return,默认也会返回 None。 ***************************************************** ''' def test1(name): ''' >>> test1('a').__name__ # 查看函数返回函数的名字 'a' >>> test1('b').__name__ # 查看函数返回函数的名字 'b' >>> test1('a')() # 调用函数返回的函数 call function a >>> print(test1('c')) # 调用函数,返回2个值:整型和字符串 (0, 'test') ''' if name == "a": def a(): print('call function a') return a elif name == "b": def b(): pass return b else: return 0, 'test' ''' ************************************************************* 2. 参数 2.1 函数的传参方式灵活多变,可按位置顺序传参,也可不关⼼顺序⽤命名实参。 ************************************************************* ''' def test21(a, b): ''' >>> test21(1,2) # 位置参数 1 2 >>> test21(b=3,a=4) # 命名参数 4 3 ''' print(a, b) ''' ************************************************************* 2.2 ⽀持参数默认值。不过要⼩⼼, 默认值对象在创建函数时生成,所有调用都使⽤同⼀对象。 如果该默认值是可变类型,那么就如同 C 静态局部变量。 ************************************************************* ''' def test22(x, lst=[]): ''' >>> test22(1) [1] >>> test22(2) # 保持了上次调⽤用状态。 [1, 2] >>> test22(3, []) # 显式提供实参,不使⽤用默认值。 [3] >>> test22(3) # 再次使⽤用默认值,会继续使用默认的列表对象 [1, 2, 3] ''' lst.append(x) return lst ''' ************************************************************* 2.3 默认参数后⾯不能有其他位置参数,除非是变参。 SyntaxError: def test23(a, b=1, c): pass 2.4 用 *args 收集 "多余" 的位置参数,**kwargs 收集 "额外" 的命名参数。 这两个名字只是惯例,可 ⾃自由命名。 *args是可变参数,args接收的是一个tuple; **kwargs是关键字参数,kwargs接收的是一个dict。 ************************************************************* ''' def test24(a, b=1, *args, **kwargs): ''' >>> test24(0) 0 1 () {} >>> test24(0,2,3,4) 0 2 (3, 4) {} >>> test24(0,2,3,4,x=5) 0 2 (3, 4) {'x': 5} # 可 "展开" 序列类型和字典,将全部元素当做多个实参使⽤用。如不展开的话,那仅是单个实参对象。 >>> test24(*range(5), **{'x': 10, 'y': 11}) 0 1 (2, 3, 4) {'x': 10, 'y': 11} >>> test24(range(5)) range(0, 5) 1 () {} # 单个 "*" 展开序列类型,或者仅是字典的主键列表。 # "**" 展开字典键值对。但如果没有变参收集, 展开后多余的参数将引发异常。 >>> p = dict(a=20,b=21) >>> test24(p) {'a': 20, 'b': 21} 1 () {} >>> test24(*p) # 仅展开 keys(),test("a"、"b") a b () {} >>> test24(**p) # 展开 items(),test(a = 1, b = 2)。 20 21 () {} ''' print(a) print(b) print(args) print(kwargs) ''' ************************************************************* 3. 作用域 3.1 函数形参和内部变量都存储在 locals 名字空间中。 ************************************************************* ''' def test31(a, b=1): ''' >>> test31(100) {'s': 'Hello Python', 'b': 1, 'a': 100} ''' s = 'Hello Python' print(locals()) ''' ************************************************************* 3.2 除⾮使用 global、nonlocal 特别声明, 否则,在函数内部使用赋值语句,总是在 locals 名字空间中 新建一个对象关联。 注意:"赋值" 是指名字指向新的对象,⽽⾮通过名字改变对象状态。 名字查找顺序: locals -> enclosing function -> globals -> __builtins__ • locals: 函数内部名字空间,包括局部变量和形参。 • enclosing function: 外部嵌套函数的名字空间。 • globals: 函数定义所在模块的名字空间。 • __builtins__: 内置模块的名字空间。 如果不修改全局变量也可以不使用global关键字。 Python3 提供了 nonlocal 关键字,用来修改外部 嵌套函数名字空间, python2.7 没有。 nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量。 ************************************************************* ''' x, y = 1, 2 # 在模块级别直接定义的全局变量 def test321(): global x # 引用全局变量 x = 11 y = 21 # 这里的 y 是局部变量 global z # 在函数内部定义一个全局变量,而没有在模块级别定义 z = 3 print(' in test321(): x=%d; y=%d; z=%d' % (x, y, z)) def test322(): print(' in test322(): x=%d; y=%d ; z=%d' % (x, y, z)) # 直接访问所有全局变量,包括变量z # 测试 print(' out 1: x=%d; y=%d; z=undefine' % (x, y)) test321() test322() print(' out 2: x=%d; y=%d ; z=%d' % (x, y, z)) ''' ************************************************************* 3.3 闭包 闭包(closure)是函数式编程的重要的语法结构。 闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。 ************************************************************* ''' def lineConfig(a,b): ''' 这个例子中,函数line与环境变量a,b构 成闭包。 在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值, 这样我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。 我们只需要变换参数a,b,就可以获得不同的直线表达函数。 由此,我们可以看到,闭包也具有提高代码可复用性的作用。 >>> y1 = lineConfig(1, 1) # 定义直线 y = x + 1 >>> p1 = y1(2) # 计算y坐标,获取x=2时y1的值 >>> print(p1) 3 >>> lstPoint = [y1(x) for x in range(3)] # 计算y坐标列表 >>> print(lstPoint) [1, 2, 3] >>> y2 = lineConfig(4, 5) # 定义直线 y = 4x + 5 >>> p2 = y2(x=2) # 计算y坐标,获取x=2时y2的值 >>> print(p2) 13 ''' def line(x): return a*x + b return line def newCounter(i=0): ''' 两个独立的计数器 >>> c1 = newCounter() >>> c2 = newCounter(2) >>> print(c1(),c1(),c1()) 1 2 3 >>> print(c2(),c2(),c2()) 3 4 5 ''' def counter(): nonlocal i i = i + 1 return i return counter if __name__ == '__main__': import doctest doctest.testmod(verbose=True)