开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Python回顾与整理9:函数和函数式编程

简介:
+关注继续查看

  无论在什么编程语言中,函数都不可或缺,充分利用函数的特性,可以大大减少我们程序中的代码量。




1.什么是函数


        所谓函数,英文名为function,其实就是表示为实现一定功能的一段代码,显然,如果需要多次实现某一功能时,使用函数就是把重复代码放入其中,既节省空间,又有助于保持一致性(主要是修改代码时)。


(1)函数vs过程

        两者都是可以被调用的实体,过程是简单、没有返回值、特殊的函数。在Python中,过程就是函数,因为解释器会隐匿地返回默认值None。


(2)返回值与函数类型

        在C语言中,如果定义的一个函数没有返回值,则默认返回`void`,并且同时还要在定义函数时声明函数的类型(即返回值为void的函数类型)。

        在Python中,并不需要定义函数的返回值类型,函数可以返回不同类型的值,而如果没有返回值,则默认返回None:

1
2
3
4
5
6
7
8
9
10
>>> def hello():
...     print 'hello world!'
... 
>>> res = hello()
hello world!
>>> res
>>> print res
None
>>> type(res)
<type 'NoneType'>

        另外需要注意的是,跟C语言一样,Python也只能返回一个值或对象,但也许你会看到下面这样的情况:

1
2
3
4
>>> def foo():
...     return 'xpleaf''clyyh'
... 
>>> res = foo()

        执行没有报错,似乎真的可以返回多个对象!但其实,它真的返回了一个对象:

1
2
3
4
>>> res
('xpleaf''clyyh')
>>> type(res)
<type 'tuple'>

        即在上面的函数中,其实是隐式地返回了一个元组,只是看起来像是返回了多个对象而已。显然,Python的这种特性要比C语言的灵活很多。关于返回值数量,可总结如下:

返回的对象的数目 Python实际返回的对象
0 None
1 object
>1 tuple




2.调用函数


(1)函数操作符

        其实就是使用圆括号来调用一个函数。


(2)关键字参数

        指的是在调用函数时,可以通过给指定参数传值,而不需要按照原来函数定义参数的顺序来传值。

        定义如下一个函数:

1
2
def net_conn(host, port):
    net_conn_suite

        可以有如下调用方式:

  • 标准调用(非关键字参数调用)

1
2
# 给参数传递值时,必须按照原来参数定义的顺序传递值
net_conn('www.xpleaf.com'80)
  • 关键字参数调用

1
2
3
4
5
# 按照顺序给参数传递值
net_conn(host='www.xpleaf.com', port=80 )
 
# 不按照顺序给参数传递值
net_conn(port=80, host='www.xpleaf.com')


(3)默认参数

        默认参数就是声明了默认值的参数,因为给参数赋予了默认值,所以在调用函数时,不向该参数传入值也是允许的,后面会有讨论。


(4)参数组

        通过一个把元组(非关键字参数)或字典(关键字参数)作为参数部分传递给函数,可以在Python中执行一个没有显式定义参数的函数。如下:

1
func(*tuple_grp_nonkw_args, **dict_trp_kw_args)

        当然也可以给出其它形参,包括标准的位置参数(既不是默认参数也不是关键字参数,即按照函数定义时参数的位置来给相应的参数传递值)和关键字参数(函数调用时指定给哪一个参数传递值,其实就是所谓关键字参数了),函数调用的完整语法如下:

1
func(positional_args, keyword_args, *tuple_grp_nonkw_args, **dict_grp_kw_args)




3.创建函数


(1)def语句

        Python中使用def语句来定义函数,语法如下:

1
2
3
def function_name(arguments):
    "function_documentation_string"
    function_body_suite

        即Python函数由标题行文档字符串函数体组成。


(2)声明与定义比较

        在C语言中,往往习惯于先在文件前声明一个函数,然后在文件末尾处才定义这个函数。而在Python中,声明与定义是被视为一体的,这是因为Python的函数由声明的标题行和随后定义的函数体组成。


