Python小白到老司机,快跟我上车!基础篇(十三)

Posted coder-pig

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python小白到老司机,快跟我上车!基础篇(十三)相关的知识,希望对你有一定的参考价值。

函数(上)

1、函数定义

我们可以将一些实现特定功能重复使用到的「代码片段」抽取出来,封装成一个函数。比如求两个数和的函数:

def plus(a, b):
    """
    计算两个数的和
    :param a: 第一个参数
    :param b: 第二个参数
    :return: 两个参数的和
    """
    return a + b

if __name__ == '__main__':
    print("1 + 2 = %d" % plus(1, 2))

运行结果如下

1 + 2 = 3

从上面的求和函数可以初窥函数定义的一些端倪,接着具体说下规则:

  • 函数定义格式def 函数名(传入参数)
  • 使用 return 返回值,不写的话默认返回 None 值。
  • Python函数的 返回值可以有多个 ,本质上利用的元组
  • Python传递的 参数有多个 的话,可以用**逗号隔开**。
  • 一个建议:函数定义时,可在函数的第一行语句中选择性地使用文档字符串编写函数说明,除了方便阅读,使用**help(函数名)**也可以拿到这个函数的说明信息。

2、形参与实参

定义函数时,函数名后传入的参数叫「形参」,调用函数时,函数名后传入的参数叫「实参」。这里还涉及到一个「传值与传址」的问题,传值就是传入一个参数的值,而传址则是传入一个参数的内存地址。两者的区别是:如果函数是传值类型的,在函数里修改了参数的值,外部的变量(实参)是不会改变的,比如下面这样一段代码:

def f(a):
    a = 1

b = 0
f(b)
print(b)

运行结果如下

0

尽管我们修改了传入参数的值,但是实参却依旧是0,没有改变,这就是传参,如果传递的是内存地址,修改的了话实参也会受影响。但是Python和其他编程语言有点不同(比如C语言里可以用&参数名传地址)。

Python不允许开发者选择采用传值还是传址,而是采用「传对象引用」的方式,如果传入的参数是一个不可变对象数字,字符串和元组)的引用,就不能修改原始对象的值;如果传入的参数是一个可变对象列表,字典)的引用,就能直接修改原始对象的值。

比如下面这样一串代码:

def f(a):
    b[0] = 1

b = [0]
f(b)
print(b)

运行结果如下

[1]

3、关键字参数与默认参数

关键字参数」:当函数需要传入的参数有多个的时候,怕参数混淆传错,可以在传入的时候指定形参的参数名,比如:plus(a = 1, b = 2)

默认参数」:在 定义形参的时候赋予初始值,调用的时候就可以不带参数去调用函数,比如:
def plus(a=1, b = 2),调用的时候直接plus()或者只传入一个参数plus(3)都是可以的,还可以配合关键字参数指定传入的是哪个参数。另外,默认参数也称作「缺省参数」。

4、可变参数

有时传入函数中的 参数数目 可能是 不固定 的,比如,要你计算一堆数字的和,而具体有多少
个数字不知道,这个时候就可以使用可变参数了。只需要在函数定义时在参数前加上***** 星号,
就代表这个参数是可变参数(其实是只是把数据打包成了一个元组)。

另外,如果除了可变参数外还有其他的参数,那么写在可变参数后的参数要用关键字参数指定
否则会加入可变参数的范畴。还有一点要注意,如果传入的参数是列表或者元组,会被再次
打包成元组,如果想解包的话,需要在**实参前加***,代码示例如下:

def plus(*a):
    result = 0
    for b in a:
        print(b, end='\\t')

if __name__ == '__main__':
    a = [1, 2, 3, 4, 5]
    plus(a)
    print()
    plus(*a)

运行结果如下

[1, 2, 3, 4, 5] 
1   2   3   4   5 

另外,如果想把参数打包成字典的方式,可在函数形参前使用两个**标识

5、全局变量与局部变量

