python 函数和函数式编程

1 什么是函数

函数是对程序逻辑进行结构化或过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理 的小块,把重复代码放到函数中而不是进行大量的拷贝--这样既能节省空间,也有助于保持一致性,由于你只需改变单个的拷贝而无须去寻找再修改大量复制代码的拷贝。html

1.1 过程 vs 函数

在C++里我不记得有过程这种东西,可是在一些其它的语言好比PL/SQL里面会有过程。过程和函数同样是能够调用的代码块,可是过程没有返回值。在pyhon里,面其实过程和函数是一个东西,由于若是你在函数里面不指定返回值,python的interpreter会返回None.python

1.2 返回值

python的函数会返回一个返回值。 若是你显示的指定了这个值,那么返回的就是你想要的类型,不然python的interpreter默认返回none。好比下面的例子就是:c++

>>> def foo():
...     pass
...
>>> print a
None
View Code

可是,有人声称python的函数能够返回多个值。其实事实是,python把这些返回值打包到了一个元组中。好比下面的例子:编程

>>> def foo():
...     return 1,2,'a','b'
...
>>> foo()
(1, 2, 'a', 'b')
View Code

python 能够经过返回一个容器,好比 list ,dict,tuple等来间接的实现返回多个值的目的,上面的例子中返回的是一个元组,由于元组的语法不强制要求写括号,因此 return语句就把后面逗号分隔的内容当作一个元组一块儿返回。数组

2 调用函数

2.1 关键字参数

关键字参数指的是以下调用函数的形式,能够看到在函数调用的时候采用的是(x= , y= )。这样的形式。这种理念是让调用者经过函数调用中的参数名字来区分参数。这样规范容许参不按顺序,由于解释器能经过给出的关键字来匹配参数的值。闭包

>>> def foo(x,y):
...     print x,y
...
>>> foo(y=2,x=1)
1 2
View Code

但要注意的是关键字参数的概念仅仅是针对函数调用来讲的,也就是说在定义函数的时候不用作任何的特别设定。咱们再看一个例子,假设如今有一个函数conn()须要两个参数 host 和 port。 定义以下:app

>>> def conn(host,port):
...     hostname=host
...     portnumber=port
...     print hostname,portnumber
View Code

你在调用的时候能够采用正常的调用方式,按顺序输入恰当的参数,如:ide

>>> conn('server_A',22)
server_A 22
View Code

你也能够用关键字调用的方式,这时候就不用管输入参数的顺序了,如:函数式编程

>>> conn(port=22,host='server_B')
server_B 22
View Code

2.2 默认参数

这个很简单,在c/c++里都有,就是在函数定义的时候提供一个默认的参数,这样将来调用的时候若是不显示提供参数,函数就会使用默认的参数。函数

>>> def foo(x=4,y=4):
...     return x+y
...
>>> foo()
8
View Code

2.3 参数组

python的函数在调用时,能够把参数放入一个元组或者是字典,而后把这个元组或者字典传递给函数进行调用。固然元组中存放的是非关键字参数而字典中存放的是关键字参数。

>>> def foo(x,y):
...     return x+y
...
>>> a=4,4
>>> foo(*a)
8
>>> d={'y':5,'x':3}
>>> foo(**d)
8
View Code

注意在元组前加一个* 而在字典前要加**。他们表示把字典或者元组解开的意思,也就是把a还原成 4,4 而 d还原成 y=3,x=5

3 建立函数

3.1 用def语句建立函数

建立函数的语法很是简单,以下:

def function_name(arguments):
    "function documentation string"
    function body
View Code

文档字符串是可选的。

3.2 声明和定义的比较

有些语言好比C/C++中,函数的声明和定义是分开的。声明只包括函数的名字和参数列表,而函数的定义则要包括函数的内部逻辑。函数声明和定义有区别的语言每每是由于他们要把函数声明和函数定义放到不一样的文件里,而在python里面二者是一体的。

3.3 向前引用

状况下面一段代码:

def foo():
        print "in foo()"
        bar()

def bar():
        print "in bar()"