(3)前向引用

        和大多数编程语言一样,如果在声明函数(在Python中其实也就是定义函数)前对其进行使用,就会有问题:

1
2
3
4
5
6
7
8
9
10
>>> def foo():
...     print 'in foo()'
...     bar()
... 
>>> foo()
in foo()
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
  File "<stdin>", line 3in foo
NameError: global name 'bar' is not defined

        显然bar()还没有被声明(定义),在定义之后就可以使用了:

1
2
3
4
5
6
7
8
9
10
>>> def bar():
...     print 'in bar()'
... 
>>> def foo():
...     print 'in foo()'
...     bar()
...
>>> foo()
in foo()
in bar()

        当然,如果在一个脚本文件中,不按顺序给出定义bar()、定义foo()、执行foo()的顺序也是可以的:

1
2
3
4
5
6
7
8
9
def foo():
    print 'in foo()'
    bar()
 
def bar():
    print 'in bar()'
 
foo()
# 即相当于先声明foo(),再声明bar(),接着调用foo(),这里bar()已经存在了

        执行如下:

1
2
3
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
in foo()
in bar()

        但如果在声明(定义)bar()前执行foo(),就会报错:

1
2
3
4
5
6
7
8
def foo():
    print 'in foo()'
    bar()
 
foo()
 
def bar():
    print 'in bar()'

        执行如下:

1
2
3
4
5
6
7
8
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
in foo()
Traceback (most recent call last):
  File "/home/xpleaf/PycharmProjects/Python_book/11/test.py", line 7, in <module>
    foo()
  File "/home/xpleaf/PycharmProjects/Python_book/11/test.py", line 4, in foo
    bar()
NameError: global name 'bar' is not defined

        这是因为Python是一种动态语言。


(4)函数属性

        谈到函数属性,显然会涉及到名称空间的讨论,但这里先不对名称空间进行说明。

        函数属性是Python另外一个使用了句点属性标识并拥有名称空间的领域,这意味着,它是一个独立的名称空间,与函数内部所定义的变量(局部变量)所产生的名称空间没有关系,这点尤其要注意。

        看如下一个例子:

1
2
3
4
5
6
7
>>> def foo():
...     'foo() -- properly created doc string'
... 
>>> dir(foo)
['__call__''__class__''__closure__''__code__''__defaults__''__delattr__''__dict__''__doc__''__format__''__get__''__getattribute__''__globals__''__hash__''__init__''__module__''__name__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__str__''__subclasshook__''func_closure''func_code''func_defaults''func_dict''func_doc''func_globals''func_name']
>>> foo.__doc__
'foo() -- properly created doc string'

        可以给foo()添加其它的属性:

1
2
3
4
5
6
7
8
9
>>> foo.version = 0.1
>>> foo.writer = 'xpleaf'
>>> dir(foo)
['__call__''__class__''__closure__''__code__''__defaults__''__delattr__''__dict__''__doc__''__format__''__get__''__getattribute__''__globals__''__hash__''__init__''__module__''__name__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__str__''__subclasshook__''func_closure''func_code''func_defaults''func_dict''func_doc''func_globals''func_name''version''writer']
>>> foo.version
0.1
>>> foo.writer
'xpleaf'
# 可以看到version多了version和writer属性,并可以直接通过句点属性的方式进行调用

        当然,上面定义的这些函数属性也会保存在函数的__dict__属性字典中,并且可以通过字典的方式进行调用:

1
2
3
4
5
6
>>> foo.__dict__
{'writer''xpleaf''version'0.1}
>>> foo.__dict__['writer']
'xpleaf'
>>> foo.__dict__['version']
0.1

        但是对于函数本身已经存在的__attribute__类型的属性,不会保存在这个字典中,如__doc__属性。

        可以看下面的一个例子来理解“函数属性与函数局部变量分别拥有独立的名称空间”的特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def info():
