函数

来源:互联网 发布:浏览器代理软件 编辑:程序博客网 时间:2024/06/06 02:11

函数:最大化的代码重用和最小化代码冗余,同时可以进行流程分解,包含更多独立的函数来完成整个流程中的每个子任务。

def是可执行的代码-函数并不存在,直到运行了def后才存在。def语句在模块文件中编写,并自然而然的在模块文件第一次被导入时生成定义的函数。

def创建了一个对象并将其赋值给某一变量名

lambda创建一个函数对象,并将该对象返回给调用者。

yield向调用者发回一个结果对象,但记住它离开的地方,相当于挂起状态以便稍后能够恢复

func 是共享的

return 函数调用的结束,并且返回一个值,一个没返回值的函数自动返回none对象,这个值常被忽略


def语句是实时执行的

Python中所有语句都是实时运行的,没有独立的编译时间这样的流程。def可以出现在任何一个语句可以出现的地方,甚至嵌套。

Python函数在程序运行之前并不需要全部定义。def在运行时才进行评估,而在def中的代码在函数调用后才会评估,比如下面就是通过在调用if的时候func被没有定义,而是通过条件进行筛选,最后我们再调用得到的版本。注意区分函数和函数名,函数是对象,调用的时候它记录在内存中,并且允许任何的属性附加到记录信息供随后使用。

if test:    def func():            #Define func this way        ...else:    def func():            #or this way        ......func                       #call the version select and build

Python因为没有数据类型的限制,所以他就根据所传入的对象来进行合理性的交由对象置身来判断,比如x * y,*碰见两个整数执行乘法,而两个序列就是重复。也就是随机应变,见机行事。如果传给函数的对象有预期的方法和表达式操作符,那对于函数逻辑来说就有即插即用的兼容性。如序列对*有自身的处理方式,所以碰见*就进行重复的出现相同结构。如果不支持预期的接口,就会报错。*就相当与一个预期的接口,因为它不知道将来会对谁进行*操作,所以说如果你的对象不支持这种操作的话,就会报错。

所以说我们在Python中为对象编写接口,而不是数据类型,只要该对象支持预期的接口就行,否则报错,这也是多态的核心。。


作用域

在任何情况下,一个变量的作用域(它所使用的地方)总是由代码中被赋值的地方所决定。也就时你再哪儿赋值的(注意它不是说的定义,因为python没有变量声明,用它的时候对它赋值就行,然后我们就可以在这个区域内访问它),就限定了你能够被使用的范围。

1:如果在def中赋值,它被定位在这个函数之内

2:如果变量在一个嵌套的def中赋值,对于嵌套的函数来说,它是不可访问的

3:如果在def之外,他就是全局的

函数定义了本地作用域,而模块定义的是全局作用域

1:全局作用域是针对一个模块而言的或单个文件。因为我们写的程序都是在每一个文件中封装好的。

2:每次对函数的调用都创建一个新的本地作用域,函数def赋值的变量默认为本地

3:如果要在函数内部给一个在模块文件顶层的全局变量赋值,需要在文件内部通过global声明

      如果在函数内部要对嵌套在def中的变量赋值,可以通过nonlocal语句声明来实现

      上面的两种情况之所以这么做,都是因为2中说它会默认创建一个本地的,所以你肯定得指明那些特殊地方的变量,否则它就是新建一个相同名字的本地变量了。

4:3中说的是,在函数内赋值的情况,注意在原处更改对象并不会把变量划分为本地的,因为正常理解的话,我们在函数中肯定能够正常访问全局变量,如L是顶层被赋值的链表,如果函数内部L.append(x)不能把L变为本地的,而L=x可以,它相当与在函数内部创建一个局部变量。

5:我们可以在函数内部直接使用全局变量,但必须声明为全局变量才能够改变它的属性,如由指向列表对象,现在成为指向整数。