全局变量:定义在最外部,可在函数内部进行访问,但不能直接修改
局部变量:定义在**函数内部**,在函数外部无法访问的参数和变量。

局部变量无法在外部访问的原因

Python在运行函数时,会利用**栈(Stack)**来存储数据,执行完函数后,所有数据会被自动删除。

函数中无法修改全局变量的原因

试图在函数里修改全局变量的值时,Python会自动在函数内部新建一个名字一样的局部变量代替。如果硬是要修改,可以在函数内部使用**global关键字**修饰全局变量,但是不建议这样做,会使得程序维护成本的提高。

6、内部函数

所谓的内部函数其实就是「函数嵌套」,在一个函数中嵌套另一个函数,要注意:

内部函数的作用域,只在内部函数的「直接外部函数内」,外部是无法调用的没,外部调用内部函数会直接报:函数找不到的错误!



内部函数无法直接修改外部函数中的变量,否则会报**UnboundLocalError错误!如果想在内部函数中直接修改,可以把直接外部函数中的变量通过容器类型来存放**,或者使用Python提供的 nonlocal关键字 修饰。代码示例如下:

def fun_x():
    x = [10]
    y = 10
    def fun_y():
        x[0] += x[0]
        nonlocal y
        y *= y
        return x[0] * y
    return fun_y()

if __name__ == '__main__':
    print(fun_x())

运行结果如下

2000

7、闭包

在函数内嵌套了另一个函数,如果「内部函数引用了外部函数的变量」,则可能产生闭包。
Python中形成闭包的三个条件:

  • 函数嵌套
  • 内部函数引用外部变量
  • 外部函数返回内部函数

一个函数闭包的代码示例如下:

def outer(a):
    b = 1
    def inner():
        print(a + b)
    return inner

if __name__ == '__main__':
    test_1 = outer(2)
    test_1()

运行结果如下

2

在上面的代码中,直接把内部函数当做返回值返回了,b是一个局部变量,按理来说,生命周期在调用完outer()函数后就完结了。但是载上面的代码中,调用test_1时,b变量的值却正常输出了,函数闭包使得函数的「局部变量信息」得以保存。

Python中通过 __closure__属性 保存闭包中的局部变量,把上面test_1函数里的东东
打印出来,代码如下:

print(test_1.__closure__)
print(test_1.__closure__[0].cell_contents)
print(test_1.__closure__[1].cell_contents)

运行结果如下

(<cell at 0x000001D09ACF85E8: int object at 0x00000000667D6C30>, <cell at 0x000001D09ACF8648: int object at 0x00000000667D6C10>)
2
1

8、lambda表达式

在Python中可以使用**lambda关键字来创建匿名函数**,直接返回一个函数对象,而不用去纠结给函数起什么名字,省去了定义函数的步骤从而简化代码,一个对比大小简单的lambda表达式代码示例如下:

big = lambda x, y: x > y
print("第一个参数比第二个参数大:%s" % big(1, 2))

运行结果如下

第一个参数比第二个参数大:False

9、递归

所谓的递归就是「函数调用自身」,最简单的递归求和代码示例如下:

def sum(n):
    if n == 1:
        return 1
    else:
        return n + sum(n - 1)

print("1到100的求和结果是: %d" % sum(100))

运行结果如下

1到100的求和结果是: 5050

另外要注意两点

  • 递归要有结束条件,以避免递归的无休止调用!
  • 递归可以简化程序,但不一定能提高程序的执行效率!

以上是关于Python小白到老司机,快跟我上车!基础篇(十三)的主要内容,如果未能解决你的问题,请参考以下文章

Python小白到老司机,快跟我上车!基础篇

Python小白到老司机,快跟我上车!基础篇(十九)

Python小白到老司机,快跟我上车!基础篇(十九)

Python小白到老司机,快跟我上车!基础篇(二十)

Python小白到老司机,快跟我上车!基础篇(二十)

Python小白到老司机,快跟我上车!基础篇(二十)