...     name = 'xpleaf'
...     age = 21
...     print locals()
... 
>>> info()
{'age'21'name''xpleaf'}
>>>
>>> info.name = 'cl'
>>> info.age = 20
>>> info.__dict__
{'age'20'name''cl'}
>>>
>>> info()
{'age'21'name''xpleaf'}


(5)内部/内嵌函数

        在函数中可以定义另外一个函数,举例如下:

1
2
3
4
5
6
7
8
9
>>> def foo():
...     def bar():
...             print 'bar() called'
...     print 'foo() called'
...     bar()
... 
>>> foo()
foo() called
bar() called

        如果内部函数访问外部函数里定义的对象,那么就又涉及到闭包的问题,后面会有介绍。


(6)函数(与方法)装饰器

        在没有装饰器之前,如果要在类中定义一个静态方法,需要使用下面的方法:

1
2
3
class MyClass(object):
    def staticFoo():
        staticFoo = staticmethod(staticFoo)

        即要在该静态方法中加入类似staticmethod()内建函数将该方法转换为静态方法,这显然非常麻烦,而有了装饰器之后,就可以写成下面这样:

1
2
3
4
class MyClass(object):
    @staticmethod
    def staticFoo():
        pass

        这样就简洁很多了。

(a)无参数装饰器    

  • 一个装饰器

        下面的情况:

1
2
3
@f
def foo():
    pass

        其实就相当于:

1
2
3
def foo():
    pass
foo = g(foo)
  • 多个装饰器

        下面的情况:

1
2
3
4
@g
@f
def foo():
    pass

        就相当于:

1
2
3
def foo():
    pass
foo = g(f(foo))


(b)含参数装饰器

  • 带有参数的一个装饰器

        下面的情况:

1
2
3
@decomaker(deco_args)
def foo():
    pass

        就相当于:

1
2
3
def foo():
    pass
foo = decomaker(deco_args)(foo)

        用这样的思想去理解就非常好理解了:decomaker()用deco_args做了些事并返回函数对象,而该函数对象正是以foo作为其参数的装饰器

        下面多个装饰器的例子也是按这样的思想去理解。

  • 带有参数的多个装饰器

        下面的情况:

1
2
3
4
@deco1(deco_arg)
@deco2()
def foo():
    pass

        就相当于:

1
2
3
def foo():
    pass
foo = deco1(deco_arg)(deco2(foo))


(c)实践例子

        OK,有了上面的理论基础,理解下面一个较为复杂的装饰器就很容易了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import wraps
 
def log(text):
    def decorator(func):
        @wraps(func)                    #it works like:wraper.__name__ = func.__name__
        def wrapper(*args, **kwargs):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return decorator
 
 
@log('Hello')
def now(area):
    print area, '2016-01-23'
     
 
now('Beijing')
print 'The name of function now() is:', now.__name__

        执行如下:

1
2
3
4
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/decorator_test/dec10.py
Hello now():
Beijing 2016-01-23
The name of function now() is: now

对于该程序的执行过程,可以分析如下:

1.先执行log('Hello')函数,此时返回了一个新的函数,只不过其中的text变量被替换为'Hello',所以用来装饰now函数的新的装饰器如下:

1
2
3
4
5
6
def decorator(func):
    @wraps(func)                    #it works like:wraper.__name__ = func.__name__
    def wrapper(*args, **kwargs):
        print '%s %s():' % ('Hello', func.__name__)
        return func(*args, **kwargs)
    return wrapper

2.所以此时的now函数,就相当于:

1
now = decorator(now)

3.即now就相当于:

1
2
3
4
def now(*args, **kwargs):
    print '%s %s():' % ('Hello', old_now.__name__)
    return old_now(*args, **kwargs)
# 现在的函数名称变为了now而不是wrapper,是因为使用了wraps装饰器

   所以,输出的结果也就非常好理解了。

        关于wraps,它也是一个装饰器,使用它的作用是,被我们用自定义装饰器修改后的函数,它的函数名称,即func.__name__跟原来是一样的,而它的工作原理正如上面所提及的,即:

