使用函数的意义
首先必须要认识到的是函数在python使用过程中是十分重要的,函数就是将一些语句集合在一起的一个部件,它解决了大部分的语句重用问题,并且每个函数都有返回值,传入的参数不同代码运行可能都不一样。有了函数以后程序的修改也简化了很多。简而言之,函数就是代码的最大程度重用和最小化代码冗余的最基本程序结构。
当然函数式编程的思维还告诉我们独立完成较小的任务比一次完成整个程序要容易得多,所以将一个大的模块分解为多个函数来完成,整体结构也是非常易懂并且方便操作的。
函数在面临修改代码时的操作也减轻了很大一部分的工作量,并且程序可读性也很好,所以函数有这样多的好处,我们来使用函数吧。
函数的定义与调用
def语句用来创建一个对象,并且将其赋给一个变量名,即def func(),就是创建一个函数对象并且将对象赋值给func这个变量名。def 是一个可执行语句,函数本身并不存在,直到python运行了def后才存在,新的函数对象将值赋给函数名。除此之外还可以通过lambda创建匿名函数,这种函数不绑定变量名,而是将其对象直接作为结果返回。
def语句的基本格式:
def <name>(arg1,arg2...):
<statements>
return <value>
函数通过return将一个结果对象返回,当然这个结果对象可以是一个包含了多个值额元组。return可以出现在函数主体的任意位置,他表示函数调用结束,然后把函数结果返回到函数调用处。对于没有返回值的函数将自动返回None对象。
值得注意的是函数定义是引用的变量名,对于变量name,name=key,则调用时key与name都指向的是同一个函数的内存地址,他们具有同样的运行职能。
在函数使用过程一定要先定义在调用。
def func(a): print(a) name = func func(1) name(1) 运行结果: 1
还有一点是除了调用以外,函数允许任意属性附加到记录信息以供随后使用。
def func(): pass func.attr=1 func.attr+=1 print(func.attr) 运行结果: 2
在程序中,函数名增加括号就可以调用函数。参数的调用是通过实参赋值给形参所传递的,并且在调用函数之前必须要先定义函数,即使函数的主体为pass,或者在别的模块中并且导入。
yield也会向调用者发一个结果,这在生成器中运用广泛,yield每次返回值时都会挂起当前状态,以便下一次能够从当前状态继续运行。
函数中赋值的对象都是这个函数的本地变量,但是global在函数中声明则可以使用在整个模块的任意位置。
nolocal则使用在嵌套函数中可改变嵌套函数中本地变量的值。
函数中依然没有类型约束,即参数,返回值,变量都是不需要声明类型的。这就是python的多态行为,只要对象支持预期的接口,name函数就可以处理,当然如果传递的对象不支持预期的接口那么运行时会自动抛出一个异常,所以检查代码错误是没有意义的。实际上在python中都是为对象写接口,并没有为数据类型写接口。
max(iter,key=lambda) min(iter,key=lambda) sorted(iter,key=lambda) map(lambda,iter) reduce(lambda,iter) filter(lambda,iter)
函数返回值
函数通过return关键字返回值。
1、不写return的情况下,会默认返回一个None。
2、只写return,后面不写其他内容,也会返回None,既然没有要返回的值,完全可以不写return,为什么还要写个return呢?因为return有一个重要用法,就是一旦遇到return,结束整个函数
3、 return None与return一致。
def max_l(a,b) : ''' 用来比较两个数值的大小 :param a: 传进来的第一个数 :param b: 传进来的第二个数 :return: 无返回值 ''' if a > b: return a else: return b
return和返回值之间要有空格,可以返回任意数据类型的值
当然函数可以返回多个值。
def func(): '''返回多个任意类型的值''' return 1,['a','b'],3,4
返回的多个值会被组织成元组被返回,也可以用多个值来接收。
def func(): return 1,['a','b'],3,4 a,b,c,d = func() print(a,b,c,d)
定义函数
(1)在Python中采用def关键字进行函数的定义,不用指定返回值的类型。
(2)函数参数args可以是零个、一个或者多个,函数参数也不用指定参数类型,因为在Python中变量都是弱类型的,Python会自动根据值来维护其类型。
根据args的个数可以将函数分为无参函数,有参函数和空函数。
(3)return语句是可选的,它可以在函数体内任何位置出现;如果没有return语句,会自动返回NONE,如果有return语句,但是return后面没有接表达式或者值的话也是返回NONE,return一个值,则返回一个值,如果return多个值则返回一个元组。注:与yield不同的是,return执行到就结束不会返回后面的值了。
在函数的定义阶段涉及到全局变量与局部变量。
x=10000#定义全局变量 def foo(): x=1#局部变量 print(x) print(x) foo()
局部变量在调用函数时建立关系,结束时解除关系。
形参与实参
形参是在定义函数的时候,括号里面的参数,而实参则是在调用函数的时候,括号里面的参数。可以理解为将实参的变量值赋给形参,与变量的赋值关系类似。当然只有有参函数才有形参与实参啦。
def foo(x): x.append(4) print(x) x = [1, 2, 3] foo(x)
这里实参采用的是一个列表,并且在函数中改变了原来的列表l,这里的实验环境比较简单,如果在实际生产中也使用这样可变的实参,是不支持的,这往往导致一些难以排查出的故障。所以实参最好是不可变数据类型。
位置参数与默认参数
参数传递的关键点:
1.参数传递是通过自动将对象赋值给本地变量名的过程;
2.函数内的参数变量名的赋值不会影响调用者作用域变量名的变化;
3.上面一条只针对不可变数据类型,可变数据类型作为实参传递时,如果发生改变,很有可能影响到调用者。只要想到传参和变量赋值引用是一样的过程就很好理解了。
传参方式:
1.位置传参,从左往右的顺序进行匹配;对于非关键字参数来说。
2.关键字参数进行匹配,使用name=value来传参;
3.采用默认参数;默认可以跳过所以默认值参数。
4.采用可变参数来收集任意多的参数;**kwargs总是放在最后面,只对关键字参数有效,一个形参不可以只有**kwargs这个形参。
5.传递任意多的参数,可变函数可以解包参数;
6.Keyword-only参数,参数必须按照名称进行传递。这种传参只能使用关键字传参,它的位置比较固定一般出现在*args后面(只是定义时,调用可出现在*前面,还有可能在**的调用中),并且*后面所有传参都使用关键字传递。kwonly参数当然也可以有默认值啦,那些没有默认值的就必须使用键来传参了。
根据位置参数传值:
def foo(x,y): res=x+y return res print(foo(5,10))
还可以通过给定默认参数进行赋值:
def foo(x,y): res=x+y return res print(foo(x=5,y=10))
或者两者混合使用:
def foo(x,y): res=x+y return res print(foo(5,y=10))
默认参数就是定义参数时,形参不传值默认使用的值。
def foo(x,y=10): res=x+y return res print(foo(5))
动态参数
参数前一个“*”:在函数中会把传的多出的参数转成一个元组。
def func (*args): print(args) func(123,1,2,'a')
这里args是一个元组的形式包含了所有的元素。
**kwargs则是以关键字形式,多余的关键字传给kwargs,结果为字典形式。
def func(x,**kwargs): print(x) print(kwargs) func(1,y=2,a=3,b=4) 输出结果: {'a': 3, 'b': 4, 'y': 2}
名称空间与作用域
python创建、改变或查找变量都是在所谓的名称空间中完成,作用域与其关系极其密切。所有变量名包括作用域的定义在内都是在python赋值的时候生成的。在代码中给一个变量赋值的地方决定了变量的作用范围以及变量存在哪个命名空间。
这在函数中,所有的变量名都与函数的名称空间相关联。也就是说函数内部定义的变量名只有内部代码可以使用,不能在函数外部使用,并且内部定义与外部定义并不冲突,相同的变量名在不同名称空间指定的值也不同。
a=10 def func(): a=1 print(a) func() print(a) 运行结果: 1 10
作用域总是由代码中被赋值的地方所决定,并且与调用完全无关。根据变量所在的三个不同地方分配,分别对应三种不同作用域:
1.嵌套内函数,作用域是非本地作用域;
2.函数内部,本地作用域;
3.函数外的全局作用域。
如上述代码,相同变量名可以被作用域隔开,代表不同的值。这有助于防止程序之中变量名的冲突,并且有助于函数成为更加独立的程序单元。
作用域法则
1.导入的内嵌模块是全局作用域。并且每一个模块都是一个全局作用域;
2.全局作用域范围只适用于单个文件内;
3.每次调用函数都创建一个新的本地作用域;
4.函数内部赋值的变量除非是声明为全局变量或者nolocal变量否则均为本地变量;包括本地=,import模块名,def 函数名,函数参数名都是本地的。
注:一点除外=右边为可变数据类型,原地改变自然不是本地的,虽然也不支持使用可变数据类型。
5.所有的变量名称可以归纳为本地名称空间、全局名称空间以及内置名称空间。
LEGB原则
1.查找顺序为:本地(L),函数内部(E),全局(G),内置(B);
2.默认情况下,变量名赋值会创建或者改变本地变量,除非他已经在函数中声明为全局变量;
3.global和nolocal会将赋值的变量名映射到模块文件内部的作用域。
这种查询变量的方式只对简单变量名有效,类的查找与其并不一致。
注:参数传递时可以近似看做是内部进行了赋值操作。
内置作用域
简单来说就是一个__builtin__的内置模块,dir(builtins)可以看出内置作用域是内置异常和内置函数组成的,这些变量名也是可以在程序中使用的(import builtins)。由于LEGB流程,本地赋值变量可能覆盖全局变量,而全局的变量名可能覆盖内置变量名。
global
这是python为数不多的声明语句了,它的作用是允许修改模块内除了def以外的名称,他应用于def本地作用域内,将本地变量名映射到整个模块的作用域内。在函数运行前global参数可能并不存在,所以函数内的赋值语句将自动在模块中创建这个变量。
初学python还是不建议使用global的,虽然他是最直接的保持状态信息的方法,但是过度调用是的变量名乱成一气。
访问全局变量的方法:global;import 模块,模块下属性;import sys sys.moudules[‘模块名’]。
文件间最好的通信办法就是通过条用函数传递参数,然后得到返回值。直接.x=的方式总是使人眼花缭乱。
x=0 def setx(newx): global x x=newx import prac3 prac3.setx(5) print(x) 运行结果: 5
工厂函数
主要还是使用在装饰器中,或者lambda函数创建表达式中。
def a(x): def b(y): print(x*y) return b f=a(3) f(3) f(4) g=a(5) g(5) 运行结果: 9 12 25
def a(): y=5 b=(lambda x: x*y ) return b f=a() print(f(3)) 运行结果: 15
当函数中使用了循环语句,那么问题就不同了:
def a(): y=[] for i in range(5): y.append(lambda x: x*i) return y y=a() print(y[0](2)) print(y[2](2)) print(y[4](2)) 运行结果: 8 8 8
嵌套作用域中的变量在嵌套函数运行的时候才查找,所以调用时所找到的值其实都是循环迭代最后一个变量的值。为了让他可以正常迭代传值,需要在匿名函数循环过程中指定需要传入的i的值。
def a(): y=[] for i in range(5): y.append(lambda x, i = i: x*i) return y y=a() print(y[0](2)) print(y[2](2)) print(y[4](2)) 运行结果: 0 4 8
nonlocal
nonlocal允许对嵌套函数作用域中名称赋值,但是声明nonlocal的名称首先必须存在该嵌套函数作用域中,即不能由一个嵌套def第一次赋值,毫无意义。
def a(): # x=3 def b(): nonlocal x x=5 print(x) return b a()() 运行结果: nonlocal x SyntaxError: no binding for nonlocal 'x' found
nonlocal使得对其声明的变量名查找时从外围的def作用域开始,而不是声明变量的本地,也就是意味着完全略过本地作用域。他的存在主要是允许嵌套作用域中的名称被修改。
def a(n): x=n def b(m): nonlocal x print(m,x) x+=1 return b f=a(0) f('tom') f('jerry') 运行结果: tom 0 jerry 1
python在创建函数时就要解析nonlocal而不是等到调用时才解析。
同样我们也可以使用以下方式完成状态保持。
def a(start): def b(lable): print(lable,b.state) b.state+=1 b.state=start return b f=a(0) f('tom') f('jerry') 运行结果: tom 0 jerry 1
这里给对象赋值了,这点才是值得注意的地方,state也不是什么了不起的属性,使用b.s上述依旧是正确的,b.s对象也只是接收值而已。
最后,使用官方文档的例子解释一下global与nonlocal的作用:
def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam) 运行结果: After local assignment: test spam After nonlocal assignment: nonlocal spam After global assignment: nonlocal spam In global scope: global spam
函数的嵌套与闭包函数
函数嵌套就是指函数内部调用其他的函数。
例如一个比多个值大小的函数:
def my_max(x,y): res=x if x>y else y return res def my_max4(a,b,c,d): res1=my_max(a,b) res2=my_max(c,d) res3=my_max(res1,res2) return res3 print(my_max4(3,8,12,6))
这里把函数当做参数传递,并且在后一个函数调用了前一个函数完成需求,这就是嵌套函数的基本应用。
这里要提一下,在Python中函数是可以作为对象操作的,比如被赋值,被当做参数传递等,和平时操作变量名几乎一致。
def f1(): print("in f1") def f2(): print("in f2") f2() f1() def f1(): def f2(): def f3(): print("in f3") print("in f2") f3() print("in f1") f2() f1()
函数名指向函数的内存地址,函数本身就是一个对象,可以通过调用函数名任意调用。
def add(): print('=============>function add') def search(): print('=============>function search') def delete(): print('=============>function delete') def change(): print('=============>function change') def tell_msg(): msg=''' search:查询 add:添加 delete:删除 change:修改 create:新建 ''' print(msg) def create(): print('function ---->create') cmd_dic={ 'search':search, 'add':add, 'delete':delete, 'change':change, 'create':create } while True: tell_msg() choice=input('please input your choice: ').strip() # print(cmd_dic[choice]) cmd_dic[choice]()
def func(): print('in func') f = func print(f)
def func(): print('func') def func2(f): f() func2(func)
def func(): def func2(): print('hello') return func2 f2 = func() f2() f = func
闭包函数也是函数嵌套中的一种,他首先必须是内部定义的函数,对外部作用域而不是全局作用域名字的引用。
闭包函数的基本形式:
x=1 def f1(): def f2(): print(x) return f2 f2=f1() f2()
f1加括号执行,然后返回一个f2的地址值,f2就是闭包函数,运行f2打印其中的值。
#输出的__closure__有cell元素 :是闭包函数 def func(): name = '111' def inner(): print(name) print(inner.__closure__) return inner f = func() f() #输出的__closure__为None :不是闭包函数 name = '222' def func2(): def inner(): print(name) print(inner.__closure__) return inner f2 = func2() f2()
爬取python官网的代码:
from urllib.request import urlopen def f1(url): def f2(): print(urlopen(url).read()) return f2 python=f1('http://www.python.org') python()
闭包函数在python中的应用场景十分多,包括后面要提的装饰器,以及简单的爬去网页,都会用到闭包函数。
__closure__,返回f2函数还返回了f2函数的作用域。这个函数反回了作用域里的变量值。这里[0]取到了x变量,[1]取到了y变量值,确切的说是x,y在作用域里的值。
x=1 def f1(): x=2 y=1 def f2(): print(x) y return f2 f=f1() f() print(f.__closure__) #运行结果:2 #(<cell at 0x00000000021A1A68: int object at 0x000000001E1BB6D0>, <cell at 0x00000000021A1A98: int object at 0x000000001E1BB6B0>) print(f.__closure__[0].cell_contents) #2 #2 print(f.__closure__[1].cell_contents) #2 #1