使用doctest单元测试方式培训讲解:Python函数基础

时间:2022-09-29 22:44:03
# 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)