1
wraper.__name__ = func.__name__

        也就是说,使用wraps可以不改变原来函数的属性,当然,上面只是简单说明了一下其工作原理,详细的可以参考wraps的源代码。

        在GitHub上给出了10个理解装饰器的例子,可以参考一下:https://github.com/xpleaf/decorator




4.传递参数


  • 对象特性

        在Python中,函数也是以对象的形式存在,因此可以通过引用传递的方式赋值给其它变量:

1
2
3
4
5
6
>>> def foo():
...     print 'in foo()'
... 
>>> bar = foo
>>> bar()
in foo()

        需要注意的是,`foo`为函数对象的引用,而`foo()`则为函数对象的调用。但是因为函数对象最初是赋给foo的,所以函数对象的名字仍然是'foo',如下:

1
2
3
4
>>> foo.__name__
'foo'
>>> bar.__name__
'foo'
  • 参数传递

        因为函数本身也是一个对象,当然也就可以作为参数传递给其它函数,如下:

1
2
3
4
5
6
7
8
>>> def foo():
...     print 'in foo'
... 
>>> def bar(argfunc):
...     argfunc()
... 
>>> bar(foo)
in foo

        其实就相当于把foo通过引用传递的方式赋值给argfunc,然后再调用,这跟对象特性中的原理是一样的。

        

        可以看下面的一个应用例子:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
 
def convert(func, seq):
    'conv, sequence of numbers to same type'
    return [func(eachNum) for eachNum in seq]
 
myseq = (12345.67-6.2e89999999999L)
 
print convert(int, myseq)
print convert(long, myseq)
print convert(float, myseq)

        执行如下:

1
2
3
4
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
[12345-6200000009999999999]
[123L45L-620000000L9999999999L]
[123.045.67-620000000.09999999999.0]




5.Formal Arguments


        所谓Formal Arguments,即正式参数,包括位置参数和默认参数;而非正式参数,主要包括非关键字可变长参数(元组)和关键字可变长参数(字典)。

        在Python中,函数的形参集合由在调用函数时的所有参数组成,显然,传入的参数应该和函数原来定义的参数列表(形参)要精确的匹配。

        传入的参数主要包括如下:

  • 所有必要参数:即应该以正确的位置顺序来传入函数的参数

  • 关键字参数:按顺序或不按顺序,但这些关键字都应该是原来函数中有定义的

  • 默认参数:在函数定义时默认就赋了值的参数,调用函数时可以不指定默认参数

        函数执行时,就会为各个参数创建一个局部名称空间。


(1)位置参数

        所谓位置参数,即在调用函数时,应该以在被调用函数中定义的准确顺序来传递。

        当然,如果指定了该参数的关键字,就变成关键字参数了,调用函数时也就按照关键字参数的方式去给形参赋值了。


(2)默认参数

        默认参数就是在函数调用时,如果没有为参数提供值,则使用预先定义的默认值。不过需要注意的是,在Python中,在定义函数时,默认值应该在位置参数之后,即:

1
def func(posargs, defarg1=dval1, defarg2=dval2,...)

        否则就会报错:

1
2
3
4
5
>>> def foo(host='www.xpleaf.com', port):
...     print "host:%s, port:%s" % (host, port)
... 
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument




6.可变长度的参数


        如果不确定函数中的参数数目,可以使用可变长度的参数,在Python中,主要包括两种可变长度参数类型,一种是不指定关键字的非关键字可变长参数(元组),而另外一种是指定关键字的关键字可变长参数(字典),不过需要注意如下规则:

  • 两种可变长度的参数都必须位于Formal Arguments(位置参数和默认参数)之后

  • 非关键字可变长参数需要在关键字可变长参数之前


(1)非关键字可变长参数(元组)

  • 语法

1
2
def function_name([formal_args, ] *vargs_tuple)
    function_body_suite

        举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> def tupleVarArgs(arg1, arg2='defaultB'*theRest):
