本文主要介绍python函数的基础知识。 回答了,什么是函数,如何定义函数,函数的调用及函数的作用域等等问题。 本文是博客平时学习总结所得,可能有很多不对的地方,请不吝指教。
有两个变量 x 和 y ,当 x 取其变化范围中的每一个特定值时,相应地有唯一的 y 与它对应,则称 y 是 x 的函数。记为 y = f ( x ),其中 x 为自变量, y 为因变量。
可知函数由三部分组成:
x:输入,一般函数可以有多个输入。 f:function,通过某种特定的方法将x转换为y,function就是这个方法。 y:输出,一般函数只有一个输出,计算机技术中的函数也可以没有输出。
python的函数:由若干个语句块(function)、函数名称、参数列表(输入)构成,完成一个功能(输出)。
python使用def语句定义函数
def 函数名(参数列表): 函数体(代码块) [return 返回值]
函数名:一般命名要求。 参数列表:放一个标志符占位,叫做形参。 函数体:代码块,决定函数的参数。 return:默认都会使用return语句,若无默认返回None。
通过前面定义的函数名称进行调用,就可以将函数运行起来,从而得到函数的返回值。 注意: 调用的时候需要在函数名称后加个小括号()括号内填入函数体中需要的参数,传入的参数叫做实参。 函数传入的参数必须和函数体中需要的函数(实参)保持数量一致,除非原函数参数列表中定义的有默认参数。
#定义函数add() def add(x,y): sum = x+y return sum #调用函数add() add(1,3)
4
python传入的参数分为两类,一种说根据位置传入的参数叫位置参数,另外一种可以根据形参定义的变量传入的参数叫关键字参数。 传入的时候,位置参数需放在关键字参数之前。
把参数1给x,参数3给y。一一对应,这种就是位置参数。位置参数是按照顺序一一传入。
def add(x,y): sum = x+y return sum add(1,3)
4
在普通位置前面加个"*",可以一次接受多个参数。使用一个元组(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传给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
在普通关键字参数前面加两个"**",可以一次接受多个关键字参数,收集到的实参名称和值组成一个字典(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'}
有些参数很少改变,所以可以在指定形参的时候传入一个默认值,当有新的实参去替换它的时候,新的参数生效。 默认参数必须放在普通参数之后
#传入默认值参数 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
**定义方法一:**在可变位置参数后,出现普通参数。此时这个普通参数就被python视作为keyword-only参数,keyword-only参数在传入时必须使用关键字传参方法传入。
**定义方法二:**def fn(*, x,y),*,后跟普通参数,也被视为keyword-only参数,x,y均为keyword-only参数。
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 {}
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
函数返回值的特点:
每个python的标识符都有自己的可见范围,这个可见范围就是标识符的作用域(变量的作用域)。
嵌套函数中,定义同一个变量,最内层的变量生效。但是只是影响当层,不影响其外层的值。
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
前因:三行代码报错
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
总结:修改内层函数变量的时候,需要对变量重新赋值,不然会认为你修改的是外层变量的值(超出自己的权限了)。当然也可以使用新的变量来接收值(避免修改原变量)。
解决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
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(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
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()>
当默认值是列表时,就会出现这种情况:
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)
2. 使用不可变类型的默认值(推荐使用)
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)
全局函数销毁:
局部函数销毁:
python借助Lambda表达式构建匿名函数。 匿名函数:没有名字,定义完后马上调用。 格式: lambda 参数列表:表达式
(lambda x : x ** 2)(4)
16
#示例 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))]
本文作者:mykernel
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!