使用函数与模块的目的都是为了提高程序的结构性与可复用性。函数已经很熟悉了,模块是比函数粒度更大的一个程序组织单元,一个模块包含若干个函数。

函数

函数结构

Python中函数的结构为:

1
2
3
def func(形参列表):
函数体
return 表达式
  1. 如果想要返回多个值,只需要 return 值1,值2,值2,…,最终以元组的形式返回:

    Untitled

    如果只写return,那么返回的结果就是空(None)。

  2. 如果函数体为空,就要使用pass来占位。适合于先组织程序结构,再编写代码。

函数传参

形参与实参

回顾一下形参和实参的区别:

  • 形参是函数定义是形参列表中的参数。
  • 实参是函数调用时,主调函数为被调函数提供的原始数据。

函数传参可以看成普通的变量赋值。用于Python采用的是基于值的内存管理方式,对每一个出现的值都会分配空间(包括相同的值),所以传参的方式分为三种:

  1. 对于基本类型变量的传参,形参和实参都指向同一地址。形参发生改变后会指向另一个地址,不会影响实参本来的值。相当于C++中的值传递。

  2. 对于可变类型的复杂类型(列表、字典、集合)的传参,形参和实参都指向同一地址。由于形参改变其中元素的时候地址不会改变,所以这变化会传递给实参。相当于C++中的引用传参:

    Untitled

    但是,如果重新赋值整个形参,这个修改是不会传递给实参的。

  3. 如果需要传序列但又不希望修改实参,则传入元组,或者传入列表的备份list.copy()。这种方式相当于C++中的常引用传参。

实际上,Python中一切皆对象,这里传参实际上传递的是对象。形参的修改能否影响到实参和这个对象的特性有关。

python函数是传参还是传引用?不,都不是!

参数的类型

  1. 位置参数

    实参的顺序和对应实参的顺序相同,且个数相等。

  2. 关键字参数

    通过 形参名=实参 的方式来指定某个形参的值,可以顺序不同,但个数必须相等。

  3. 默认值参数

    可以再函数头中给形参指定默认值,这样如果调用时没有给这个形参传递值,就使用默认值。需要注意默认值参数需要放在非默认值参数的后面

  4. 可变长参数

    列表与元组 这篇讲到序列可以拆分赋值。如果使用*可以实现可变长度的赋值。Python函数的可变长度传参正是使用此原理,不同的是可变长参数必须放在后面,而不能放在中间。

    • 元组可变长度参数*

      Untitled

    • 字典可变长度参数**

      实参使用 关键字=值 的方式传递给形参,形参前面加**表示以字典的形式接收

      Untitled

    元组可变长度传参可以看成位置参数的扩展,字典可变长度传参可以看成关键字参数的扩展。

程序的结构

Python虽然是一个脚本语言,但为了提高程序的结构性,还是需要使用函数来将程序的功能模块化。

常用的做法是定义多个功能函数和一个主函数,主函数实现各功能函数的调度,最后在程序最后面调用主函数即可。主函数就是整个程序的入口,类似于C语言中的main函数。

1
2
3
4
5
6
7
8
9
def func1():
pass
def func2():
pass
def main():
func1()
func2()
return
main()

匿名函数

匿名函数又称为lambda(λ)函数。它在同一行内定义函数,且不需要指定函数名,往往使用一个变量来接收这个匿名函数对象。由于没有函数名,所以lambda函数又被称为lambda表达式,它可以让程序更加精简。

它的结构为:

1
2
3
4
5
# 方式一:使用变量接收函数对象
fp=lambda x,y:x**y
print(fp(2,2))
# 方式二:由于没有变量指向函数对象,所以生命期就这一行
(lambda x,y:x**y)(2,2)

从匿名函数可以进一步理解Python中的一切皆对象。所以,我们也可以把匿名函数作为函数的返回值和列表、字典的元素。

此外,lambda函数还有一些高级的用法:

细说Python的lambda函数用法,建议收藏

装饰器