...     print 'formal arg 1:', arg1
...     print 'formal arg 2:', arg2
...     for eachXtrArg in theRest:
...             print 'another arg:', eachXtrArg
... 
>>> tupleVarArgs('abc')
formal arg 1: abc
formal arg 2: defaultB
>>>
>>> tupleVarArgs(234.56)
formal arg 123
formal arg 24.56
>>> 
>>> tupleVarArgs('abc'123'xyz'456.789)
formal arg 1: abc
formal arg 2123
another arg: xyz
another arg: 456.789


(2)关键字可变长参数(字典)

        在关键字可变长参数中,参数被放入一个字典中,其中字典的键为参数名,值为相应的参数值。

  • 语法

1
2
def function_name([formal_args, ][*vargst, ] **vargsd):
    function_body_suite

        举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> def dictVarArgs(arg1, arg2='defaultB'**theRest):
...     print 'formal arg1:', arg1
...     print 'formal arg2:', arg2
...     for eachXtrArg in theRest.keys():    # 使用dict.keys()只是为了说明theRest是一个字典
...             print 'Xtra arg %s: %s' % (eachXtrArg, str(theRest[eachXtrArg])) 
... 
>>> dictVarArgs(1220740.0, c='grail')
formal arg1: 1220
formal arg2: 740.0
Xtra arg c: grail
>>> 
>>> dictVarArgs(arg2='tales', c=123, d='poe', arg1='mystery')
formal arg1: mystery
formal arg2: tales
Xtra arg c: 123
Xtra arg d: poe
>>> 
>>> dictVarArgs('one', d=10, e='zoo', men=('freud''gaudi'))
formal arg1: one
formal arg2: defaultB
Xtra arg men: ('freud''gaudi')
Xtra arg e: zoo
Xtra arg d: 10

        当然,非关键字可变长参数和关键字可变长参数也是可以同时使用的,只需要遵循参数的先后顺序规则即可,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def newfoo(arg1, arg2, *nkw, **kw):
...     print 'arg1 is:', arg1
...     print 'arg2 is:', arg2
...     for eachNKW in nkw:
...             print 'additional non-keyword arg:', eachNKW
...     for eachKW in kw.keys():
...             print "additional keyword arg '%s': %s" % (eachKW, kw[eachKW])
... 
>>> newfoo('wolf'3'projects', freud=90, gamble=96)
arg1 is: wolf
arg2 is3
additional non-keyword arg: projects
additional keyword arg 'gamble'96
additional keyword arg 'freud'90


(3)调用带有可变长参数对象函数

        指的是,将非关键字可变长参数作为元组、关键字可变长参数作为字典来对函数进行调用,使用的函数如下:

1
2
3
4
5
6
7
8
>>> def newfoo(arg1, arg2, *nkw, **kw):
...     print 'arg1 is:', arg1
...     print 'arg2 is:', arg2
...     for eachNKW in nkw:
...             print 'additional non-keyword arg:', eachNKW
...     for eachKW in kw.keys():
...             print "additional keyword arg '%s': %s" % (eachKW, kw[eachKW])
...
  • 直接在参数列表中创建元组和字典

1
2
3
4
5
6
7
>>> newfoo(24*(68), **{'foo'10'bar'12})
arg1 is2
arg2 is4
additional non-keyword arg: 6
additional non-keyword arg: 8
additional keyword arg 'foo'10
additional keyword arg 'bar'12
  • 先定义元组和字典对象,再对函数进行调用

1
2
3
4
5
6
7
8
9
10
11
12
>>> aTuple = (678)
>>> aDict = {'z'9}
>>> newfoo(123, x=4, y=5*aTuple, **aDict)
arg1 is1
arg2 is2
additional non-keyword arg: 3
additional non-keyword arg: 6
additional non-keyword arg: 7
additional non-keyword arg: 8
additional keyword arg 'y'5
additional keyword arg 'x'4
additional keyword arg 'z'9

        不过还有一点需要注意的是,因为还有额外的非关键字参数'3'以及'x'和'y'关键字对,但它们不是'*'和'**'的可变参数中的元素,所以aTuple和aDict参数仅仅是被调函数中最终接收的元组和字典的子集。




