本文主要介绍python函数的基础知识。
回答了,什么是函数,如何定义函数,函数的调用及函数的作用域等等问题。
本文是博客平时学习总结所得,可能有很多不对的地方,请不吝指教。
1.什么是函数?
有两个变量 x 和 y ,当 x 取其变化范围中的每一个特定值时,相应地有唯一的 y 与它对应,则称 y 是 x 的函数。记为 y = f ( x ),其中 x 为自变量, y 为因变量。
可知函数由三部分组成:
x:输入,一般函数可以有多个输入。
f:function,通过某种特定的方法将x转换为y,function就是这个方法。
y:输出,一般函数只有一个输出,计算机技术中的函数也可以没有输出。
python的函数:由若干个语句块(function)、函数名称、参数列表(输入)构成,完成一个功能(输出)。
2.函数的定义及调用
1.函数的定义
python使用def语句定义函数
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
函数名:一般命名要求。
参数列表:放一个标志符占位,叫做<font color=#DC143C >形参</font>。
函数体:代码块,决定函数的参数。
return:默认都会使用return语句,若无默认返回None。
2.函数的调用
通过前面定义的函数名称进行调用,就可以将函数运行起来,从而得到函数的返回值。
注意:
调用的时候需要在函数名称后加个小括号()括号内填入函数体中需要的参数,传入的参数叫做<font color=#DC143C >实参</font>。
函数传入的参数必须和函数体中需要的函数(实参)保持数量一致,除非原函数参数列表中定义的有默认参数。
#定义函数add()
def add(x,y):
sum = x+y
return sum
#调用函数add()
add(1,3)
4
3.函数的参数
python传入的参数分为两类,一种说根据位置传入的参数叫位置参数,另外一种可以根据形参定义的变量传入的参数叫关键字参数。
传入的时候,位置参数需放在关键字参数之前。
1.普通位置参数
把参数1给x,参数3给y。一一对应,这种就是位置参数。位置参数是按照顺序一一传入。
def add(x,y):
sum = x+y
return sum
add(1,3)
4
2.可变位置参数
在普通位置前面加个"*",可以一次接受多个参数。使用一个元组(tuple)收集多个实参。
def add(*nums):
sum = 0
print(type(nums))
for x in nums:
sum+=x
print(sum)
add(3,5,6)
<class 'tuple'>
14
说明:一般情况下,如果普通参数和可变位置参数一起定时候,需要把普通参数放在位置参数之前。
def add(x,*nums):
sum = 0
print(nums)
for x in nums:
sum+=x
print(sum)
add(3,5,6)
(5, 6)
11
3.关键字参数
把3传给y,1传给x。按照定义好的关键字传入参数,位置可以随意。
def add(x,y):
sum = x+y
return sum
add(y=3,x=1)
4
说明:当位置参数和关键字参数一起传入时候,需要把位置参数放在关键字参数前面。
def add(x,y):
sum = x+y
return sum
add(1,y=1)
2
4.可变关键字参数
在普通关键字参数前面加两个"**",可以一次接受多个关键字参数,收集到的实参名称和值组成一个字典(dict)。
def showconfig(**kwargs):
for k,v in kwargs.items():
print('{} = {}'.format(k, v))
showconfig(host='127.0.0.1',port='8080',username='mykernel',password='qwe123')
username = mykernel
password = qwe123
port = 8080
host = 127.0.0.1
说明:当可变参数和普通参数一起定义时,需要把可变参数放在普通参数之后。
def showconfig(x,y,*args,**kwargs):
print(x)
print(y)
print(args)
print(kwargs)
showconfig('127.0.0.1',8080,'mykernel',password='qwe123')
#此时使用关键字参数给x,y赋值就会报错。
127.0.0.1
8080
('mykernel',)
{'password': 'qwe123'}
5.默认参数
有些参数很少改变,所以可以在指定形参的时候传入一个默认值,当有新的实参去替换它的时候,新的参数生效。
默认参数必须放在普通参数之后
#传入默认值参数
def add(x=11,y=111):
sum = x+y
return sum
add() #未传入参数,默认参数生效
122
#有再次传入参数,替换默认值。
def add(x=11,y=111):
sum = x+y
return sum
add(657,y=123) #新传入的参数生效
780
定义一个函数login,参数名称为host、port、username和password。
def login(host='127.0.0.1',port='80',username='mykernel',password='123'):
print('{}:{}\nname:{}\npasswd:{}\n'.format(host,port,username,password))
login()
login('192.168.1.1',8080)
login('10.0.0.1',password='qwe123')
127.0.0.1:80
name:mykernel
passwd:123
192.168.1.1:8080
name:mykernel
passwd:123
10.0.0.1:80
name:mykernel
passwd:qwe123
6.keyword-only参数(python3后引入)
定义方法一:在可变位置参数后,出现普通参数。此时这个普通参数就被python视作为keyword-only参数,keyword-only参数在传入时必须使用关键字传参方法传入。
定义方法二:def fn(*, x,y),*,后跟普通参数,也被视为keyword-only参数,x,y均为keyword-only参数。
7.参数定义顺序
<div class="note danger no-icon"><p>参数列表的一般顺序是:普通参数,缺省参数,可变位置参数,keyword-only参数(可带缺省值),可变关键字参数。</p></div>
def fn1(x, y, z=3, *args, m=4, n, **kwargs):
print(x,y)
print(z)
print(args)
print(m,n)
print(kwargs)
print(end='\n')
#x,y是普通参数
#z,带默认值,传入时候省略,缺省参数
#*args,可变位置参数
#m=4,keyword-only 缺省参数
#n,keyword-only参数
#**kwargs,可变关键字参数
fn1(1,2,n=4)
fn1(1,2,4,43,123,k=123,m=11,n=13,j='hello')
1 2
3
()
4 4
{}
1 2
4
(43, 123)
11 13
{'j': 'hello', 'k': 123}
def fn2(x, y, z=3, *, m=4, n, **kwargs): #定义m,n为keyword-only参数。
print(x,y)
print(z)
print(m,n)
print(kwargs)
print(end='\n')
fn2(1,2,m=1,n=2)
1 2
3
1 2
{}
8.参数解构
def add(x,y):
print(x+y)
print()
add(*(4,6)) #参数解构
# add(*(1,2)) add(*[1,2]) add(*{1,3})
add(**{'x':1,'y':11}) #字典参数解构,x,y参数要和定义的对应起来。把x=1,y=11 传入形参,关键字传参。
d = {'a':1,'b':12}
add(*d.keys()) #取k 把取出来的k赋值给形参,位置传参。
add(*d.values()) #取values
10
12
ab
13
9.函数的返回值
函数返回值的特点:
- 一个函数只有一个返回值,返回值可以是函数多个结果的集合;
- python函数使用return语句返回函数的返回值;
- return语句不一定是函数的最后一条语句;
- 所有的函数都有返回值,如果不使用return函数,则默认使用return None返回;
- 如果有多条return函数,当其他任何一条执行了就直接跳出函数,其他部分不再执行;
- return None 可以简写为return。
说明:函数的返回值是函数执行完后(结束调用)给的一个值,函数的值,是运行这个函数时得到的值。
4.作用域
每个python的标识符都有自己的可见范围,这个可见范围就是标识符的作用域(变量的作用域)。
- 全局作用域,在整个python程序运行的环境中都可以见。
- 局部作用域,在函数、类等的内部可见,也仅仅其内部可以使用。
1.最小范围定义的变量生效
嵌套函数中,定义同一个变量,最内层的变量生效。但是只是影响当层,不影响其外层的值。
def outer1():
o = 65
def inner():
print("inner {}".format(o))
print(chr(o)) #内层inner函数可以使用上层outer1定义的o的变量。
print("outer {}".format(o))
inner()
outer1()
outer 65
inner 65
A
def outer2():
o = 65
def inner():
o = 97 #inner内定义的o的变量覆盖了上层函数中定义的o的值。
print("inner {}".format(o))
print(chr(o))
print("outer {}".format(o))
inner()
outer2()
outer 65
inner 97
a
2.试错
前因:三行代码报错
x = 5
def foo():
x += 1 #看似没问题的函数报错了。。
foo()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-12-4e89701596b1> in <module>()
3 x += 1
4
----> 5 foo()
<ipython-input-12-4e89701596b1> in foo()
1 x = 5
2 def foo():
----> 3 x += 1
4
5 foo()
UnboundLocalError: local variable 'x' referenced before assignment
a. 难道是外层函数,读取不到x的值?
x = 5
def foo():
print(x)
foo() #不会报错,说明foo()函数内可以读取到x的值
5
b. local variable 'x' referenced before assignment(局部变量“x”在赋值前被引用)
x = 5
def foo():
x = 11 #在内层函数再次定义x的值,覆盖外层的x,函数运行成功。
print(x)
x = x + 1
print(x)
print(x) #测试外部的x是否发生改变
foo()
5
11
12
总结:修改内层函数变量的时候,需要对变量重新赋值,不然会认为你修改的是外层变量的值(超出自己的权限了)。当然也可以使用新的变量来接收值(避免修改原变量)。
3.global全局变量(慎用)
解决2的问题也可以在函数内部定义一个global全局变量。
x =5
def foo():
global x
x += 1
print(x) #实现了改变x的值,次数x=6
def xoo():
print(x)
foo()
xoo() #此时xoo的变量值也改变了,很危险!!!正常情况下x = 5
print(x) #此时外部x的变量值也改变了,很危险!!!
6
6
6
#del x
def foo():
x = 5
global x
x += 1
print(x)
foo()
print(x) #外部没有定义x,使用global函数强行将x提升到全局,很危险!!!
6
6
<ipython-input-36-87883300c30d>:4: SyntaxWarning: name 'x' is assigned to before global declaration
global x
del x
4. 闭包
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。
def print_msg():
# print_msg 是外部函数
msg = "zen of python"
def printer():
# printer 是内层函数
print(msg)
return printer
another = print_msg() #赋值
another() #调用函数
#another()是什么?
#another = print_msg() ==> return printer ==> another = printer ==> another() = printer() ??
# printer打印出msg的值,所以another 也可以打印出msg的值。
#但是有什么不一样呢?
zen of python
这里的 another 就是一个闭包,闭包本质上是一个函数,它有两部分组成,printer 函数和变量 msg。闭包使得这些变量的值始终保存在内存中。msg就是自由变量。
自由变量会和这个print_msg函数一同存在,即使已经离开了创造它的环境也不例外。
##计数器
def generate_counter():
CNT = [0]
def add_one():
CNT[0] = CNT[0] + 1
return CNT[0]
return add_one
counter = generate_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
#多次初始化函数,但是CNT内的值没有被重置。 (具体原理还是没理解。。)
1
2
3
5. nonloacl关键字
nonlocal关键字,使变量标在上级的局部作用域中生效,但该作用域不能为全局作用域。所以一层的函数中不能使用该关键字。
def counter():
count = 0
def inc():
nonlocal count #把count 这个变量拿到我这个函数的作用域内,让我可以修改。。
count += 1
return count
return inc
foo = counter
foo()
foo()
<function __main__.counter.<locals>.inc()>
6. 默认值的作用域
当默认值是列表时,就会出现这种情况:
def foo(xyz=[]):
xyz.append(1)
print(xyz)
foo()
foo()
foo.__defaults__ #__defaults__是函数foo的一个属性
#当函数第一次运行后,xyz=[]已经变为xyz[1] ,次数__defaults__的值也为[1]。
#函数执行完成后运行着的foo函数已经调用完成,但是函数的属性依旧还在。
#当第二次调用此函数之前,foo的默认值已经变了。当执行完成后默认值再次发生变化。
[1]
[1, 1]
([1, 1],)
再次说明:
def foo(xyz=[],u='abc',z=123):
xyz.append(11)
return xyz
print(foo(),id(foo))
print(1,foo.__defaults__)
print(foo(),id(foo))
print(2,foo.__defaults__) #函数的id没有变化,说明函数在内存中的位置没有变化,那么函数的属性会一直伴随这foo函数。
#xyz=[] 引用的是一个地址,地址一直没有变化,变化的是索引对应的值。所以看起来的效果就是默认值发生变化了。
[11] 139696905538552
1 ([11], 'abc', 123)
[11, 11] 139696905538552
2 ([11, 11], 'abc', 123)
tips:函数属性kwdefaults 中保存的是keyword-only参数的默认值。
如何避免上述的特性呢?
- 传入默认值的副本
def foo(xyz = [],u='abc',z=123):
xyz = xyz[:] #浅copy
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
[1]
([], 'abc', 123)
[1]
([], 'abc', 123)
[10, 1]
([], 'abc', 123)
[10, 5, 1]
([], 'abc', 123)
- 使用不可变类型的默认值(推荐使用)
def foo(xyz=None, u='abc', z=123):
if xyz is None:
xyz = []
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
[1]
(None, 'abc', 123)
[1]
(None, 'abc', 123)
[10, 1]
(None, 'abc', 123)
[10, 5, 1]
(None, 'abc', 123)
5. 函数的销毁
全局函数销毁:
- 重新定义同名函数
- del 语句删除函数
- 程序结束时
局部函数销毁:
- 重新在上级作用域定义同名函数
- del 语句删除函数
- 上级作用域销毁时
6. 匿名函数
python借助Lambda表达式构建匿名函数。
匿名函数:没有名字,定义完后马上调用。
格式:
lambda 参数列表:表达式
(lambda x : x ** 2)(4)
16
- 匿名函数的参数列表不需要小括号;
- 冒号是用来分割参数列表和表达式;
- 不需要使用return,表达式的值就是匿名函数的返回值;
- lambda表达式只能在一行上执行,又被称为单行函数。
#示例
print(1,(lambda :0)())
print(2,(lambda x, y=3: x + y)(5))
print(3,(lambda x, y=3: x + y)(5, 6))
print(4,(lambda x, *, y=30: x + y)(5))
print(5,(lambda x, *, y=30: x + y)(5, y=10))
print(6,(lambda *args: (x for x in args))(*range(5)))
print(7,(lambda *args: [x+1 for x in args])(*range(5)))
print(8,(lambda *args: {x+2 for x in args})(*range(5)))
1 0
2 8
3 11
4 35
5 15
6 <generator object <lambda>.<locals>.<genexpr> at 0x7f0db86a2468>
7 [1, 2, 3, 4, 5]
8 {2, 3, 4, 5, 6}
[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))]
[1, 2, 3, 4, 5]
[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
[(1, (0, 1, 2, 3, 4)),
(2, (0, 1, 2, 3, 4)),
(3, (0, 1, 2, 3, 4)),
(4, (0, 1, 2, 3, 4)),
(5, (0, 1, 2, 3, 4))]
更多欢迎访问:http://www.mykernel.cn/