foo()
View Code

若是放在python解释器中运行这段代码,也许你会认为会出错,由于在调用bar()函数的时候,bar函数尚未定义。 但实际上不会出错,由于虽然foo中bar调用在bar的定义以前,可是foo()自己的调用并非在bar的定义以前

3.4 函数属性

python中函数其实也是一个实例或者说对象。而python中的对象能够当作一个名称空间。换个说法就是python中的对象,好比这里的函数,能够用来存储名字。看一下下面的实例那就明白了:

>>> def foo():
...     'this is the doc string of foo'
...
>>> foo.attr1=1
>>> foo.attr2=2
>>> foo.attr1
1
>>> foo.attr2
2
View Code

咱们建立了一个函数,这个函数什么都没作,而后咱们用句点符号来为函数建立了两个属性attr1,attr2而且赋值,这样咱们就能够经过句点符号来访问这两个属性了。 关于名称空间的概念后面会有详细介绍,这里咱们只要理解为一个存储名字的空间就能够了。要注意的另一点是,上面例子中在定义foo的时候建立了一个文档字符串。所谓文档字符串就是函数里面第一个没有赋给变量的字符串,这个字符串能够经过 函数名.__doc__来访问,也能够经过help来访问

>>> help(foo)
Help on function foo in module __main__:

foo()
    this is the doc string of foo

>>> foo.__doc__
'this is the doc string of foo'
View Code

3.5 内嵌函数

内嵌函数就是在函数体的内部建立的一个函数,好比下面的代码:

>>> def foo():
...     print 'foo is called'
...     def bar():
...             print 'bar is called'
...     bar()
...
>>> foo()
foo is called
bar is called
View Code

但要注意的是,内嵌函数的做用域只能是在外部函数内,因此下面的代码就会出错

>>> bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'bar' is not defined
>>>
View Code

由于你调用bar的地方是在foo的函数体外部,这里识别不到bar。

3.6 装饰器

装饰器是python中的一个比较独特的东西,至少在c/c++中是没有这个东西的。 咱们首先看一下python的装饰器大概是什么样的,若是感受困惑,咱们后面的连接中提供了一篇文章,能够很清晰的阐述装饰器的意义已经它究竟是什么。

首先,python中使用装饰器的代码大概是下面这样的

>>> @decorator
... def foo():
...     pass
...
View Code

上面代码中的decorator就是一个装饰器,用@放在它的前面就表示它是装饰器,而上面代码的意思就是,咱们将要用 decorator 这个装饰器,来修饰一下咱们的foo函数,修饰完成以后,你再调用foo的时候,foo其实就已经变成了另一个函数,上面代码等同于 foo=decorator(foo). 

若是你仍是感受困惑,能够看一下这个连接 http://www.cnblogs.com/kramer/p/3640040.html

4 变长参数

有时候咱们须要处理可变数量参数的状况。 也就是说直到调用的时候 才会知道函数须要多少个参数。 这在python中是可以作到的。 不过,咱们知道python中调用函数的时候分为两种状况,非关键字参数和关键字参数,相对应的,若是要实现可变长参数,也要分两种状况考虑--可变长的非关键字参数和可变长的关键字参数。首先看可变长的非关键字参数。 好比下面的代码,

>>> def foo(arg1,arg2=2,*restArgs):
...     print arg1
...     print arg2
...     print restArgs
...
View Code

上面的函数定义中,除了形参 arg1,arg2=2以外,还有一共*restArgs。 最后这个形参的意思就是,若是 foo在调用的时候有大于等于3个非关键字参数,那么除了第一个要赋给arg1,第二个要赋给 arg2以外,剩下的要组成一个元组赋给 restArgs。 咱们能够看一下,运行时的状况:

>>> foo(1,2,3,4,5,6,7,8)
1
2
(3, 4, 5, 6, 7, 8)
>>> foo(1)
1
2
()
View Code

第一次调用 除了第一个和第二个参数,剩下的都被赋给了 restArgs,而第二次调用,因为只有一个参数,因此它赋给了arg1,arg2采用了默认参数,而restArgs是一个空的元组。