7.函数式编程


        Python并不是一种函数式编程语言,但是它支持函数式编程语言的构建,主要是提供lambda表达式和四种内建函数的形式来支持这种特性。

关于函数式编程,借用liao Python的解释:

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

    函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

    Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

        关于Python函数式编程的特点,需要通过对lambda表达式及四种内建函数的理解才能有所体会。


(1)匿名函数与lambda

        在Python中可以使用lambda关键字来创建匿名函数,语法如下:

1
lambda [arg1[, arg2, ... argN]]: expression

匿名函数:

所谓匿名函数,即是没有名字的函数,在Python中,该函数调用完毕后(其实是在创建声明该匿名函数的过程中同时完成调用),就会被Python解释器通过垃圾回收机制进行回收,因为此时它的引用计数已经为0了。

        一个完整的lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行,同时,参数是可选的,如果使用参数的话,通常参数也是表达式的一部分。

lambda表达式返回可调用的函数对象:

用合适的表达式调用一个lambda生成一个可以像其他函数一样使用的函数对象。它们可以被传给其他函数,用额外的引用别名化,作为容器对象以及作为可调用的对象被调用(如果需要的话,可以带参数)。当被调用的时候,如果给定相同的参数的话,这些对象会生成一个和相同表达式等价的结果。

  • lambda表达式可以和单行的函数等价

1
2
3
4
>>> def true(): return True
... 
>>> lambdaTrue
<function <lambda> at 0x7f8112645de8>

        不过不同的是,单行的函数在定义后还是存在的,因为有引用,但是lambda表达式生成的函数对象就会马上被回收,因为没有被引用。

  • 使用默认和可变的参数

        lambda表达式:

1
2
lambda x, y=2: x+y
lambda *z: z

        等价于:

1
2
def usuallyAdd2(x, y=2): return x+y
def showAllAsTuple(*z): return z

        可以做如下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> a = lambda x, y=2: x+y
>>> a(3)
5
>>> a(35)
8
>>> 
>>> b = lambda *z: z
>>> b(23'xyz')
(23'xyz')
>>> b(42)
(42,)
>>> 
>>> c = lambda **kwargs: kwargs
>>> c(x=3, y=4, z=5)
{'y'4'x'3'z'5}
>>> d = lambda *args, **kwargs: (args, kwargs)
>>> d(345, x='abc')
((345), {'x''abc'})

        

        所以从上面的分析中就不难看出,其实lambda使用起来就像是一个函数,只是你可以选择保存或不保存它所返回的函数对象。


(2)内建函数apply()、filter()、map()和reduce()

        这些函数提供了Python函数式编程的特性,而lambda函数可以很好地和使用了这些函数的应用程序结合起来,因为它们都带了一个可执行的函数对象,lambda表达式提供了迅速创建这些函数的机制。

        如下:

函数式编程的内建函数
内建函数 描述
apply(func[, nkw][, kw])

用可选的参数来调用func,nkw为非关键字参数,kw为关键字参数;返回值是函数调用的返回

(该函数已经不建议使用)

filter(func, seq)

调用一个布尔函数func来迭代遍历每个seq中的元素;返回一个使func返回值为true的元素的序列

(可以通过列表的综合使用来替代)

map(func, seq1[, seq2...]) 将函数func作用于给定序列(s)的每个元素,并用一个列表来提供返回值;如果func为None,func表现为一个身份函数,返回一个含有每个序列中元素集合的n个元组的列表
reduce(func, seq[, init]) 将二元函数作用二seq序列的元素,每次携带一对(先前的结果以及下一个序列元素),连续地将现有的结果和下一个值作用在获得的随后的结果上,最后减少序列为一个单一的返回值;如果初始值init给定,第一个比较会是init和第一个序列元素而不是序列的头两个元素


  • apply(func[, nkw][, kw])

        目前已经不再建议使用,因为现在的函数本来就支持参数为可变参数。

  • filter(func, seq)

        使用filter()内建函数可以实现过滤的功能,其工作原理类似如下:

