函数和变量传递
函数
函数是一个可重复使用的代码块。函数能提高应用的模块性和代码的重复利用率。
Python提供了许多内建函数供用户直接调用,比如print()
。同时也可以自己创建函数,称之为用户自定义函数。
函数定义
使用def
关键字来定义函数。语法格式为:
def 函数名 (参数):
"""
说明文档
"""
执行语句
(return 返回值)
即使函数没有参数值,参数的括号也不能省略。
如:
def add(a, b):
"""
输入两个值,返回他们的和
"""
return a + b
上述代码段中定义了一个函数add()
,括号内包含两个参数a
和b
,函数的返回值为a+b
。
返回值return是指退出函数并返回结果,一个函数可以没有返回值,也可以有多个返回值;有多个返回值时,若只有一个变量来接受,则返回值为元组类型;或者必须有对应的变量数量来接收,否则会报错。
函数调用
- 一个简单的两个参数输入返回一个值的函数:
def add(a, b):
return a + b
print(add(1, 2))
# 输出:3
调用函数时,需严格按照函数的参数格式输入,包括参数的数量和顺序。
上述代码中,add()
函数指定了两个输入参数,用户在调用函数时也需要输入两个参数来获得函数的返回值。
- 多个参数输入多个参数返回的函数:
def my_print(a, b, c):
return a, b, c
a = my_print(1, 2, 3)
print(a)
# 输出:(1, 2, 3)
a, b = my_print(1, 2, 3)
print(a, b)
# 错误
a, b, c = my_print(1, 2, 3)
print(a, b, c)
# 输出:1 2 3
调用函数时请注意参数的对应关系。
参数传递
参数分类
函数的参数可以分为形式参数和实际参数。
- 形式参数(形参):在定义函数时,函数名后面括号中的参数就是形式参数。
- 实际参数(实参):在调用函数时,函数名后面括号中的参数称为实际参数。
def add(a, b):
return a + b
print(add(1, 2))
上述代码中,定义函数add(a,b)
时,其中的"a"和"b"就是形式参数;调用函数时add(1,2)
中的"1"和"2"就是实际参数。
形式参数
形式参数可以分为必选参数,默认参数,可变长位置参数,可变长关键字参数。
-
必选参数和默认参数
如上门所述,调用函数时,需严格按照函数的参数格式输入,指的就是必备参数。
而默认参数则是调用函数时可选择是否传入,若不传入则由默认值替代。
如:
def stu_info(name, age, sex="male"): # 若不传入sex,则默认值为"male" print(f"name is {name},age is {age},sex is {sex}") stu_info("user1", "20", "female") stu_info("user2", "20") # 输出: # name is user1,age is 20,sex is female # name is user2,age is 20,sex is male
-
可变长位置参数
可变长位置参数是指参数输入的数量不确定,使用星号
*
来定义可变长位置参数。加了星号的变量名会存放所有未命名的变量参数,打包成元组形式存储元素。
如:
def my_args(*nums): print(nums) my_args() my_args(1) my_args(1, 2) # 输出: # () # (1,) # (1, 2)
若要传入元组、列表或者集合中的元素(可以简单理解为解包),也要使用
*
来传入。传入的元组会被解包成单个元素再进行处理。如:
def myprint(a, b, *args): print(a, b, args) myprint(1, *(2, 3), 4, 5) # 输出:1 2 (3, 4, 5)
在上述代码中,元组(1, 2)被解包成两个元素;然后再进行位置参数的处理,即1,2对应形参a和b,3,4,5被打包成args元组输出。
-
可变长关键字参数
与可变长位置参数类似,使用两个星号
**
来定义可变长关键字参数。读入的参数会以字典形式来存储。def my_kwargs(**nums): print(nums) my_kwargs() my_kwargs(a=1) my_kwargs(b=2, c=3) # 输出 # {} # {'a': 1} # {'b': 2, 'c': 3}
同样的,如果要在参数内传入解包的字典,需要使用
**
。def myprint(x, y, **kwargs): print(x, y, kwargs) myprint(1, 2, **{'a': 1, 'b': 2}) # 输出:1 2 {'a': 1, 'b': 2}
实际参数
实际参数可以分为位置参数,关键字参数。
-
位置参数:按照函数的参数位置依次输入参数。
如:
def stu_info(name, age, sex):、 print(f"name is {name},age is {age},sex is {sex}") stu_info("user1", "20", "female")
-
关键字参数:使用关键字给定参数值;和位置参数一起使用时,放在位置参数后。
如:
def stu_info(name, age, sex): print(f"name is {name},age is {age},sex is {sex}") stu_info("user1", sex="male", age="18")
值传递和引用传递
根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:
- 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
- 引用(地址)传递:适用于实参类型为可变类型(列表,字典);
值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。
- 可更改对象与不可更改对象
可更改对象与不可更改对象的区别在于对象本身是否可变。字符串、元组、数值是不可更改对象,而列表、字典等则是可更改对象。
在更改不可更改对象的值时,实际上是生成了一个新的对象再指向原变量。
而更改可更改对象的值时,只是更改了对象内部的数据,对象本身没有发生变化。
如:
num = 10
print(f"第一个num的地址为{id(num)}")
num = 20
print(f"第二个num的地址为{id(num)}")
# 第一个num的地址为2610845516304
# 第二个num的地址为2610845516624
my_list = [1, 2, 3]
print(f"第一个list的地址为{id(my_list)}")
my_list[0] = 4
print(f"第二个list的地址为{id(my_list)}")
# 第一个list的地址为2023694724288
# 第二个list的地址为2023694724288
在python函数中,参数传递分为不可变类型传参和可变类型传参:
-
不可变类型:如果传入一个不可更改对象参数,传递的只是该参数的值,不会影响参数本身。
如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
-
可变类型:如果传入一个可更改对象参数,则传递的参数本身,修改函数的值会修改该变量的值。
如 fun(my_list),则是将 my_list真正地传过去,修改后 fun 外部的 my_list 也会受影响。
python 中一切都是对象,严格意义上来说不能说值传递还是引用传递,应该说传不可变对象和传可变对象。
变量作用域
LEGB
在python访问一个变量时,其查找顺序遵循LEGB机制:
- L:local,局部作用域,即函数中定义的变量;
- E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域;
- G:global,全局变量,即模块级别定义的变量;
- B:built-in,系统固定模块里面的变量,即python的内置作用域。
- 搜索变量的优先级顺序依次是:局部作用域>外层作用域>当前模块中的全局变量>python内置作用域
下面是一个简单的示例:
s = 'aaa'
def f1():
print(s)
def f2():
s = 'bbb'
f1()
f1()
# 输出:aaa
在函数 f1()
中,变量 s
是在全局作用域中定义的,它的值是 'aaa'
。
在函数 f2()
中,另一个名为 s
的变量被定义为 'bbb'
,但它只存在于 f2()
的本地作用域中。
当 f2()
调用 f1()
时,它会调用全局变量 s
,因此输出的值为 'aaa'
。
global关键字
如果想在函数中声明全局变量或者改变临时变量,可以使用global
关键字。
如:
def f1():
global s
s = "aaa"
def f2():
print(s)
f1()
f2()
# 输出:aaa
在上述代码中,函数f1()
定义了一个全局变量s = "aaa"
,首先调用函数f1()
来声明全局变量,再调用f2()1
时,变量s
被识别为全局变量并输出。
如果全局变量是不可变类型,想要在函数中对函数变量进行修改,则需要先在函数内声明其为global,再进行修改;如果是可变数据类型则可以直接修改。
s = "aaa"
def f1():
print(s)
def f2():
global s
s = "bbb"
def f3():
print(s)
f1()
f2()
f3()
# 输出:
# aaa
# bbb
特殊函数
匿名函数
匿名函数是指没有名字的函数。匿名函数使用关键字lambda
来定义,函数体只有一个表达式。
匿名函数可以作为参数传递给其他函数,可以使代码更简洁、更易于阅读。
语法表达式为lambda 参数:返回值
。
如:
func1 = lambda a, b: a + b
print(func1(1, 2))
# 输出:3
递归函数
如果一个函数在内部调用函数本身,这个函数就是递归函数。
下面是一个递归函数,用于计算输入值到1的和:
def calculate(n):
if n > 0:
return n + calculate(n - 1)
else:
return 0
计算输入值到1的和用数学表达式表达为n + (n-1) + (n-2) + ... + 1
。
当给定一个变量 n 时,函数会返回 n + n-1 ,并将 n-1 递归给函数本身进行下一次计算,以此循环,直至返回到 n-1=0 时结束递归。
使用递归函数需要考虑好每一层递归的执行代码,即问题的共同规矩;要考虑好函数的出口。
注意,函数的递归次数不要超过1000次,否则会造成堆栈溢出报错等问题。