函数主要有定义和调用两个阶段,在定义阶段,解释器只检查语法,不会执行代码,解释器默帮我们定义了一些内置函数,如常用的print()
,len()
等。
定义一个函数:
def print_s():
print('*'*15)
print('hello'.center(15,'*'))
print('*'*15)
print_s()
函数一般的定义方式:
def function_name(arg1,arg2,arg3):
# 注释
函数体
return 返回值
函数名一般是动词,定义的三种方式:
- 无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印。
- 有参:需要根据外部传进来的参数,才能执行相应的逻辑,比如统计长度,求最大最小值。
- 空函数:设计代码结构
return
return: 函数内部可以有多个return,但只能执行一次,函数就结束调用,并且会把return后的值作为函数执行的结果返回。
- return 的返回值没有类型限制
- 如果函数中没有return,则返回None,等同于return None. 如果只是执行一系列的操作,不需要结果则无需返回值。
- return 一个值: 返回该值
- return val1,val2,val3 :返回(val1,val2,val3)
函数调用的三种形式
1.语句形式,语句直接调用:
foo()
2.表达式形式,直接带入进行运算:
3*len('python')
3.函数中,使用另一个函数(返回值)作为参数:
max(max(1,2),3)
函数的参数
函数的参数分为形参和实参两种:
- 实参:在调用函数时,括号内的参数称为实参,实参就是变量值
- 形参: 在调用阶段变量值才会绑定形参(变量名),调用结束后,接触绑定。
参数的分类:
- 位置参数: 按照从左到右的顺序依次定义的参数
- 位置形参: 必须被传值,并且要不多不少
- 位置实参: 与形参按照位置一一对应
- 关键字实参: 指的是按照name=value的形式,给指定的参数赋值,并不一定需要按位置顺序赋值。
提示:
* 位置实参必须在关键字实参的前面
* 一定不要对同一个形参传多次值
默认参数
默认参数一般是形参,在定义阶段就已经为形参传值,在赋值的时候可以使用默认值,也可以单独赋值。
def newuser(name,age,sex='male'):
print(name,age,sex)
newuser('tt',18)
newuser('yy',22,'female')
提示:
* 默认参数必须放在位置参数之后
* 默认参数值在定义阶段赋值一次,而且仅一次。
* 默认参数的值应该定义成不可变类型。
可变长参数
可变长参数指的是多个实参的个数可以任意变化,实参分为位置实参和关键字实参两种:
- 按照位置定义的实参溢出的情况使用:
*
def foo(x,y,*args):
print(x)
print(y)
print(args)
foo(1,2,3,4,5)
输出的内容为变量值和元组。
- 按照关键字定义的实参溢出的情况:
**
def foo(x,y,**kwargs):
print(x)
print(y)
print(kwargs)
foo(x=1,y=2,a=3,b=4,c=5)
输出的内容为变量值和列表。
在有些场景下,为了后期代码的维护和扩展,会使用一个中间函数(wrapper())对函数参数进行传递,可以接受任意长度,任意形式的参数,而不改变原本的参数顺序和属性:
def foo(name,age,sex='male'):
print(name)
print(age)
print(sex)
def wrapper(*args,**kwargs):
print(args)
print(kwargs)
foo(*args,**kwargs)
提示:关键字实参一定要在位置实参的后面
命名关键字参数
在*
后面定义的 形参称为命名关键字参数
,必须是以关键字实参的形式传值。
def foo(name,age,*,sex): # * 后面的sex为命名关键字参数,对其传值需要指明关键字,否则会报错。
print(name)
print(age)
print(sex)
foo('andy',22,sex='male') # sex必须以关键字形式传值
参数的使用顺序
如果将多个参数放在一个函数中,那么他们必须遵循以下顺序规则:
- 位置形参
name
- 默认参数
age=22
- 可变长参数
*args
- 命名关键字参数(一般在
*
后)sex=male
- 多值关键字参数
**kwargs
函数对象
函数的定义类似与变量的定义,可以当作对象来赋值:
def foo():
print('from foo')
f=foo # 函数本身被当作对象赋值
f() # 执行 f()
可以当作函数的参数传入:
def foo():
print('from foo')
def wrapper(func):
print(func)
wrapper(foo)
可以当作函数的返回:
def foo():
pass
def wrapper(func):
return func
res=wrapper(foo)
print(res)
可以当作容器类型的元素:
def foo():
pass
cmd_dic={'func':foo}
print(cmd_dic)
cmd_dic['func']()
函数嵌套
在调用一个函数的过程中调用另一个函数:
def foo1():
print('from foo1')
def foo():
print('from foo')
foo1()
foo()
函数的嵌套可以使代码简洁,逻辑更加清晰。在函数内部定义的定义的变量和函数只能在函数的内部访问。
名称空间
名称空间是存放名字和变量值绑定关系的区域。
分为内置名称空间,全局名称空间,局部名称空间。内置名称空间:在python 解释器启动时产生,存放一些python内置的名字。
全局名称空间: 在执行文件时产生,存放文件级别定义的名字,非内置和非函数中定义的为全局名称。
局部名称空间:在执行文件的过程中,如果调用了函数,则会产生该函数局部名称空间,用来存放该函数内定义的名字,该名字在函数调用时生效,在函数调用结束后失效。
时那个名称空间的加载顺序:
内置名称空间 > 全局名称空间 > 局部名称空间
名称空间的查找顺序:
在局部定义: 局部 > 全局 > 内置
在全局定义: 全局 > 内置
在执行的当前位置作为起始,一层一层往上找。
作用域
分为全局作用域和局部作用域。
全局作用域:全局存活,全局有效
局部作用域:临时存活,只在局部有效。
全局的局部其实也是全局本身。
对全局的不可变类型如果在函数内部要进行修改,需要添加global。
对全局的可变类型进行修改在函数内部直接修改即可
x=1
def f():
global x # 全局的不可变类型需要添加global
x=2
l=[1,2,3]
def f():
l.append(4)
对于多层函数中定义的变量,如果要修改上层变量,可以使用nonlocal的关键字
def f1():
x=1
def f2():
x=2
def f3()
nonlocal x # 此时修改的是f2中的值
x=3
f3()
f2()
f1()
作用域关系,在函数定义时就已经固定,与调用位置无关。
x=1
def f1():
def f2():
print(x)
return f2
def foo(func):
x=2
func() # 此处调用 f1()会在f1()的作用域中查找x的值,不会在foo()作用域中查找,他们不属于同一个name space
foo(f1())
输出结果: 1
如果在调用函数之前已经对变量重新赋值,则修改生效:
x=1
def f1():
def f2():
print(x)
return f2
def f3(func):
x=2
func()
x=3
f3(f1()) # 在调用此函数时,同一name space中的x已经被重新定义
输出结果: 3
如果在调用之后,再对同一name space中的变量值进行修改,则函数调用的值为修改之前的值:
x=1
def f1():
def f2():
print(x)
return f2
def f3(func):
x=2
func()
f3(f1())
x=3
输出结果: 1
闭包函数
由上面作用域的规律,可以总结出在作用的范围,通过定义返回函数,可以固定参数的值,无论在程序的任何位置调用,都可以保证函数参数的值不变,这种功能的函数就叫做闭包函数,为方便外部的函数调用,使用return的方式返回内部的函数对象:
def deco():
x=123
def wrapper():
print(x)
return wrapper
func=deco() # func=wrapper
func() #函数无论在何时何处调用,x的值始终为函数定义的值。