1
2
3
4
5
6
def filter(bool_func, seq):
    filtered_seq = []
    for eachItem in seq:
        if bool_func(eachItem):
            filtered_seq.append(eachItem)
    return filtered_seq

        下面通过多次重构一个程序来说明filter()的用法,同时也说明它完全可以用列表的综合应用替代,如下:

  1. 返回一个序列中的奇数

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
from random import randint
 
def odd(n):
    return % 2
 
allNums = []
for eachNum in range(9):
    allNums.append(randint(199))
print filter(odd, allNums)

        执行如下:

1
2
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
[65993159]
  1. 第一次重构:使用lambda表达式

1
2
3
4
5
6
7
#!/usr/bin/env python
from random import randint
 
allNums = []
for eachNum in range(9):
    allNums.append(randint(199))
print filter(lambda n: n%2, allNums)

        执行如下:

1
2
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
[59, 51, 17, 15]
  1. 第二次重构:通过列表的综合使用替换filter()

1
2
3
4
5
6
7
#!/usr/bin/env python
from random import randint
 
allNums = []
for eachNum in range(9):
    allNums.append(randint(199))
print [n for in allNums if n%2]

        执行如下:

1
2
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
[85, 93, 7, 91, 87, 63]
  1. 第三次重构:进一步简化代码

1
2
3
4
#!/usr/bin/env python
from random import randint as ri
 
print [n for in [ri(199for in range(9)] if n%2]

        执行如下:

1
2
/usr/bin/python2.7 /home/xpleaf/PycharmProjects/Python_book/11/test.py
[9394385]
  • map(func, seq1[, seq2...])

        将函数func并行(如果存在多个序列的话)作用(迭代)于每一个序列中的每一个元素,将所得结果保存在一个列表中,如下:

  1. 单序列

        单序列的map函数的工作原理类似如下:

1
2
3
4
5
def map(func, seq):
    mapped_seq = []
    for eachItem in seq:
        mapped_seq.append(func(eachItem))
    return mapped_seq

        举例如下:

1
2
3
4
>>> map(lambda x:x+2, [ i for in range(6)])
[234567]
>>> map(lambda x: x**2range(6))
[01491625]

        可以看到,lambda表达式这里就有非常大的作用了,即不需要特别定义一个函数,只需要用lambda表达式生成一个匿名函数就可以了。

        不过从功能上分析,单序列的map()函数功能完全可以用列表解析实现:

1
2
3
4
>>> [x+2 for in range(6)]
[234567]
>>> [x**2 for in range(6)]
[01491625]
  1. 多个序列

        多个序列的map()函数应用就比较灵活了,其工作原理可以查看书上的图解,不过从下面的例子中也可以很容易知道:

1
2
3
4
5
6
7
>>> map(lambda x, y: x + y, [135], [246])
[3711]
>>> 
>>> map(lambda x,y: (x+y, x-y), [135], [246])
[(3-1), (7-1), (11-1)]
>>> map(None, [135], [246])
[(12), (34), (56)]

        最后一个例子,函数部分使用None,可以用来连接两个序列,并组成一个新的序列,当然,因为有了zip()函数,就可以直接使用zip()函数来实现了:

1
2
>>> zip([135], [246])
[(12), (34), (56)]
  • reduce(func, seq[ ,init])

        reduce()函数通过取出序列的头两个元素,将它们传入二元函数来获得一个单一的值来实现。然后又用这个值和序列的下一个元素来获得又一个值,之后继续直到整个序列的内容都遍历完毕以及最后的值被算出来为止。

        因此,reduce()的工作原理类似如下:

1
2
3
4
5
6
7
8
9
def reduce(bin_func, seq, init=None):
    Iseq = list(seq)
    if init is None:
        res = Iseq.pop(0)
    else:
        res = init
    for item in Iseq:
        res = bin_func(res, item)
    return res

        不使用reduce()时,可以用下面的方法计算一个序列中元素的和:

1
2
3
4
5
6
7
8
9
>>> def mySum(x, y): return x+y
... 
>>> allNums = range(5)
>>> total = 0
>>> for eachNum in allNums:
...     total = mySum(total, eachNum)
... 
>>> print 'the total is:', total
the total is10

        但是如果使用reduce()函数和lambda表达式,只需要一行代码就可以了:

1
2
>>> print 'the total is:'reduce(lambda x, y: x+y, range(5))
the total is10

        相当于reduce()函数运行了如下的算术操作:

1
( ( (0 + 1) + 2) + 3) + 4) = 10