6:变量名解析的LEGB法则,也就是当我们调用一个变量名时如何进行查找它的值local->enclosing function locals上一层结构中的def/lambda -> global 全局 ->build-in 比如open这些系统内置的方法。strout 系统内置的变量。

7:根据6中的LEGB法则,内置的一些方法也在搜索路径,所以说我们可以直接使用内置的open方法。

#Global scopex =99                    #x is globaldef func(y):             #func is global    # local scope          z = x + y            #x is global, y and z is local    open = 'spam'        #hide built-in function    open('data.txt')     #this wont open an file.   这一点我不知道其它语言如何处理,究其原因是python中的变量仅仅时名字,跟付给它的值有关    return z


全局变量

比方说我们现在有两个模块(即两个file),然后我可以通过引入一个模块并且对它的里面变量的值进行修改

下面是对first中值进行修改,但是本身对first而言,你不知道有其它地方对它修改呀。所以说这个时候就比较隐晦性的存在一个bug在second.py 里面,像个炸弹,不知道什么时候就出bug了。

# first.pyX = 99# second.pyimport firstprint(first.X)         #reference a name in another filefirst.X = 88           #change its value 
对于上面的问题,最小化风险的解决办法,是通过一个函数进行修改。就好比给你个心里提示,这个X是将来要被其它模块进行修改的,所以要注意。这只是一个降低风险的做法,我们最好是尽量别对另一个模块进行更改

# first.pyX = 99def setX(new):    global X    X = new# second.pyimport firstfirst.setX(88)

enclosing function local -- 词法闭包

上面所要表述的也就是lisp中所谓的词法闭包,这个在lambda中是非常常见的一种写法。我在lisp中也专门写了一片关于lambda词法闭包的博客,但是当时是比较简单的描述。

嵌套的概念就是一个def A中又包含了一个def B,词法闭包说的就是如何在B中访问到A中信息的方式

1:下面的这个程序需要注意的是make中又定义了一个子程序action。并且action是作为一个值,返回作为make的值。这点可以通过查看f的输出信息看出就是一个action 函数的地址。

2:f(3)这个函数的调用,按我们正常的想法就是把3传入到f指向的函数,即action,但是这有个问题了,现在3你是传给了X,但是aciton中的N你怎么办呀?如果调用了make函数的话,它会给N赋值,但是现在我们没有调用make呀!那N怎么办?

3:词法闭包就是针对上面的问题而设计的,当我们第一次调用完make方法以后,实际上N作为函数f的执行的状态信息被保存了下来,所以f=make(2),N的值为2会被存下来,当我们在调用f函数时候,它会读取状态信息,然后发现N为2,所以f(3)的值就是9啦。

4:对于上面的“N作为函数的执行状态信息保存下来”,我感觉它是指的作为f函数的信息。你可以实验再定义一个函数p=make(3),然后执行再执行f(3)它仍旧是9.所以状态信息是跟当前的函数名f的。只要f不重新被赋值,N就不会变。

>>> def make(N):... def action(X):... return X ** N... return action... >>> f= make(2)>>> f<function action at 0x8b6e4c4>>>> f(3)9>>> f(5)25

上面整个部分说的都是词法闭包,但是你应该清楚,程序的任何思想都是逐渐进化的,所以说Python的这种思想也是同样,刚开始它是不支持这种语法闭包的,在python2.2以后才支持。下面列出来的是早期当有内嵌函数时是如何保存其上层函数中值的方式,它通过给函数参数一个默认值来达到保存上一层变量的目的

def f1():    x = 88    def f2(b=x):        print(b)    f2()
其实词法闭包本身就是lisp中lambda的思想,其实最简单的方法,就是把f2作为一个单独的函数,在f1中调用f2,什么问题都解决了。

嵌套的注意点:

如果子函数嵌套在父函数的循环中,并且每一次循环子函数所引用的上一层函数中变量的值都会变,那么这一系列子函数最终保存的上一层函数中变量的值是一样的。