装饰器(decorator)可以理解为一个特殊的函数。要明白装饰器,首先要知道函数嵌套与函数闭包

  • 函数嵌套就是函数中再定义一个函数。外层函数可以调用同级的内层函数:

    1
    2
    3
    4
    5
    def wrapper():
    def add(a,b):
    return a+b
    print(add(1,4))
    wrapper()# 打印5
  • 函数闭包就是外层函数返回值为内层函数,这样就可以先执行外层函数,再执行内层函数了。这种方式内层函数能访问到外层函数的变量,但外层访问不到内层。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def wrapper(a,b):
    print('调用了外层函数')
    def add(a,b):
    print('调用了内层函数')
    return a+b
    return add(a,b)
    print(wrapper(1,4))
    # 输出:
    # 调用了外层函数
    # 调用了内层函数
    # 5

装饰器的定义与使用

装饰器就类似于函数闭包,不同的是它将函数对象作为参数,里面定义一个新函数作为增加的功能,这个新函数在返回的时候调用原来的函数。这样就可以在原来函数的基础上增加一些新的功能:

1
2
3
4
5
def deco(func):# 定义一个装饰器
def new_func(a,b):# 新函数
print('使用了装饰器')
return func(a,b)# 新函数将原函数作为返回值
return new_func# 装饰器返回新函数对象

在原函数前面使用 @装饰器名 的方式即可使用装饰器:

1
2
3
@deco
def add(a,b):
return a+b

下次调用原函数时,装饰器就会将原函数对象自动替换为新函数对象,首先功能的拓展:

1
2
3
4
print(add(1,4))
# 输出:
# 使用了装饰器
# 5

有参数装饰器

装饰器的参数是函数对象。如果拓展的功能涉及到其他参数的调用,那么需要使用有参数装饰器。

有参数装饰器在原来装饰器的外面再套一个装饰器用来接收参数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def DECO(argv):# 再套一个装饰器来接收参数
def deco(func):# 原来的装饰器
def new_func(a,b):
print('使用了装饰器')
print('{}x={},y={}'.format(argv,a,b))
return func(a,b)
return new_func
return deco# 新套的装饰器返回原来的装饰器对象
@DECO('add:')# 装饰器有参数
def add(a,b):
return a+b
@DECO('sub:')
def sub(a,b):
return a-b

print(add(1,4))
print(sub(1,4))
# 输出:
# 使用了装饰器
# add: x=1,y=4
# 5
# 使用了装饰器
# sub: x=1,y=4
# -3

一口气把Python装饰器描述清楚

变量的作用域

  1. 函数外的变量称为全局变量,再整个程序中都可以访问到
  2. 函数里面的变量如果不加声明都是局部变量,只能在函数内部或者函数的内层函数里面访问到
  3. 要让函数内的变量称为全局变量,就要使用 global 语句声明。
  4. 如果多个函数都能使用和修改同一全局变量,那么一个函数的执行就有可能影响另一个函数,程序的耦合性变高,不建议过多使用。

模块

模块其实就是一个.py文件,和普通的.py文件一样,它里面可以是函数、类,或者就是普通的脚本。模块分为标准库模块和用户自定义模块(包括第三方模块),它的名字就是Python文件的名字(去掉.py)。

模块的导入

模块的导入方式有三种:

  1. from 模块名 import *将整个模块全局导入,需要注意重名的情况。类似于C++中的 using namespace std;
  2. from 模块名 import 函数名1/类名1,函数名2/类名2,…按需全局导入需要的函数或类,同样注意重名的情况。类似于C++中 using std::cout;
  3. import 模块名 使用命名空间导入整个模块,不会重名,推荐使用这种方式。

模块的条件执行

因为模块其实也是一个.py文件,所以它里面可能也会有一些脚本。很多时候都希望直接运行这个模块的时候会执行这些脚本代码,而把它作为模块导入的时候不执行这些脚本。这时就要使用到py程序中的内置变量__name__

当直接执行一个模块时,__name__的值是'__main__',而把模块导入到另一个python程序中时,__name__的值是模块的名字。

Untitled

基于__name__内置变量可以实现模块的条件执行:判断__name__的值是否为"__main__",如果是,则执行里面的脚本。所以常常把程序的“主函数”放在if语句里面:

1
2
3
4
5
6
7
8
9
10
11
12
# 模块b
if __name__=="__main__":
print("执行模块b")
else: print("不执行模块b")
# 模块a
from b import *
if __name__=='__main__':
print("执行模块a")
else: print("不执行模块a")
# 运行a.py的结果:
# 不执行模块b
# 执行模块a