(3)偏函数应用

        所谓偏函数,其实就是把一个函数的某个参数固定下来,虽然可以通过函数的默认参数来实现这个功能,但是使用偏函数会方便很多。

  • 简单的函数式例子

        operator模块中有add和mul两个函数,分别实现两个数相加和相乘的功能,如下:

1
2
3
4
5
>>> from operator import add, mul
>>> add(13)
4
>>> mul(1005)
500

        但是如果每次都需要相加或相乘同一个数,这样的话就需要每次给add和mul传递两个参数,会比较麻烦。使用functools模块中的partial函数,就可以把一个函数中的某个参数固定下来,这样在下次调用这个函数时,就可以省略一个参数了,如下:

1
2
3
4
5
6
7
8
9
10
11
>>> from functools import partial
>>> add1 = partial(add, 1)            # add1(x) == add(1, x)
>>> mul100 = partial(mul, 100)   # mul100(x) == mul(100, x)
>>> add1(10)
11
>>> add1(1)
2
>>> mul100(100)
10000
>>> mul100(500)
50000

        这样的话就会方便很多。

  • 警惕关键字

        需要注意的是,固定下来的参数,如果没有指定关键字,默认是固定在原来函数的最左边的,可以先看int()工厂函数的例子:

1
2
3
4
>>> baseTwo = partial(int, base=2)
>>> baseTwo.__doc__ = 'Convert base 2 string to an int.'
>>> baseTwo('10010')
18

        即上面的例子使用了int()内建函数并将base固定为2来指定二进制字符串转化,但如果不指定base关键字时,就会出错:

1
2
3
4
5
>>> baseTwoBAD = partial(int2)    # baseTwoBAD(x) == int(2, x)
>>> baseTwoBAD('10010')    # int(2, '10010')
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
TypeError: an integer is required

        那是因为如果不指定关键字参数,则固定的参数是放到原来函数最左边的,显然如果int()函数的第一个参数为一个数字时就肯定会有异常。




8.变量作用域


(1)全局变量与局部变量

        定义在函数内部的局部变量形成局部作用域,而定义在模块中的全局变量形成全局作用域,但不管局部变量还是全局变量,都存放在它们对应的名称空间中(局部名称空间和全局名称空间)。

        在访问或使用一个变量时,会按照下面的顺序来搜索该变量:

  • 在局部作用域中寻找

  • 在全局作用域中寻找

        因此,可以通过创建局部变量来覆盖全局变量。


(2)global语句

        通过使用global语句,可以在函数内声明一个全局变量,通过这样的方式,就可以在函数内修改全局变量,举例如下:

1
2
3
4
5
6
7
8
9
10
11
>>> is_this_global = 'xyz'
>>> def foo():
...     global is_this_global
...     this_is_local = 'abc'
...     is_this_global = 'def'
...     print this_is_local + is_this_global
... 
>>> foo()
abcdef
>>> print is_this_global
def


(3)闭包

        如果在一个内部函数里,对在外部作用域(但不是在全局作用域 )的变量进行引用,那么内部函数就被认为是闭包(closure),定义在外部函数内的但由内部函数引用或者使用的变量就被称为自由变量。

        闭包是一个函数,只是它们能携带一些额外的作用域,它们所引用的上层函数的变量,既不属于全局名称空间,也不属于这个内部函数的局部名称空间,而是属于其它名称空间,因此说闭包带着“流浪”的作用域。

  • 用闭包实现模拟计数器

1
2
3