下面的例子试图创建一个函数的列表,即每个元素都是一个函数对象。因为i的值最终是4.所以都想当实参为2时,都想当于求4的平方。

>>> def makeAction():... acts = []... for i in range(5):... acts.append(lambda x:i ** x)... return acts... >>> act = makeAction()>>> act[0]<function <lambda> at 0x8b6e684>>>> act[0](2)16>>> act[2](2)16
对于上面解决的办法是把变化的i在上一层的值作为拥有默认值的参数传递过来

>>> for i in range(5):... acts.append(lambda x, i =i: i ** x)
记住:程序无嵌套,生活更美好!!!

nonlocal语句

我们前面说了词法闭包,但是有个问题就是,你不能够对它上一层函数中的参数进行更改。其实第一次看到下面这个情况的时候,我感觉有个问题,因为内嵌函数完全可以类比单层函数跟global全局的关系,state=4,如果在全局下一层的函数里面,它是不是就可以让我们进行这么赋值了?因为就相当与创建一个新的局部变量呗,而在内嵌函数中为啥就不行了呢?

其实造成上面困惑的原因是,我把前面引用的地方给忽略了。因为state首先被引用了,然后在它所嵌套的函数中找到了它的值,如果接下来你再进行state=的话,那就是对它进行修改了。而不是进行创建了!!!这个就类似函数与全局域的关系。如果你想在函数中更改全局变量的值的话,你必须声明为global,如果你先引用再对它赋值的话,会报同样的错;而如果你先赋值的话,相当于创建一个局部变量,然后你可以引用了。

>>> def tester4(start):... state=start... def nested(label):... print(label,state)... state=4... return nested... >>> tester4(8)<function nested at 0x8b6ea74>>>> F=tester4(8)>>> F<function nested at 0x8b6eaac>>>> F('spam')UnboundLocalError: local variable 'state' referenced before assignment   #你在赋值前先引用了>>> state=7                                                              #类比到global的情况 >>> def tester8():... print(state)... state=6... >>> tester8()UnboundLocalError: local variable 'state' referenced before assignment

使用nonlocal进行修改

nonlocal限制了查找规则:查找范围限定在所嵌套的函数中;所以所跟的变量必须只能在所嵌套在的函数中赋值,global/内置的定义全是“鞭长莫及”

>>> def tester4(start):... state=start... def nested(label):...nonlocal state... print(label,state)... state +=1... return nested... >>> F=tester4(0)>>> F<function nested at 0x8b6eaac>>>> F('spam')spam 0>>> F('ham')ham 1>>>G=tester(42)               #make a new tester>>>G('spam')spam 42 

总结:

1:def是实时的,对内嵌函数也是同样,如果内嵌函数后面没有同一缩进等级的return语句的话,该内嵌函数也将是临时的,随其所在函数的编译的结束而结束,对全局不可见了。

2:  注意内嵌函数一般后面跟一个跟def同一个缩进等级的return语句,用于返回该内嵌函数,并且在此终止函数的执行

3:对于嵌套函数或者是它所在的函数,如果想更改全局变量的话,只需在当前作用域变量声明为global就行

4:注意在函数中赋值跟引用的先后关系,如果赋值在前的话,说明新建一个局部变量;如果引用在前的话,就要注意了,正常引用它的值没有关系,如果一旦执行赋值语句的话,前面必须把变量要么声明成global要么是nonlocal

5:通过嵌套函数的方式,我们可以记住状态信息,并且能够根据状态信息的不同,创建多个副本。而使用了nonlocal语句语句我们更是得到了可变的多个副本。

6:因为嵌套函数会根据你第一次给的参数的值,产生多个副本,如果可以让这些副本中的状态信息都一致呢?只需把函数中的相应变量声明为global,然后在内嵌函数中同样声明为global,这时大家改的话,改的都是全局变量。