上面是针对非关键字参数的状况,那么可变长的关键字参数,怎么处理呢?

>>> def foo(arg1,arg2,**restKeyArgs):
...     print arg1
...     print arg2
...     print restKeyArgs
...
View Code

上面这段代码的意思是,在调用foo的时候,除了第一个和第二个参数,剩下全部的关键字参数都要复制给 restKeyArgs做为一个字典对象。好比在调用的时候:

>>> foo(1,2,x=3,y=4)
1
2
{'y': 4, 'x': 3}
View Code

1,2赋值给了arg1,arg2. x=3和y=4复制给了restKeyArgs做为一个字典对象。

若是你想让你的函数在调用的时候,能够接受任意个非关键字参数和关键字参数,你能够这样定义函数:

>>> def foo(arg1,arg2,*restNonkey,**restKey):
...     print arg1
...     print arg2
...     print restNonkey
...     print restKey
...

>>> foo(1,2,3,4,5,x=1,y=2)
1
2
(3, 4, 5)
{'y': 2, 'x': 1}
View Code

总结一下,就是说若是你想让函数接受可变长的非关键字参数,就使用一个带有一个*的形参来接受可变长个实参 , 若是你想让函数接受可变长的 关键字参数 ,那么就用一个带有两个*的形参来接受可变长个关键字实参。

可是有一个地方要明白,咱们以前提到了参数组。所谓参数组就是能够在调用函数的时候把参数打包进容器(非关键字打包进元组,而关键字参数打包进字典),而后在调用的时候经过*或者**来表名他们是关键字参数或者是非关键字参数。虽然都用到了*和**这个符号,但跟咱们这一小节讲的可变长参数虽然很像是有区别的。由于这一小节的知识是在定义函数的时候让函数能够有可变长个参数,而参数组部分的知识是说咱们能够把实参打包进容器传递给函数。 仔细看下面的例子:

>>> def foo(arg1,arg2,*restNK,**restK):
...     print arg1
...     print arg2
...     print restNK
...     print restK
...
View Code

这里是咱们这一节讲的知识点,可变长参数。而下面的调用涉及的* 和 **是参数组部分的知识点。

>>> t1
(1, 2)
>>> d1
{'y': 2, 'x': 1}
>>> foo(*t1,**d1)
1
2
()
{'y': 2, 'x': 1}
View Code

5 函数式变成

函数是编程到底和面向对象变成有什么区别,这个我还不太清楚,不过关于python的函数是编程知识点能够总结为下面的内容。

5.1 匿名函数与lambda

匿名函数,顾名思义就是没有名字的函数,而lambda就是定义匿名函数的一个方法。好比下面的代码就是用lambda定义了一个匿名函数。

>>> lambda x:x*x
<function <lambda> at 0x1c8e70>
>>> type(lambda x:x*x)
<type 'function'>
View Code

第一行代码返回一个匿名函数,用type能够验证这一点。 并且能够看出lambda定义匿名函数的语法很是简单  lambda 后面紧跟形参列表,而后是冒号':'。 冒号后面是函数逻辑。简单的说,第一行代码就是定义了一个须要接受一个形参x,而后返回x的平方的一个函数。不过这个函数没有名称。整个代码除了没有一个函数名字外跟真正的函数没什么区别。

看到这里你必定想问,那么为何咱们还须要匿名函数呢?看一下下面的场景。

假设你在写程序的时候写了下面一段代码,

map(lambda x:x*x , [y for y in range(10)])

上面这段代码使用了 lambda。逻辑很是清晰,就是说把 [y for y in range(10)]这个列表里面的全部元素都平方一次,并返回一个新的列表。 但假设若是没有lambda呢? 你须要这么写, 首先定义一个函数

>>> def a(x):
...     return x*x
...

而后你要把这个函数应用到你的代码里面去,

map(a,[y for y in range(10)])

