一、定义函数
- 函数代码块以
def
关键词开头,后接函数标识符名称和圆括号圆括号()
,内容以冒号:
起始 - 任何传入参数和自变量必须放在圆括号
()
中间,圆括号之间可以用于 定义参数 - 函数的第一行语句可以 选择性 地使用文档字符串—用于 存放函数说明
- 结束函数时,选择性 地返回一个值给调用方,不带表达式的相当于返回
None
# 一般形式
def function_name(parameter_list):
function_body
return [expression]
二、调用函数
- 函数的基本结构完成,可以通过另一个函数 调用执行 ,也可以直接从
Python 3.x
命令提示符执行
# 该实例演示计算面积函数
def area(width, height):
return width * height
def print_hello(name):
print("Hello", name)
print_hello("World")
w = 4
h = 5
print("width = %d, height = %d, area = %d" % (w, h, area(w, h)))
-> Hello World
-> width = 4, height = 5, area = 20
三、参数传递
- 在
Python 3.x
中一切都是对象,严格意义上 不能说 值传递还是引用传递,应该说 传不可变对象和传可变对象 - 无论传递的参数是 可变 还是 不可变 ,只要 针对参数 使用 赋值语句,会在 函数内部 修改 局部变量的引用,不会影响到 外部变量的引用
- 如果传递的参数是 可变类型,在函数内部,使用 方法 修改了数据的内容,同样会影响到外部的数据
- 可变数据(3 个):
List
(列表)、Dictionary
(字典)、Set
(集合) - 不可变数据(3 个):
Number
(数字)、Tuple
(元组)、String
(字符串)
# 该实例演示传不可变对象
def ChangeInt( a ):
a = 10
print(a)
b = 2
ChangeInt(b)
print(b)
-> 10
-> 2
# 该实例演示传可变对象
def changeme(mylist):
"""修改传入的列表"""
mylist.append([1, 2, 3, 4])
print("函数内取值: ", mylist)
return
mylist = [10, 20, 30]
changeme(mylist)
print("函数外取值: ", mylist)
-> 函数内取值: [10, 20, 30, [1, 2, 3, 4]]
-> 函数外取值: [10, 20, 30, [1, 2, 3, 4]]
- 列表变量执行
+=
时,本质上是在调用列表变量的list.extend()
方法,不会修改变量的引用
def demo(num, num_list):
print("函数内部代码")
# num = num + num
num += num
# num_list.extend(num_list) 由于是调用方法,所以不会修改变量的引用
# 函数执行结束后,外部数据同样会发生变化
num_list += num_list
print(num)
print(num_list)
print("函数代码完成")
gl_num = 9
gl_list = [1, 2, 3]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
-> 函数内部代码
-> 18
-> [1, 2, 3, 1, 2, 3]
-> 函数代码完成
-> 9
-> [1, 2, 3, 1, 2, 3]
四、参数类型
- 必需参数 ,需以正确的顺序传入函数,调用时的数量必须和声明时的一样
def printme(str):
"""打印任何传入的字符串"""
print(str)
return
printme("Hello")
-> Hello
- 关键字参数 ,允许函数调用时参数的顺序与声明时不一致,因为解释器能够用参数名匹配参数值
def printinfo(name, age):
"""打印任何传入的字符串"""
print("名字: ", name)
print("年龄: ", age)
return
printinfo(age=70, name="中国")
-> 名字: 中国
-> 年龄: 70
- 缺省参数 ,必须保证 带有默认值的缺省参数 在参数列表 末尾 ,否则会报错
- 在调用函数时,如果 没有 传递参数,则会使用 默认参数
- 在调用函数时,如果 有多个 缺省参数,则需要 指定参数名
- 将 常见的值 设置为参数的 缺省值,从而 简化函数的调用
- 参数的值 不确定,则不应该设置默认值,具体的数值在调用函数时,由外界传递
gl_num_list = [6, 3, 9]
# 默认就是升序排序,因为这种应用需求更多
gl_num_list.sort()
print(gl_num_list)
# 只有当需要降序排序时,才需要传递 `reverse` 参数
gl_num_list.sort(reverse=True)
print(gl_num_list)
def printinfo(name, age=18):
"""打印任何传入的字符串"""
print("名字: ", name)
print("年龄: ", age)
return
printinfo(age=50, name="中国")
print("-" * 24)
printinfo(name="中国")
-> 名字: 中国
-> 年龄: 50
-> ------------------------
-> 名字: 中国
-> 年龄: 18
- 不定长参数(多值参数),一个函数能处理比当初声明时 更多的参数
# args 是 arguments 的缩写,有变量的含义
# kwargs 可以记忆 键值对参数, kw 是 keyword 的缩写
def functionname(var, *args, **kwargs):
"""函数_文档字符串"""
function_suite
return [expression]
- 星号
*
的参数会以元组(tuple)
的形式导入,存放所有未命名的变量参数
def printinfo(arg1, *args):
"""打印任何传入的参数"""
print("输出: ")
print(arg1)
print(args)
printinfo(70, 60, 50)
-> 输出:
-> 70
-> (60, 50)
- 如果在函数调用时,没有指定参数,它就是 一个空元组 ,可以 不传递 未命名的变量
def printinfo(arg1, *args):
"""打印任何传入的参数"""
print("输出: ")
print(arg1)
for var in args:
print(var)
return
printinfo(70, 60, 50)
printinfo(70, (60, 50))
-> 输出:
-> 70
-> 60
-> 50
-> 输出:
-> 70
-> (60, 50)
- 星号
**
的参数会以字典(dictionary)
的形式导入,存放所有未命名的变量参数
def printinfo(arg1, **kwargs):
"""打印任何传入的参数"""
print("输出: ")
print(arg1)
print(kwargs)
printinfo(1, a=2, b=3)
-> 输出:
-> 1
-> {'a': 2, 'b': 3}
- 在调用带有 多值参数 的函数时,如果希望:
- 将一个 元组变量,直接传递给
args
- 将一个 字典变量,直接传递给
kwargs
- 将一个 元组变量,直接传递给
- 就可以使用 拆包,简化参数的传递,拆包 的方式是:
- 在 元组变量前,增加 一个
*
- 在 字典变量前,增加 两个
*
- 在 元组变量前,增加 一个
def demo(*args, **kwargs):
print(args)
print(kwargs)
# 需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}
# 会把 num_tuple 和 xiaoming 作为元组传递个 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)
- 如果单独出现星号
* / **
后的参数必须用 关键字传入
>>> def f(a, b, *, c):
... return a + b + c
...
>>> f(1, 2, 3) # 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1, 2, c=3) # 正常
6
五、匿名函数
lambda
只是一个表达式,函数体比def
简单很多lambda
的主体是 一个表达式 ,而不是一个代码块,仅仅能在lambda
表达式中封装有限的逻辑进去lambda
函数拥有自己的命名空间,且 不能访问 自己参数列表之外或全局命名空间里的参数lambda
函数看起来只能写一行,却不等同于C OR C++
的内联函数,后者的目的是调用小函数时 不占用 栈内存从而增加运行效率
# 一般形式
# 可以使用关键字参数
# 也可以设定默认参数
lambda [arg1 [,arg2,.....argn]]:expression
# 该实例演示两数相加
sum = lambda arg1, arg2: arg1 + arg2
print("相加后的值为 : ", sum(10, 20))
print("相加后的值为 : ", sum(20, 20))
-> 相加后的值为 : 30
-> 相加后的值为 : 40
六、返回函数
- 选择性地向调用方返回一个表达式,不带参数值返回
None
- 返回值 是函数执行完成后,最后 给调用者的 一个结果
- 调用函数,可以使用变量来 接收函数 的返回结果
- 变量的数量需要和元组中的元素数量 保持一致
# 一般形式
# 可以返回多个值,以元组的方式返回
return [expression]
# 该实例演示两数相加
def sum(arg1, arg2):
"""返回2个参数的和"""
total = arg1 + arg2
print("函数内:", total)
return total
total = sum(10, 20)
print("函数外:", total)
-> 函数内: 30
-> 函数外: 30
七、递归函数
- 函数内部可以 调用其他函数 ,当然在函数内部也可以 调用自己
- 函数内部的 代码 是相同的,只是针对 参数 不同,处理的结果不同
- 当 参数满足一个条件 时,函数 不再 执行,通常被称为 递归的出口,否则 会出现死循环
def sum_numbers(num):
print(num)
# 递归的出口很重要,否则会出现死循环
if num == 1:
return
sum_numbers(num - 1)
sum_numbers(3)
# 该实例演示计算数字累加
def sum_numbers(num):
if num == 1:
return 1
# 假设 sum_numbers 能够完成 num - 1 的累加
temp = sum_numbers(num - 1)
# 函数内部的核心算法就是 两个数字的相加
return num + temp
print(sum_numbers(2))
八、变量作用域
-
程序的变量 并不是 在哪个位置都可以访问的,访问权限 取决于 这个变量是在哪里赋值的
-
变量的作用域 决定 在哪一部分程序可以访问哪个特定的变量名称
-
变量引用:
- 变量 和 数据 是分开存储的
- 数据 保存在内存中的一个地址
- 变量 保存着数据在内存中的地址,记录数据的地址叫做 引用
- 函数的 实参 / 返回值 都是是靠 数据引用 来传递的
- 使用
id()
函数可以查看变量中保存数据所在的 内存地址 - 如果变量已经被定义,继续给该变量赋值的时候,本质上是 修改变量的数据引用
- 变量 不再 对之前的数据引用,变量 改为 对新赋值的数据引用
-
局部变量:
- 局部变量 是在 函数内部 定义的变量,只能在函数内部使用
- 不同的函数 可以定义 相同名称 的局部变量,彼此之间不会产生任何影响
- 作用:在函数内部使用,临时 保存 函数内部需要使用的数据
- 生命周期:变量 从执行函数 被创建 到 执行结束 被系统回收 的过程
-
全局变量:
- 全局变量 是在 函数外部定义 的变量,所有函数 内部 都可以使用这个变量
- 在其他的开发语言中,大多 不推荐使用全局变量,因为 可变范围太大,导致程序不好维护!
- 为了保证所有的函数都能够 正确使用 到全局变量,应该将全局变量 定义在其他函数的上方,因为程序是自顶向下进行
-
变量细节:
- 在函数内部,可以通过全局变量的引用获取对应的数据,但不允许使用赋值语句直接修改全局变量的引用
- 在函数内部,可以定义一个局部变量与全局变量的名称相同,根据就近原则,优先引用函数内部匹配的局部变量,再引用函数外部匹配的全局变量
- 变量作用域优先级:L (Local)局部作用域 –> E (Enclosing)闭包函数外的函数中 –> G (Global)全局作用域 –>B (Built-in)内置作用域
- 在函数内部,如果需要修改全局变量,可以使用
global
进行变量声明 - 为了避免局部变量和全局变量出现混淆,在定义全局变量时,某些公司会有些特殊的开发要求,例如:全局变量名前应该增加
g_
或者gl_
的前缀 - 对于全局变量的格式,各公司可能会有些差异,建议主动询问领导公司内部的代码格式有没有什么特殊要求?
代码结构示意图 |
---|
Shebang |
导入模块 |
全局变量 |
函数定义 |
执行代码 |
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域
# 该实例演示访问全局变量和局部变量
total = 0 # 这是一个全局变量
def sum(arg1, arg2):
"""返回2个参数的和"""
total = arg1 + arg2 # total在这里是局部变量.
print("函数内是局部变量:", total)
return total
sum(10, 20)
print("函数外是全局变量:", total)
-> 函数内是局部变量: 30
-> 函数外是全局变量: 0
# 当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字
# 该实例演示修改全局变量,需要 global 关键字
num = 1
def fun():
global num # 使用 global 关键字声明
print(num)
num = 123
print(num)
fun()
print(num)
-> 1
-> 100
-> 100
# 该实例演示修改嵌套作用域 (enclosing 作用域,外层非全局作用域),需要 nonlocal 关键字
def outer():
num = 10
def inner():
nonlocal num # 使用 nonlocal 关键字声明
num = 100
print(num)
inner()
print(num)
outer()
-> 100
-> 100
九、简单装饰器
- 在 不改变 当前函数的情况下, 给其 增加 新的功能,回调函数和返回函数的实例就是装饰器
def log(pr): # 将被装饰函数传入
def wrapper():
print("-" * 24)
return pr() # 执行被装饰的函数
return wrapper # 将装饰完之后的函数返回(返回的是函数名)
@log
def pr():
print("Hello World")
pr()
-> ------------------------
-> Hello World