什么是函数
所谓函数,就是把重复的代码单独的分离出来,放在⼀个公共的地⽅,以后可以⼀直的进⾏调⽤,这样就可以解决多次重复来编写,并且可以通过输入的参数值,返回需要的结果。
函数实际就是相当于一个具有某个/些功能的工具,先定义好,然后在需要的时候进行调用执行。
为什么要引入函数?
没有使用函数时的代码会有以下不足:
- 组织结构不清晰,可读性差;
- 代码冗余(同一个功能可能需要在多个地方使用);
- 可维护性及扩展性差(同一个功能在多个地方写了多次,需求变更时也需要更改多个地方的代码);
函数的定义及调用
函数的定义

- def开头,代表定义函数;
- def和函数名中间要敲一个空格;
- 函数名,这个名字是我们自己命名的,;
- 函数名后跟圆括号(),代表定义的是函数,里边可加参数/参数列表;
- 圆括号()后一定要加冒号: ;
- 代码块部分,是由语句组成,要有缩进;
- 有的函数有返回值,有的函数没有返回值;
定义函数时:
- 申请内存空间保存函数体代码;
- 将上述内存地址绑定函数名;
- 定义函数不会执行函数体代码,但会检测函数体语法;
调用函数时:
- 直接使用函数名调用,获得的是函数的内存地址;
- 通过函数名()带上括号调用,将执行函数体代码,返回执行结果;
所以,函数定义阶段只检测语法,不会执行代码内容,而调用函数时才会执行函数体的代码。
定义函数的两种形式
无参函数
无参函数即函数名右侧的括号内为空。(函数体代码的多少无关)
def func():
print("hello python")
有参函数
有参函数即函数名右侧的括号内有参数(变量)
x = 20
y = 200
result = x + y
print(result)
将上面的代码写成函数:
首先,写定义函数的关键字def跟上函数名,将上述代码缩进。如下:
def sum():
x = 20
y = 200
result = x + y
print(result)
然后,查看函数体代码中是否有变量被写死了,比如这里的x和y被写死为20和200,将x和y提取到参数中
def sum(x,y):
result = x + y
return result
最后通过sum(20,200)
就可以调用执行sum
函数了。
后续内容会详细介绍有参函数
函数调用的三种形式
语句形式
直接调用函数
def sum(x,y):
result = x + y
return result
sum(20,200)
表达式形式
函数执行结果赋值给某个变量,或参与与其他变量/数据的处理计算
def sum(x,y):
result = x + y
return result
result1 = sum(20,200)
result2 = sum(20,200) * π
print(result1,result2)
函数当做参数调用
执行上面的函数sum(20,200)
的返回结果是220
那么sum(220,100)
就可以改写成sum(sum(20,200),100)
,即sum(sum(x,y),z)
函数的返回值
return是函数结束的标志,即函数体代码一旦运行到return会立刻终止函数的运行,并且将return后面的值当做本次运行的结果返回;
返回None
- 函数体内没有return
- return
- return None
返回一个值
return result
返回多个值
用逗号分隔开多个值,会被return返回成元组
return result1,result2,result3
详解有参函数
形参和实参
形参:在定义函数阶段定义的参数,称之为形式参数,简称形参,相当于变量名;
def sum(x,y):
return x+y
实参:在调用函数阶段传入的值称之为实际参数,简称实参,相当于变量值;
sum(10,20)
在函数调用阶段,实参会绑定给形参,这种绑定关系会在函数调用时生效,在函数体代码中进行使用,调用结束后解除绑定关系。
位置参数
按照从左到右的顺序依次定义的参数称之为位置参数。
位置形参:按照从左到右的顺序直接定义形参名,必须被传值,个数一致,不能多也不能少;
def sum(x,y,z):
return x+y+z
位置实参:按照从左到右的顺序依次传入值,与形参的顺序一一对应;
传值顺序x,y,z分别对应1,2,3
def sum(x, y, z):
print(x)
print(y)
print(z)
return x + y + z
result = sum(1, 2, 3)
print(result)
1
2
3
6
传值顺序x,y,z分别对应3,2,1
result = sum(3, 2, 1)
print(result)
3
2
1
6
多传参数
result = sum(3, 2, 1, 5)
print(result)
Traceback (most recent call last):
File "/Users/laobai/workspaces/git_study_02/1.py", line 8, in <module>
result = sum(3, 2, 1, 5)
TypeError: sum() takes 3 positional arguments but 4 were given
少传参数
result = sum(1, 2)
print(result)
Traceback (most recent call last):
File "/Users/laobai/workspaces/git_study_02/1.py", line 8, in <module>
result = sum(1, 2)
TypeError: sum() missing 1 required positional argument: 'z'
关键字参数
关键字实参,在函数调用阶段,按照key=value的形式传入的值。指定某个形参进行传值。
def func(x,y):
print(x,y)
func(y=10,x=1)
1 10
位置参数和关键字参数可以混用,但位置参数要在关键字参数之前。
func(10,y=1)
默认参数
在定义函数阶段,就已经被赋值的形参,称之为默认参数。
因为在定义阶段已经赋值,那么在调用阶段可以根据实际的情况,可以不用为其赋值,当然也可以进行赋值。
def func(x, y=100):
print(x, y)
func(1)
func(1, 1)
1 100
1 1
位置形参和默认形参同时使用时,位置形参必须在默认形参的左边;
默认参数的值是在函数定义阶段被赋值的,赋予的是值的内存地址(有可能是变量如list等);
可变长度参数
可变长度指的是在调用函数时,传入的值的个数是不固定的。即实参个数不固定。
由于实参个数不固定,就需要有个形参来接收多出来的实参。
def sum(a,b):
sum = a+b;
return sum
sum(1,2)
sum(1,2,3)
sum(1,2,3,4)
sum(a=1,b=2)
sum(a=1,b=2,c=3)
sum(a=1,b=2,c=3,d=4)
可变长度的位置参数可以用*args
来接收并组成元组;可变长度的关键字参数可以使用**kwargs
来接收并组成字典。其中args和kwargs都是变量名,可以换成a,b,x,y等都可以,只是python的规范要求用args和kwargs两个固定的变量名称。理解了args和kwags只是可以被替换成别的变量名称的代号,那么能够实现接收可变长度参数的关键就是*
和**
这两个的作用。
即sum(a,b,*args)
和sum(a,b,*c)
是完全一样的;sum(a,b,**kwargs)
和sum(a,b,**c)
是完全一样的。
上面的例子可以改写成如下:
def sum1(*args):
sum=0
for i in args:
sum += i
print(sum)
sum1(1,2)
sum1(1,2,3)
sum1(1,2,3,4)
def sum2(**kwargs):
sum=0
for k,v in kwargs.items():
sum += v
print(sum)
sum2(a=1,b=2)
sum2(a=1,b=2,c=3)
sum2(a=1,b=2,c=3,d=4)
上面介绍的内容,*
用在形参中,下面介绍*
用在实参中的情况。
下面的这个例子,想把列表中的3个值传给形参x,y,z,但下面的写法并不可行
def sum(x, y, z):
return x + y + z
res = sum(*[1, 2, 3])
print(res)
Traceback (most recent call last):
File "/Users/laobai/workspaces/git_study_02/1.py", line 22, in <module>
res = sum([1, 2, 3])
TypeError: sum() missing 2 required positional arguments: 'y' and 'z'
会将列表的内容当做一个值传给x,而y和z没有接收到值。
在[1,2,3]
的前面加上*
,即*[1,2,3]
,就可以将列表中的每个元素都分解出来作为一个值进行传递
def sum(x, y, z):
print(x)
print(y)
print(z)
return x + y + z
res = sum(*[1, 2, 3])
print(res)
1
2
3
6
实参和形参中都带有*
的情况
def func(a, b, *args):
print(a, b, args)
func(1, 2, [3, 4, 5, 6])
func(1, 2, *[3, 4, 5, 6])
1 2 ([3, 4, 5, 6],)
1 2 (3, 4, 5, 6)
同样**
也可以存在在实参中,以及形参和实参都存在的情况
def func(a, b, c):
print(a, b, c)
func(**{'a': 1, 'b': 2, 'c': 3})
1 2 3
def func(a, b, **kwargs):
print(a, b, kwargs)
func(**{'d': 4, 'c': 3, 'b': 2, 'a': 1})
1 2 {'d': 4, 'c': 3}
注意:
*args
必须写在**kwargs
之前
def func(*args, **kwargs):
print(args)
print(kwargs)
func(1, 2, 3, 4, 5, 6, a=7, b=8, c=9)
(1, 2, 3, 4, 5, 6)
{'a': 7, 'b': 8, 'c': 9}
名称空间
名称空间(namespaces),存放名称的地方,是对栈区的划分。有了名称空间,就可以在栈区中存放相同的名字。名称空间分为三种:内置名称空间(built-in names)、全局名称空间(global names)和局部名称空间(local names)
内置名称空间
存放的名字:存放的python解释器内置的名字;
>>> print <built-in function print> >>> input <built-in function input> >>> open <built-in function open>
存活生命周期:Python解释器启动时创建,解释器关闭时销毁;
全局名称空间
存放的名字:不是函数内定义的,也不是内置的,剩下的都是全局名称;
import pytest a = 1 for i in ragne(5): print(i) if i = 2: b = 100 def func(): pass class Test: pass
存活生命周期:被Python解释器读入时创建(即模块被执行时),文件执行完成后被销毁
局部名称空间
- 存放的名字:在调用函数时,运行函数体代码过程中产生的函数内的名字;
def sum(x, y, z):
print(x)
print(y)
print(z)
return x + y + z
- 存活生命周期:调用函数时创建,函数调用完毕后销毁;
名称空间的加载顺序
内置名称空间 > 全局名称空间 > 局部名称空间
名称空间的查找顺序
当前所在的位置向上一层一层的查找:
局部名称空间 > 全局名称空间 > 内置名称空间
例子:
先从函数的局部名称空间查找input:
input = 123 def func(): input = 456 print(input) func() 456
先从函数的局部名称空间查找input,查找不到,在全局名称空间查找input:
input = 123 def func(): print(input) func() 123
先从函数的局部名称空间查找,再从全局名称空间查找,都查不到,再从内置名称空间查找input:
def func(): print(input) func() <built-in function input>
名称空间在函数代码定义时就已经确定了的,跟执行时的环境无关。
a = 1
def func1():
print(a)
def func2():
a = 100
func1()
func2()
1
作用域
- 全局范围作用域
- 全局作用域,即全局作用空间,包括内置名称空间和全局名称空间。
- 全局作用域,全局有效,被模块中的所有函数共享
- 局部范围作用域
- 局部作用域,即局部作用空间,只包含局部命名空间。
- 局部作用域,局部有效,只能在函数内使用。
# Built-in (内置)
# Global (全局)
def func1():
# Enclosing (函数嵌套)
def func2():
# Enclosing (函数嵌套)
def func3():
# Local (局部)
pass
global关键字:在局部想要修改全局的名字对应的值(不可变类型),就需要用global进行声明
a=11
def func():
global a
a=22
func()
print(a)
22
函数对象
函数对象,就是将函数当做变量去用。
a.将函数当做变量去用:
函数名=内存地址,单独使用函数的名称func实际上是在调用func的内存地址,而加上括号的func()调用的是函数func的执行结果。
def func():
return "hello python"
f1 = func()
f2 = func
print("f1:", f1)
print("f2:", f2)
f1: hello python
f2: <function func at 0x109928160>
也就是func
实际就是内存地址0x109928160
,f2=func
就是将0x109928160
赋值给了f2
,那么当f2
加上括号f2()
就相当于在执行func()
了。
b.将函数当做参数传给另外一个函数:
def func1():
print("hello python")
def func2(x):
print(x)
x()
func2(func1)
<function func1 at 0x1068a6160>
hello python
上面的例子就是将func1
这个函数名作为参数传递给了func2(x)
这个函数,并在func2(x)
函数内func1
加上了括号func1()
执行这个函数。
c.将函数当做另外一个函数的返回值:
def func1():
print("hello python")
def func2(x):
return x
res = func2(func1)
res()
hello python
d.将函数当做容器类型的一个元素:
def func():
print("hello python")
list1 = [func,111,'abc']
print(list1)
print(list1[0])
list1[0]()
[<function func at 0x10e086160>, 111, 'abc']
<function func at 0x10e086160>
hello python
上面的例子是将函数func
的名字当做一个元素存放在了列表中,可以通过下标取得函数名,也可以再加上括号list1[0]()
来执行函数。
同样,将函数名存入元组、字典等容器都是可以的。
函数嵌套
函数的嵌套调用,在调用一个函数的过程中又调用其他函数
def a():
pass
def b():
pass
def c():
pass
def func():
a()
b()
c()
在函数内定义新的函数
def func():
def a():
pass
def b():
pass
return a(),b()
闭包函数
闭包函数是基于名称空间与作用域、函数对象和函数嵌套的综合应用。
什么是闭包函数
“闭”指的是该函数定义在一个函数内的函数,即内嵌函数;
“包”指的是该函数包含对外层函数作用域名字的引用(不是全局作用域)
def func1():
x = 1
def func2(): # 这里函数func2()即为闭函数,func2()是func1()函数的内置函数
print(x) # 函数func2()的x引用的值是外层函数的值,即为包函数
return func2 # 将内置的闭包函数的函数名在外层函数进行return(是函数名,不是函数名加括号)
x=1111
f=func1()
print(f)
f()
<function func1.<locals>.func2 at 0x10c29ea60>
1
名字的查找关系是以函数定义阶段为准
闭包函数的应用场景
实现另外一种传递参数的方式
def func1():
a = 1
def func2():
print(a)
return func2
func3 =func1()
func3()
将上方代码中的变量a通过参数传递
def func1(a):
# a = 1
def func2():
print(a)
return func2
func3 =func1(1)
func3()