你觉的那种作法更好呢?是lambda仍是后面这种? 要知道,在实际状况下, 你多是在面对几万条甚至更多的代码。在阅读代码的时候,遇到上面的代码,你就须要去查询一下a这个函数是干吗的。这无疑是很耗费精力的。 若是这种状况特别多,你就须要不断的去查询函数定义,而后返回来查看代码,这是很头疼的。 可是用第一种形式就至关的简单明了。

因此lambda尤为适用于这种逻辑简单的函数。尤为是,若是你这个函数仅仅定义且使用一次,那就更应该使用lambda了。 函数存在的意义是抽象而抽象的目的是提升代码复用率。可是对这种仅仅使用一次的代码,根本不涉及复用的问题,咱们为何不直观一点呢?

6 变量做用域

6.1 局部变量和全局变量

函数内部建立的变量,叫作局部变量,只在函数内部能够访问。而模块内部最高层变量叫作全局变量。全局变量在模块内任何一个地方都可以访问。以下面的例子:

>>> g1=123
>>>
>>> def foo():
...     l1=456
...     print 'g1 is ', g1
...     print 'l1 is ', l1
...
>>> foo()
g1 is  123
l1 is  456
>>> g1
123
>>> l1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'l1' is not defined
View Code

首先咱们定义了一个全局变量g1,赋值为 123。而后定义一个函数foo而且在函数内部定义局部变量l1赋值为456. 咱们在foo内部会访问如下g1和l1.  经过foo()函数调用能够发现访问g1和l1都没问题。 可是在foo外部访问g1能够,访问l1却出错。 

虽说在模块内部任何一个地方均可以访问全局变量,但这么说也不彻底正确。 python在访问一个变量的时候是先从局部做用域找起,而后向外层做用域查找。若是这个过程当中找到了变量,python就中止查找。 因此若是你在局部做用域--好比说函数内部,定义了一个和全局变量同名的变量,那么在这个局部做用域你可能就访问不到全局变量了,由于python老是先找到你定义的局部变量,而后就中止查找了。好比:

>>> global_v=1234
>>>
>>> def foo():
...     global_v=45679
...
...
>>> foo()
>>> global_v
1234
View Code

这个例子中,咱们在foo内部,也就是局部做用域定义了一个和全局变量同名的变量global_v。因此在foo内部你就访问不到全局变量global_v。因此你会发现虽然foo对global_v进行了从新赋值,可是真正的全局变量global_v并无改变。

不过你能够用global 关键字来指定,你访问的就是全局变量,以下:

>>> global_v=1234
>>> def foo():
...     global global_v
...     global_v=45678
...
>>> foo()
>>> global_v
45678
>>>
View Code

6.2 python嵌套做用域

python中做用域是嵌套的,就是说内部做用域老是能访问外部做用域中的变量。好比下面的代码:

>>> def foo1():
...     l1='first'
...     def foo2():
...             l2='second'
...             def foo3():
...                     l3='third'
...                     print 'l1 ', l1
...                     print 'l2 ', l2
...                     print 'l3 ', l3
...             foo3()
...     foo2()
...
>>> foo1()
l1  first
l2  second
l3  third
View Code

foo3是第三层函数了,可是它能够访问全部外层变量

6.3 闭包

