1、函数优点:
-
使得代码可以重复利用,减小代码冗余;
-
函数提供了一个将系统分割为不同部分的工具
2、函数编写一些概念:
-
def语句是可执行代码:只有当python运行到def的时候函数才存在
-
def创建了一个对象并将其赋值给某一变量名:当python运行到def语句时,它会生成一个新的函数对象并将其赋值给这个函数名
-
return将一个函数计算结果返回给调用者
-
函数是通过对象引用来传递的:python中参数通过赋值传递给函数
-
global声明了一个模块级的变量并被赋值,这个变量名可以在真整个模块中使用
-
参数、返回值以及变量并不是声明:python函数没有类型的约束
-
def语句:
创建一个函数对象并将其赋值给一个变量名,一般格式如下:
def <name>(arg1,arg2,…argN):
<statements>
return <value> #此处缺省默认为None对象
-
def语句是实时执行的:def语句是可执行语句,当其运行时,它创建并将一个新的函数对象赋值给一个变量名,事实上,python语句都是实时运行的,没有独立的编译时间这样的流程
3、python中的多态:
python将对某一对象在某种语法上的合理性郊游对象本身来判断,换句话说,函
数可以自动适应所有类别的对象类型,只要所处理对象支持所预期的函数接口,例如:
定义一个乘法函数time()如下:
>>>def time(x,y)
return x*y
>>>time(3,4)
12
>>>time(‘Ni’,4)
‘NiNiNiNi’
注意:假如所处理的某个对象不支持函数的接口操作,调用时python会抛出异常
1、作用域法则:
-
函数作用域基础:函数定义了本地作用域,模块定义的是全局作用域
-
内嵌的模块是全局作用域
-
全局作用域的作用范围仅限于单个文件:全局是指在一个文件的顶层的变量名仅对于这个文件的内部代码是全局的
-
每次对函数的调用都创建了一个新的本地作用域,即是说会存在由那个函数创建的变量的命名空间
-
函数内部赋值的变量名除非声明为全局变量,否则均为本地变量
-
函数内部定义的且尚未赋值的所有变量名都可以归纳为本地,全局或内置的
事实上,函数内部的任意赋值操作定义的变量名都将会成为本地变量,这些赋值操作有:=、impot、def、参数传递。
注意:如果实地地改变对象并不会将变量划分为本地变量,只有赋值操作才可以。例如:变量名L在模块顶层被赋值为一个列表,在函数内部的像L.append(x)这样实地改变对象的操作并不会将L划分为本地变量,而L=X却可以将L划分为本地变量
-
变量名的解析:LEGB法则
-
对于一个def语句有如下三条简单的法则:
A、变量名引用分为三个作用域进行查找:首先是本地、然后是上层函数内、之后是全局、最后是内置
B、默认情况下,变量名赋值会创建或改变本地变量
C、全局声明将赋值变量映射到模块文件内部的作用域
-
LEGB法则:
A、在函数内使用未认证的变量名时,Python会搜索4个作用域:本地作用域(L)、然后是上层结构中def或lambda的本地作用域(E)、之后是全局作用域(G)、最后是内置的作用域(B),并且在第一处能找到这个变量名的地方停止搜索,若未搜索到,则会报错
B、当在函数内给一个变量名赋值时,python会创建或改变本地作用域的变量名,除非它已被声明为全局变量
C、在函数外给一个变量名赋值时,本地作用域与全局作用域相同
-
内置作用域:
-
实际上,内置作用域仅仅是一个名为_ _builtin_ _的内置模块,但必须事先import后才能使用,因为变量名builtin本身并没有预先内置;
-
还有一点值得注意的,由LEGB法则,会使得它找到第一处变量名生效,这样就有可能在本地作用域的变量名会覆盖全局作用域和内置作用域的有着相同变量名的变量,例如:
def hider():
open=’spam’
…
open(‘data.txt’)
#此处open被上面变量名覆盖,是得open内置函数失去作用
-
global语句:
-
全局变量是位于模块文件内部的顶层的变量名
-
全局变量在函数内部被赋值时需要进行声明
-
全局变量在函数内部不经过声明也可以进行引用
-
最小化全局变量:
-
在def内部定义的变量自定义为本地变量,若希望在函数外进行修改的话需要进行全局声明,但这样会引发一些问题:由于变量的值取决于函数调用的顺序,而函数本身是任意顺序进行排列的,导致程序调试有困难
-
解决上述问题的办法:在程序中委任一个单独的模块文件区定义所有的全局变量,这样全局变量在并行线程中在不同函数间进行共享
-
最小化文件间修改:
若要在一个文件中修改另一个文件中的变量值,通常要通过属性引用直接修改,这样过于模糊,文件间最好的通信办法就是通过函数调用,传递参数,然后得到其返回值,这样可以清楚知道文件间的变量修改。
其实,除非已经被广泛认可,否则应该尽量最小化文件间变量的修改。
2、作用域与嵌套函数:
-
嵌套作用域细节:
变量查找法中的E环节,对于一个函数来说:
-
在默认情况下,一个赋值创建或改变了变量名当前的作用域。若在函数内部声明为global变量,那么就会创建或改变变量名的作用域为整个文件模块
-
若没有声明为global变量,那么将会从最内层函数作用域开始,逐渐向外查找,直到内置作用域
-
嵌套作用域举例:
-
工厂函数——一个能够记住嵌套作用域的变量值的函数
例如:
def f1():
x=88
def f2():
print x
return f2
action=f1() #make return a function
action() #call it now:print 88
在这个例子中,我们命名了f2函数的调用是在f1()运行后发生的,f2记住了在f1中嵌套作用域中的x,尽管f1已经不再激活状态;
再例如,工厂函数有时用于需要及时生成事件处理,实时对不同情况进行反馈程序中,如:
>>>def maker(N):
… def action(X):
… return X**Nac
… return action
>>>f=maker(2)
>>>f
<function action at 0x014720B0>
>>>f(3)
9
这样如果再调用外层函数,那么将会得到一个新的具有不同状态信息的嵌套函数,而里面的action函数在外层函数调用完后依然记得外层函数的作用域。
以上就是工厂函数的两个简单例子,通常类将是一个更好的具有这样进行“记忆”的选择
-
嵌套作用域和lambda:
lambda就是一个表达式,它将会生成后面会被调用的函数,lambda表达式引入了一个新的本地作用域。例如:
def func():
x=4
action=(lambda n:x**n)
return action
-
作用域与带有循环变量的默认参数的比较:
在给出的法则中,这儿有一个特例:如果lambda或者def在函数定义中,嵌套在一个循环中,并且嵌套的函数引用了一个上层作用域的变量,该变量被循环所改变,所有在这个循环中产生的函数将会有相同的值——在最后一次循环完成时被引用的变量的值,例如:
def makeActions():
acts=[]
for i in range(5): #try to remember each i
acts.append(lambda x:i**x) #all remember same last I !!!!!
return acts
接下来调用函数makeActions:
>>>acts=makeActions()
>>>acts[0]
<function <lambda> at 0x012B16B0>
>>>acts[0](2)
16
>>>acts[2](2)
16
这是由于嵌套作用域中的变量在嵌套的函数被调用时才进行查找,所以他们记住了相同的值(最后一次循环迭代中循环变量的值)
为了解决上述问题,必须使用默认参数把当前的值传递给嵌套作用域的变量,因为默认参数是在嵌套函数创建时评估的,每个函数记住了自己的变量i的值:
def makeActions():
acts=[]
for i in range(5): #use defaults instead
acts.append(lambda x,i=i:i**x) #remember current i !!!!!
return acts
>>>acts=makeActions()
>>>acts[0](2)
0
>>>acts[2](2)
4
>>>acts[4](2)
16
3、传递参数:
-
参数的传递是通过自动将对象赋值给本地变量来实现的
-
在函数内部的参数名的赋值不会影响调用者
-
改变函数的可变对象参数的值也许会对调用者有影响
-
不可变参数是“通过值”进行传递
-
可变对象是通过“指针”进行传递的
-
参数和共享引用
例子:>>>def changer():
a=2
b[0]=’spam’
>>>X=1
>>>L=[1,2]
>>>changer(X,L)
>>>X,L
(1,[‘spam’,2])
换句话说:自动传入的参数进行赋值的效果与运行一系列简单的赋值语句是相同的,即上面参数传递等效于:
>>>X=1
>>>a=X
>>>a=2
>>>X
1
>>>L=[1,2]
>>>b=L
>>>b[0]=’spam’
>>>L
[‘spam’,2]
-
避免可变参数的修改:
如果想在函数调用过程中避免修改可变参数,那么我们可以利用对象拷贝来实现,例如上例中:
L=[1,2]
changer(X,L[:]) #pass a copy, so our ‘L’does not change
我们也可以在函数内进行拷贝:
def changer(a,b):
b=b[:]
a=2
b[0]=’spam’
4、特定的参数匹配模型:
-
位置:从左到右进行匹配
-
关键字参数:通过参数名进行匹配
-
默认参数:为没有传入值的参数定义参数值
-
可变参数:收集任意多个基于位置或关键字的参数
-
关键字参数:
>>>def f(a,b,c):print a,b,c
在python中调用函数时,能够详尽的定义内容传递的位置,这种匹配方法允许通过变量名进行匹配而不是通过位置:
>>>f(a=1,c=3,b=2)
1,2,3
当调用中混合使用位置匹配和关键字匹配时,所有基于位置的参数首先从左至右的顺序匹配头部的参数,之后再进行关键字匹配
-
默认参数:
在创建函数时对参数赋值,这个值就是默认参数值,之后再调用过程中加入对应参数有值传入就将参数赋值给传入的值,否则就为默认值
-
任意参数匹配:
收集参数:
-
用法一:在函数定义中,在元组中收集不匹配的位置参数:
>>>def f(*args):print args
当这个函数被调用时,python会将所有位置相关的参数收集到一个新的元组里面,并将元组赋值给args
**特性与*相似,只不过它只对关键字参数有效,并将这些关键字参数传递给一个新的字典,并将字典赋值给args
分解参数:
-
我们也可以将*语法用于调用函数时,此时它的意思与函数定义时相反,是将参数的集合,同理**会分解字典,使其成为独立的关键字参数
-
一些参数匹配细节:
假如我们决定使用并混合特定的参数匹配模型,那么将会有如下的法则:
-
在函数调用中,所有的非关键字参数(name)必须首先出现,其后跟随所有的关键字参数(name=value),后面再跟*name形式,最后跟**name形式
-
在函数的头部,参数必须以如上的相同的顺序出现
python内部赋值来进行参数匹配的顺序:
-
通过位置分配非关键字参数
-
通过匹配变量名分配关键字参数
-
其他额外的非关键字参数分配到*name元组中
-
其他额外的关键字参数分配到**name中
-
用默认值分配给在头部为得到分配的参数
1、匿名函数lambda:
lambda表达式能创建一个可以被调用的函数,它返回了一个函数而不是像def将函数复制给一个变量名
-
lambda表达式:
lambda表达式一般形式如下:
lambda argument1,argument2,…argumentN: expression using arguments
注意:
-
lambda是一个表达式而不是一个语句,返回了一个新函数
-
lambda主体是一个单个表达式,而不是一个代码块,这个主体要十分简单,就像return语句中的代码一样
-
默认参数可以在lambda参数中使用
-
lambda主体中的代码遵循LEGB作用域查找法则
-
lambda表达式一般用处:
lambda通常用来编写跳转表,也就是行为的列表或字典,能够按照需要执行相应的动作,例如:
L=[(lambda x:x**2),(lambda x:x**3),(lambda x:x**4)]
即是说的当需要封装把小段可执行代码编写进某些地方,而这些地方从语法上说是不能编写进def语句的
2、将函数作为参数来应用:
内置函数apply:
我们可以将一个函数作为参数传递给apply函数来调用一个生成的函数,同时将那个函数的参数作为一个元组传递给apply函数,例如:
>>>def func(x,y,z):return x+y+z
>>>apply(func,(1,2,3))
6
>>>f=lambda x,y,z:x+y+z
>>>apply(f,(1,2,3))
6
我们应该重视回调
3、在序列中映射函数:map函数
map函数会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含了所有函数调用结果的一个列表,例如:
>>>counters=[1,2,3,4]
>>>def inc(x):return x+10
>>>map(inc,counters)
[11,12,13,14]
使用map与使用for循环是等价的,这样我们就可以编写自己的映射工具(mymap):
>>>def mymap(func,seq):
res=[]
for x in seq:res.append(func(x))
return res
通常内置函数的功能更加强大,因此我们在实际中可以不必这样去模拟map函数。
4、函数式编程工具:filter和reduce
函数编程概念:函数编程就是对序列应用一些函数的工具;
filter:过滤掉序列中的一些元素
reduce:对每对元素都应用函数并运行到最后结果
对reduce的理解,我们可以编写自己的reduce函数来理解reduce
>>>def myreduce(func,seq):
tally=seq[0]
for next in seq[1:]:
tally=func(tally,next)
return tally
>>>myreduce((lambda x,y:x+y),[1,2,3,4])
10
5、重访列表解析:映射
-
列表解析基础:列表解析的简单形式就是在方括号中编写一个表达式,其中的变量,在后边随后跟着一个类似for循环的头部那样的语句,有着相同的变量名的变量,并将其结果收集到一个新的列表中并返回。例如:
>>>[ord(x) for x in ‘spam’]
[115,112,97,109]
-
增加测试和嵌套循环:
-
列表解析可以增添测试语句:
>>>[x for x in range(5) if x%2==0]
[0,2,4]
-
列表解析还可以编写任意数量的嵌套for循环:
[expression for target1 in sequence1 [if condition]
target2 in sequence2 [if condition]…
targetN in sequenceN [if condition]
]
-
生成器
生成器:生成器函数在生成值以后自动挂起并暂停他们的执行和状态。生成器与一般函数的区别在于,生成器yield一个值,而不是return一个值。yield会将函数关起来并返回一个值,之后可以从这个地方再一次恢复函数。
例如:>>>def genquares(N):
for i in range(N):
yield i**2
函数每次循环都会产生一个值,并将其返回,当被暂停后,它的上一个状态被保存了下来。
当我们直接调用一个生成器函数时我们可以得到一个生成器对象,这个对象支持迭代协议!!!
-
生成器与列表解析:
从语法上来说,生成器就和一般的列表解析一样,只是它是括在圆括号中,例如:
>>>[x**2 for x in range(4)]
[0,1,4,9]
>>>(x**2 for x in range(4))
(generator object at 0x011DC648)
6、一些函数设计注意的地方:
-
耦合性:对于输入使用参数,对输出使用return语句
-
耦合性:除非必要,不要使用全局变量
-
耦合性:不要改变可变类型的参数,函数会改变传入的可改变类型对象,对此我们可以使用拷贝技巧
-
大小与聚合:函数应相对较小,功能尽量单一统一
-
耦合性:避免直接改变在另一个模块中的变量