(这一小节参考博文 http://blog.csdn.net/marty_fu/article/details/7679297)python中的闭包简单的说就是,若是在一个内部函数里,对在外部做用域(但不是在全局做用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 好比下面的例子:

>>> def foo(x):
...     def inner(y):
...             return x+y
...     return inner
...
>>>
>>> inner=foo(2)
>>> type(inner)
<type 'function'>
>>> inner(6)
8
View Code

inner是foo的内部函数,可是引用了foo的局部变量,因此就造成了闭包。  其实我最开始接触闭包的时候还有一个疑问,拿这个例子来讲,我认为foo调用返回后,x这个局部变量就应该被销毁了,因此inner调用应该会出错才对。 但实际上并非,由于python中的变量是每被引用一次 引用reference数会加1,每被销毁或者删除引用一次,reference数会减一。在这个例子中,foo引用了一次 inner又引用了一次,虽然foo退出了,可是inner没有,因此x不会被消除。

6.3.1 python闭包的几个注意事项

a. python闭包修改外部做用域的时候,不要覆盖外部变量

好比下面的例子,就是python的一个经典错误。

>>> def foo():
...     a=1
...     def bar():
...             a=a+1
...             return a
...     return bar
...
>>>
>>> b=foo()
>>> print b()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in bar
UnboundLocalError: local variable 'a' referenced before assignment
View Code

这段代码本意是在每次print b()的时候,都能递加a一次。 可是仔细分析内部函数bar() 你就会发现问题所在。bar中a=a+1这一段,在python从左到右解读的时候会认为a=说明a是python的一个局部变量。 因此这里a就覆盖了foo中的a。可是紧接着你又使用a+1,这时候python就会认为你在使用一个没有赋值的局部变量,因此就出错了。 解决办法很简单,就是把a变成一个容器。

>>> def foo():
...     a=[1]
...     def bar():
...             a[0]=a[0]+1
...             return a[0]
...     return bar
...
>>> b=foo()
>>> print b()
2
View Code

为何python 看到a[0]的时候就不会认为它是变量了呢? 我是这么理解的,若是看到一个a=认为它是变量,而后在本地先查找就找到了,因此就认为是local变量。可是看到a[0]的时候,会首先认为a是一个容器,而后就会在local查找,可是本地又没有a的定义,因此就会到外层查找,而这样就查到了外层的a。 

ok,如今你应该会以为上面的写法有些别扭吧?咱们的本意只是想让闭包函数可以引用外层函数的变量foo而已,有必要弄出容器什么的这么奇怪吗? python 3对这个就有了改进。在python 3中,你只要在 bar里面 a的前面加一个nonlocal 声明它是否是一个local变量就能够了。这样python就会跳过local 从外层开始查找,这样就找到了 foo的a。

b. another error

咱们再看一个例子,这个例子可能和闭包没太大关系,但也值得一看。 先看下面一段代码:

>>> for i in range(3):
...     print i
...
0
1
2
>>> print i
2
>>>
View Code

这段代码自己只是很简单的一段for循环,可是在python里面for循环有个问题就是for循环结束以后不会销毁它的内部变量 i.

python 还有一个问题就是python的函数体只有在执行的时候才会肯定其函数体里面变量的值。以下:

>>> funList=[]
>>> for i in range(3):
...     def foo(x): print x+i
...     funList.append(foo)
...
>>> for f in funList:
...     f(2)
...
View Code

这一段代码你也许会认为执行的结果是2,3,4 但其实是4,4,4。这是由于python把这些函数放入funList的时候并无肯定i的值。

6.3.2 闭包在编程中的意义

说了这么多,闭包在函数编程中到底有什么意义呢?主要有以下两种做用:

a. 闭包执行完成后,保留住当前的运行环境。

以一个棋盘游戏为例,假设咱们有一个50*50的棋盘,左下角坐标为0,0 我须要一个函数,假设函数名为 play,这个函数接受两个参数,方向和步长,而后返回按照方向步长移动棋子后,棋子的新位置。固然这个函数还要记住我如今的位置,下一次在执行的时候还要从这个位置开始执行,这里,就能够用到闭包了,由于闭包能够记住上一次执行结束的环境。

>>> orignal=[0,0] #orignal locaiton 
>>> def create(pos=orignal):
...     def player(direction,step):
...             pos[0]=pos[0]+direction[0]*step #x direction
...             pos[1]=pos[1]+direction[1]*step #y direction
...             return pos
...     return player
...
>>> p=create()
>>> print p([1,0],2) #x direction 2 steps
[2, 0]
>>> print p([1,0],3) #x direction 3 steps
[5, 0]
>>> print p([0,1],3) #y direction 3 steps
[5, 3]
>>> print p([1,0],-2)#x direction -2 steps
[3, 3]
>>> print p([0,1],2) #y direction 2 steps
[3, 5]
View Code

b. 闭包函数能够根据外部做用域的局部变量来获得不一样的结果

这就相似于配置的意思。用外部函数的局部变量来配置内部闭包,看完下面的例子你就明白了。

假设咱们须要对一些文件进行过滤处理, 过滤出含有某些词的行, 看下面的代码:

>>> def makeFilter(word):
...     def filter(file):
...             f=open(file)
...             lines=f.readlines()
...             f.close
...             results=[i for i in lines if word in i]
...             return results
...     return filter
...
>>>
>>> myfilter=makeFilter('host')
>>> myfilter('/tmp/a.txt')
View Code

这个闭包中,外部函数起到了一个配置的做用,经过外部函数能够建立不一样的filter。

7 生成器

7.1 迭代器

理解生成器以前,须要了解另外一个概念迭代器。迭代器是python中一种线性的数据集合,有一个next()方法。调用next() 会依次序返回迭代器中的数据,当访问完迭代器中最后一个数据以后,会抛出预约义的异常 StopIteration。 python中线性的数据集合有不少,好比list tuple string,但他们没有next()方法,因此都不是迭代器,不过咱们能够用迭代器的工厂函数iter()来把这些类型变成迭代器。以下:

>>> a=(1,2,3,4,5,6,7,8)
>>> a1=iter(a)
>>> a1.next()
1
>>> a1.next()
2
View Code

具体能够参考这篇文章。http://www.cnblogs.com/kramer/p/3678879.html

7.2 生成器

python中使用了yield关键字的函数叫作生成器函数。普通的函数返回一个返回值,可是生成器函数却返回一个生成器。 好比下面的代码:

>>> def foo():
...     print 'begin run'
...     yield 1
...     yield 2
...     yield 3
...
>>> a=foo()
>>> type(a)
<type 'generator'>
View Code

使用了yield的函数foo 在 a=foo()的时候 并无执行print语句,而咱们用type(a)能够看到,foo()返回的是一个叫作生成器的东西。 接下来,咱们要运行a.next(),函数foo的内部逻辑才会真的开始执行,但每次调用只会调用到一个yield关键字处,而后再次调用会调用到接下来的yield关键字处。最后一个yield返回后,咱们再调用a.next()会像迭代器同样抛出StopIteration异常。

>>> a.next()
begin run
1
>>> a.next()
2
>>> a.next()
3
>>> a.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
View Code

因此,你能够发现,生成器和迭代器很像,惟一不一样的就是,生成器里面可能会有一些函数逻辑,好比这里的print语句。

7.3 yield

python 2.5以后对yield作了一些调整。原来的yield是关键字或者说是语句,但如今是表达式了。 也就是说原来的 yield 5只是表明某次迭代的时候会返回5.但如今 yield 5会有返回值了,由于yield是一个表达式。 好比下面的代码:

>>> def foo():
...     print 'step 1'
...     a1=yield 1
...     print 'a1 is ', a1
...     a2=yield 2
...     print 'a2 is ', a2
...     a3=yield 3
...     print 'a3 is ', a3
...
View Code

在你调用 b=foo()会生成一个生成器,接下来你调用b.next()会执行到第一个yield 处并暂停

>>> b=foo()
>>> b.next()
step 1
1
View Code

接下来就是yield 跟之前不一样的地方了。 咱们知道yield如今是表达式了。a1=yield 1不但会打印出一个1,还应该返回一个值给a1。 那么这个值是什么呢? 这个值是一个叫send的函数传递进去的。 新版本的python有了一个send函数,这个函数的做用是接受一个参数,并把这个参数当作是yield 表达式的当前返回值。 因此咱们接下来的调用是这样的。

>>> b.send('two')
a1 is  two
2
View Code

咱们调用b.send('two')。 send这时候会把two当作当时yield的返回值 传递给a1.而后生成器继续往下走走到第二个yield 表达式处。 如今你明白send的用法了吧。 ok 其实next()和send(None)是等价的,因此咱们能够理解为next()就是发送一个None给当时的yield当作返回值而后继续往下执行,那么咱们看一下是否是这样的

>>> b.next()
a2 is  None
3
View Code

正是咱们所指望的

相关文章
相关标签/搜索