概述
一、简介
Python中的函数具有预期特性,还支持多种调用方式以及参数类型并实现了一些函数式编程接口。Python函数中变量的作用域和递归函数都是必须理解的内容。
二、详解
1、什么是函数
函数是对程序逻辑进行结构化或过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝,这样既能节省空间,也有助于保持一致性,因为只需改变单个的拷贝而无须去寻找再修改大量复制代码的拷贝。
(1)函数 vs 过程
函数和过程比较:两者都是可以被调用的实体,但函数可能不带任何输入参数,经过一定的处理,,最后向调用者传回返回值。其中一些函数则是布尔类型的,返回一个非零或者零值。而过程是简单、特殊,没有返回值的函数。python的过程就是函数,因为解释器会隐式地返回默认值None。
(2)返回值与函数类型
函数会向调用者返回一个值,而实际编程中大部分函数更接近过程,不显示地返回任何东西。把过程看待成函数的语言通常对于“什么都不返回”的函数设定了特殊的类型或者值的名字。这些函数 对应的返回对象类型是none。
Python里的函数可以返回一个值或者对象。只是在返回一个容器对象的时候有点不同,看起来像是能返回多个对象。如下,bar()函数返回一个元组。由于元组语法上不需要一定带上圆括号,所以让人真的以为可以返回多个对象。
>>> def bar():
... return 'abc', [42, 'python'], "Guido"
...
从返回值的角度来考虑,可以通过很多方式来存储元组,如下三种保存返回值的方式是等价的。在对 x,y,z 和 a,b,c 的赋值中,根据值返回的顺序,每个变量会接收到与之对应的返回值,而aTuple直接获得函数隐式返回的整个元组。元组既可以被分解成为单独的变量,也可以直接用单一变量对其进行引用。
>>> aTuple = bar()
>>> x, y, z = bar()
>>> (a, b, c) = bar()
当没有显式地返回元素或者如果返回None时,python会返回一个None。那么调用者接收的就是python返回的那个对象,且对象的类型仍然相同。如果函数返回多个对象,python 把他们聚集起来并以一个元组返回。
许多静态类型的语言主张一个函数的类型就是其返回值的类型。在python中, 由于是动态地确定类型而且函数能返回不同类型的值,所以没有进行直接的类型关联。因为重载并不是语言特性,程序员需要使用type()这个内建函数作为代理,来处理有着不同参数类型的函数的多重声明以模拟类C语言的函数重载(以参数不同选择函数的多个原型)。
2、调用函数
(1)函数操作符
同大多数语言相同,用一对圆括号调用函数。实际上,有些人认为(())是一个双字符操作符。但任何输入的参数都必须放置在括号中,作为函数声明的一部分,括号也会用来定义那些参数。
(2)关键字参数
关键字参数的概念仅仅针对函数的调用。这种理念是让调用者通过函数调用中的参数名字来区分参数,这样规范允许参数缺失或者不按顺序,因为解释器能通过给出的关键字来匹配参数的值。
一个函数(当参数允许"缺失“的时候,也可以使用关键字参数.这取决于函数的默认参数。):
def net_conn(host, port):
net_conn_suite
标准调用:net_conn('kappa', 8080)
关键字调用:net_conn(port=8080, host='chino')
(3)默认参数
默认参数就是声明了默认值的参数,因为给参数赋予了默认值,所以在函数调用时,不向该参数传入值也是允许的。
(4)参数组
Python同样允许程序员执行一个没有显式定义参数的函数,相应的方法是通过一个把元组(非关键字参数)或字典(关键字参数)作为参数组传递给函数。基本上,可以将所有参数放进一个元组或者字典中,仅仅用这些装有参数的容器来调用一个函数,而不必显式地将它们放在函数调用中:func(*tuple_grp_nonkw_args, **dict_grp_kw_args),其中的tuple_grp_nonkw_args是以元组形式体现的非关键字参数组,dict_grp_kw_args是装有关键字参数的字典,存在这样的特性允许把变量放在元组和/或者字典里,并在没有显式地对参数进行逐个声明的情况下,调用函数。
实际上,也可以给出形参!这些参数包括标准的位置参数和关键字参数,所以在python中允许的函数调用的完整语法为:func(positional_args, keyword_args,*tuple_grp_nonkw_args, **dict_grp_kw_args),该语法中的所有的参数都是可选的,从参数传递到函数的过程来看,在单独的函数调用时,每个参数都是独立的,这可以有效地取代apply()内建函数。
3、创建函数
(1)def 语句
函数是用 def 语句来创建的,语法如下:
def function_name(arguments):
"function_documentation_string"
function_body_suite
标题行由def关键字、函数的名字以及参数的集合(如果有的话)组成。def子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串和必需的函数体。
(2)声明与定义比较
在声明和定义有区别的语言中,往往是因为函数的定义可能和其声明放在不同的文件中。python将这两者视为一体,函数的子句由声明的标题行以及随后的定义体组成的。
Python不允许在函数未声明之前,对其进行引用或者调用。但Python不会有前向引用的问题,只要函数在调用前都已定义。
(4)函数属性
你可以获得每个pyhon模块、类和函数中任意的名字空间。可以在模块 foo和bar里都有名为 x 的一个变量,在将这两个模块导入程序后,仍然可以使用这两个变量。所以,即使在两个模块中使用了相同的变量名字,这也是安全的,因为句点属性标识对于两个模块意味了不同的命名空间。比如说,在这段代码中没有名字冲突:
import foo, bar
print foo.x + bar.x
函数属性是python另外一个使用了句点属性标识并拥有名字空间的领域。可以用句点属性标识来增加文档字串以及其他属性,接着任意地访问属性。
(5)内部/内嵌函数
在函数体内创建另外一个函数(对象)是完全合法的,这种函数叫做内部/内嵌函数。因为现在python支持静态地嵌套域,内部函数实际上很有用的。
最明显的创造内部函数的方法是在外部函数的定义体内定义函数(用 def 关键字),如:
>>> def foo():
... def bar():
... print 'bar() called'
... print 'foo() called'
... bar()
...
>>> foo()
foo() called
bar() called
(6)函数(与方法)装饰器
装饰器背后的主要动机源自python面向对象编程。装饰器是在函数调用之上的修饰,这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。
装饰器的语法以@开头,接着是装饰器函数的名字和可选的参数。紧跟着装饰器声明的是被修饰的函数和装饰函数的可选参数。装饰器定义:
@decorator(dec_opt_args)
def func2Bdecorated(func_opt_args):
...
比如类中定义静态方法:
class MyClass(object):
def staticFoo():
...
staticFoo = staticmethod(staticFoo)
...
上述定义了叫staticFoo()的方法,现在因为打算让它成为静态方法,省去它的 self 参数。接着用staticmethod()内建函数来将这个函数“转化”为静态方法,但是在def staticFoo()后跟着staticFoo = staticmethod (sta- ticFoo)显得有多么的臃肿。使用装饰器,现在可以用如下代码替换掉上面的:
class MyClass(object):
@staticmethod
def staticFoo():
...
此外,装饰器可以如函数调用一样“堆叠“起来,这里有一个更加普遍的例子,使用了多个装饰器:
deco2
@deco1
def func(arg1, arg2, ...): pass
等价于:
def func(arg1, arg2, ...): pass
func = deco2(deco1(func))
有参数和无参数的装饰器
没有参数的情况:
@deco
def foo(): pass
等价于:foo = deco(foo)
带参数的装饰器 decomaker():
@decomaker(deco_args)
def foo(): pass
...
等价于:foo = decomaker(deco_args)(foo),其中decomaker()用deco_args做了些事并返回函数对象,而该函数对象正是以foo作为其参数的装饰器。
这里有一个含有多个装饰器的例子,其中的一个装饰器带有一个参数:
@deco1(deco_arg)
@deco2
def func(): pass
等价于:func = deco1(deco_arg)(deco2(func))
什么是装饰器
装饰器实际就是函数。一般说来,当包装一个函数的时候,最终会调用它。可以考虑在装饰器中置入通用功能的代码来降低程序复杂度,例如引入日志、增加计时逻辑来检测性能、给函数加入事务的能力等。对于用python创建企业级应用,支持装饰器的特性是非常重要的。
修饰符举例
通过显示函数执行的时间"装饰"了一个(没有用的)函数,是一个"时戳装饰"来真正地了解装饰器是如何工作的。
#!/usr/bin/env python
from time import ctime, sleep
def tsfunc(func):
def wrappedFunc():
print '[%s] %s() called' % (
ctime(), func.__name__)
return func()
return wrappedFunc
@tsfunc
def foo():
pass
foo()
sleep(4)
for i in range(2):
sleep(1)
foo()
运行结果:
[root@localhost alt]$ python deco.py
[Mon Mar 9 22:17:48 2015] foo() called
[Mon Mar 9 22:17:53 2015] foo() called
[Mon Mar 9 22:17:54 2015] foo() called
这个装饰器(以及闭包)示范表明装饰器仅仅是用来“装饰“(或者修饰)函数的包装,返回一个修改后的函数对象,将其重新赋值原来的标识符,并永久失去对原始函数对象的访问。
函数立刻被调用,第一次调用后,调用函数的第二个时间点应该为 5(4+1),第三次的时间应该大约为之后的 1 秒。
4、传递函数
当学习一门如C的语言时,函数指针的概念是一个高级话题。但是对于函数就像其他对象的python来说就不是那么回事了。函数是可以被引用的(访问或者以其他变量作为其别名),也作为参数传入函数以及作为列表和字典等等容器对象的元素。
函数有一个独一无二的特征使它同其他对象区分开来,那就是函数是可调用的。
可以用其他的变量来做作为函数的别名,因为所有的对象都是通过引用来传递的,函数也不例外。当对一个变量赋值时,实际是将相同对象的引用赋值给这个变量。如果对象是函数的话,这个对象所有的别名都是可调用的。
>>> def foo():
... print 'in foo()'
...
>>> bar = foo
>>> bar()
in foo()
bar和foo引用了同一个函数对象,分清楚"foo"(函数对象的引用)和"foo()"(函数对象的调用)的区别。
甚至可以把函数作为参数传入其他函数来进行调用:
>>> def foo():
... print 'in foo()'
...
>>> def bar(argfunc):
... argfunc()
...
>>> bar(foo)
in foo()
传递和调用(内建)函数的例子,函数作为参数传递并在函数体内调用这些函数。这个脚本用传入的转换函数简单将一个序列的数转化为相同的类型,特别地test()函数传入一个内建函数int()、long()或者float()来执行转换。
#!/usr/bin/env python
def convert(func, seq):
'conv. sequence of numbers to same type'
return [func(eachNum) for eachNum in seq]
myseq = (123, 45.67, -6.2e8, 999999999L)
print convert(int, myseq)
print convert(long, myseq)
print convert(float, myseq)
[root@localhost ch11]$ python numConv.py
[123, 45, -620000000, 999999999]
[123L, 45L, -620000000L, 999999999L]
[123.0, 45.670000000000002, -620000000.0, 999999999.0]
5、形式参数
Python函数的形参集合由在调用时要传入函数的所有参数组成,这参数与函数声明中的参数列表精确的配对。这些参数包括了所有必要参数(以正确的定位顺序来传入函数的),关键字参数(以顺序或者不按顺序传入,但是带有参数列表中曾定义过的关键字),以及所有含有默认值,函数调用时不必要指定的参数。(声明函数时创建的)局部命名空间为各个参数值,创建了一个名字。一旦函
数开始执行,即能访问这个名字。
(1)位置参数
标准化参数:位置参数必须以在被调用函数中定义的准确顺序来传递。另外,没有任何默认参数的话,传入函数(调用)的参数的精确的数目必须和声明的数字一致。
对于默认参数,如果在函数调用时没有为参数提供值则使用预先定义的的默认值,这些定义在函数声明的标题行中给出。c++也支持默认参数,和python有同样的语法:参数名等号默认值。这个从句法上来表明如果没有值传递给那个参数,那么这个参数将取默认值。
Python中用默认值声明变量的语法是所有的位置参数必须出现在任何一个默认参数之前。每个默认参数都紧跟着一个用默认值的赋值语句。如果在函数调用时没有给出值,那么这个赋值就会实现。
def func(posargs, defarg1=dval1, defarg2=dval2,...):
"function_documentation_string"
function_body_suite
默认参数让程序的健壮性上升到极高的级别,因为它们补充了标准位置参数没有提供的一些灵活性,这种简洁极大的帮助了程序员。默认参数让开发者受益的地方在于,使开发者更好地控制为顾客开发的软件,当提供了默认值的时候,可以精心选择“最佳“的默认值,所以用户不需要马上面对繁琐的选项。
所有必需的参数都要在默认参数之前。为什么?简单说来就是因为它们是强制性的,但默认参数不是。从句法构成上看,对于解释器来说,如果允许混合模式,确定什么值来匹配什么参数是不可能的。如果没有按正确的顺序给出参数,就会产生一个语法错误。如果不使用默认参数,调用时可以不按顺序给出参数。然而,如果我们将默认参数引入这个等式,虽然上面的调用仍然有效,但情况就会不同,函数定义:
def net_conn(host, port=80, stype='tcp'):
net_conn_suite
所有对net_conn()有效的调用:
a、net_conn('phaze', 8000, 'udp') # no def args used
b、net_conn('kappa') # both def args used
c、net_conn('chino', stype='icmp') # use port def arg
d、net_conn(stype='udp', host='solo') # use port def arg
e、net_conn('deli', 8080) # use stype def arg
f、net_conn(port=81, host='chino') # use stype def arg
参数host是一直不变的,它必须出现在所有对net_conn()的调用中,关键字参数已经被证明能给不按顺序的位置参数提供参数,结合默认参数,它们同样也能被用于跳过缺失参数。
5、可变长度的参数
可变长度的参数列表用于函数处理可变数量参数,变长的参数在函数声明中不是显式命名的,因为参数的数目在运行时之前是未知的(甚至在运行的期间,每次函数调用的参数的数目也可能是不同的),这和常规参数(位置和默认)明显不同,常规参数都是在函数声明中命名的。由于函数调用提供了关键字以及非关键字两种参数类型,python用两种方法来支持变长参数在函数调用中使用*和**符号来指定元组和字典的元素作为非关键字以及关键字参数的方法。
(1)非关键字可变长参数(元组)
当函数被调用的时候,所有的形参(必须的和默认的)都将值赋给了在函数声明中相对应的局部变量。剩下的非关键字参数按顺序插入到一个元组中便于访问。可变长的参数元组必须在位置和默认参数之后,带元组(或者非关键字可变长参数)的函数普遍的语法如下:
def function_name([formal_args,] *vargs_tuple):
"function_documentation_string"
function_body_suite
其中星号操作符之后的形参将作为元组传递给函数,元组保存了所有传递给函数的"额外"的参数(匹配了所有位置和具名参数后剩余的)。如果没有给出额外的参数,元组为空。函数调用时给出不正确的函数参数数目,就会产生一个 TypeError异常。通过末尾增加一个可变的参数列表变量,就能处理当超出数目的参数被传入函数的情形,因为所有的额外(非关键字)参数会被添加到变量参数元组。
>>> def tupleVarArgs(arg1, arg2='defaultB', *theRest):
... 'display regular args and non-keyword variable args'
... 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(23, 4.56)
formal arg 1: 23
formal arg 2: 4.56
>>> tupleVarArgs('abc', 123, 'xyz', 456.789)
formal arg 1: abc
formal arg 2: 123
another arg: xyz
another arg: 456.789
(2)关键字变量参数(Dictionary)
在有不定数目的或者额外集合的关键字的情况中,参数被放入一个字典中,字典中键为参数名,值为相应的参数值。为什么一定要是字典呢?因为为每个参数(参数的名字和参数值)都是成对给出,用字典来保存这些参数自然就最适合不过了。
变量参数字典来应对额外关键字参数的函数定义的语法:
def function_name([formal_args,][*vargst,] **vargsd):
function_documentation_string function_body_suite
为了区分关键字参数和非关键字非正式参数,使用了双星号(**), **是被重载了的以便不与幂运算发生混淆。关键字变量参数应该为函数定义的最后一个参数,带**。
>>> def dictVarArgs(arg1, arg2='defaultB', **theRest):
... 'display 2 regular args and keyword variable args'
... print 'formal arg1:', arg1
... print 'formal arg2:', arg2
... for eachXtrArg in theRest.keys():
... print 'Xtra arg %s: %s' % (eachXtrArg, str(theRest[eachXtrArg]))
...
>>> dictVarArgs(1220, 740.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
关键字和非关键字可变长参数都有可能用在同一个函数中,须要关键字字典是最后一个参数并且非关键字元组先于它之前出现。如:
>>> 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 is: 3
additional non-keyword arg: projects
additional keyword arg 'gamble': 96
additional keyword arg 'freud': 90
(3)调用带有可变长参数对象函数
新的调用:将非关键字参数放在元组中将关键字参数放在字典中,而不是逐个列出变量参数。
>>> newfoo(2, 4, *(6, 8), **{'foo': 10, 'bar': 12})
arg1 is: 2
arg2 is: 4
additional non-keyword arg: 6
additional non-keyword arg: 8
additional keyword arg 'foo': 10
additional keyword arg 'bar': 12
新的调用:在函数调用之外来创建元组和字典,然后再次调用。
>>> aTuple = (6, 7, 8)
>>> aDict = {'z': 9}
>>> newfoo(1, 2, 3, x=4, y=5, *aTuple, **aDict)
arg1 is: 1
arg2 is: 2
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'关键字对也被包含在最终的参数列表中,而它们不是’*‘和’**‘的可变参数中的元素。
7、函数式编程
Python不是也不大可能会成为一种函数式编程语言,但是它支持许多有价值的函数式编程语言构建。也有些表现得像函数式编程机制,但是从传统上也不能被认为是函数式编程语言的构建。Python提供的以4种内建函数和 lambda 表达式的形式出现。
Python允许用lambda关键字创造匿名函数。匿名是因为不需要以标准的方式来声明,比如使用 def 语句。除非赋值给一个局部变量,这样的对象也不会在任何的名字空间内创建名字,然而作为函数,它们也能有参数。一个完整的 lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行。匿名函数的语法:lambda [arg1[, arg2, ... argN]]: expression,参数是可选的,如果使用参数,参数通常也是表达式的一部分。用合适的表达式调用一个 lambda 生成一个可以像其他函数一样使用的函数对象。
def true(): return True,使用lambda的等价表达式为:lambda :True。使用lambda 创建了一个函数(对象),但是既没有在任何地方保存它,也没有调用它。这个函数对象的引用计数在函数创建时被设置为True,但是因为没有引用保存下来,计数又回到零,然后被垃圾回收掉。为了保留住这个对象,将它保存到一个变量中,以后可以随时调用。
>>> true = lambda :True
>>> true()
True
可以把lambda表达式赋值给一个如列表和元组的数据结构。其中,基于一些输入标准,可以选择哪些函数可以执行以及参数应该是什么。先设计一个带2个数字或者字符串参数,返回数字之和或者已拼接的字符串的函数。
def add(x, y): return x + y等价于lambda x, y: x + y
>>> a = lambda x, y=2: x + y
>>> a(3)
5
>>> a(3, 5)
8
>>> b = lambda *z: z
>>> b(23, 'zyx')
(23, 'zyx')
>>> b(42)
(42,)
lambda表达式运作起来就像一个函数,当被调用时创建一个框架对象。
(2)内建函数 apply()、filter()、map()、reduce()
apply()、filter()、map()以及reduce()内建函数提供了在python中可以找到的函数式编程的特征,lambda函数可以很好的和使用了这些函数的应用程序结合起来,因为它们都带了一个可执行的函数对象,lambda表达式提供了迅速创造这些函数的机制。
函数式编程的内建函数:
使用了filer()来获得任意奇数的简短列表(随机数集合中过滤出所有的的偶数,并返回一个新创建的序列):
>>> from random import randint
>>> def odd(n):
... return n % 2
...
>>> allNums = []
>>> for eachNum in range(9):
... allNums.append(randint(1, 99))
...
>>> Result=filter(odd, allNums)
>>> print Result
[89, 25, 5, 43, 89]
第一次重构:
<span style="font-size:14px;"></span><pre name="code" class="html">>>> from random import randint
>>> allNums = []>>> for eachNum in range(9):... allNums.append(randint(1, 99))... >>> print filter(lambda n: n%2, allNums)[63, 55, 59, 27, 81, 65]
第二次重构:
>>> from random import randint
>>> allNums = []
>>> for eachNum in range(9):
... allNums.append(randint(1, 99))
...
>>> print [n for n in allNums if n%2]
[43, 39, 65, 93]
第三次重构:
>>> from random import randint as ri
>>> print [n for n in [ri(1,99) for i in range(9)] if n%2]
[3, 99, 99]
map()内建函数与 filter()相似,它也能通过函数来处理序列。它将函数调用“映射”到每个序列的元素上,并返回一个含有所有返回值的列表。map()中每个序列可以是N个对象的M个序列。
>>> map((lambda x: x+2), [0, 1, 2, 3, 4, 5])
[2, 3, 4, 5, 6, 7]
>>> map(lambda x: x**2, range(6))
[0, 1, 4, 9, 16, 25]
>>> [x**2 for x in range(6)]
[0, 1, 4, 9, 16, 25]
>>> map(lambda x, y: x + y, [1,3,5], [2,4,6])
[3, 7, 11]
>>> map(lambda x, y: (x+y, x-y), [1,3,5], [2,4,6])
[(3, -1), (7, -1), (11, -1)]
>>> map(None, [1,3,5], [2,4,6])
[(1, 2), (3, 4), (5, 6)]
reduce使用了一个二元函数(一个接收带两个值作为输入,进行了一些计算然后返回一个值作为输出)、一个序列和一个可选的初始化器。它通过取出序列的头两个元素,将他们传入二元函数来获得一个单一的值来实现,然后又用这个值和序列的下一个元素来获得又一个值,然后继续直到整个序列的内容都遍历完毕以及最后的值会被计算出来为止,如reduce(func, [1, 2, 3])=func(func(1, 2), 3)。
>>> reduce((lambda x,y: x+y), range(5))
10
reduce()函数运行了如下的算术操作,((((0 + 1) + 2) + 3) + 4)=>10。
8、变量作用域
标识符的作用域是定义为其声明在程序里的可应用范围,或者即是所说的变量可见性,可以在程序里的哪些部分去访问一个制定的标识符。变量可以是局部域或者全局域。(1)全局变量与局部变量
定义在函数内的变量有局部作用域,在一个模块中最高级别的变量有全局作用域。声明适用的程序的范围被称为了声明的作用域。在一个过程中,如果名字在过程的声明之内,它的出现即为过程的局部变量;否则的话,出现即为非局部的。全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动。当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。
python搜索标识符, 先从局部作用域开始搜索,如果在局部作用域内没有找到那个名字,那么就一定会在全局域找到这个变量否则就会被抛出NameError异常。一个变量的作用域和它寄住的名字空间相关,作用域的概念和用于找到变量的名字空间搜索顺序相关。当一个函数执行的时候,所有在局部命名空间的名字都在局部作用域内。那就是当查找一个变量的时候,第一个被搜索的名字空间。如果没有在那找到变量的话,那么就可能找到同名的全局变量。这些变量存储(搜索)在一个全局以及内建的名字空间。
仅仅通过创建一个局部变量来“隐藏“或者覆盖一个全局变量是有可能的,因为局部名字空间是首先被搜索的,存在于其局部作用域;如果找到一个名字,搜索就不会继续去寻找一个全局域的。
(2)globa语句
为了明确地引用一个已命名的全局变量,必须使用 global 语句。global 的语法:global var1[, var2[, ... varN]]]。
>>> 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)闭包
由于python的静态嵌套域,支持了多个函数嵌套级别,因此是定义内部函数变得很有用处。
如果在一个内部函数里,对外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。closures在函数式编程中是一个重要的概念,Scheme和Haskell便是函数式编程中两种。Closures从句法上看很简单(和内部函数一样简单)但是仍然很有威力。闭包将内部函数自己的代码和作用域以及外部函数的作用结合起来,Closurs 对于安装计算、隐藏状态以及在函数对象和作用域中随意地切换是很有用的。
闭包简单的例子,模拟一个计数器,通过将整数包裹为一个列表的单一元素来模拟使整数易变。
>>> def counter(start_at=0):
... count = [start_at]
... def incr():
... count[0] += 1
... return count[0]
... return incr
...
>>> countA = counter(5)
>>> print countA()
6
>>> print countA()
7
>>> countB = counter(100)
>>> print countB()
101
>>> print countA()
8
counter()将参数值赋给列表count唯一一个成员,然后定义一个incr()的内部函数,在内部使用变量count,创建了一个闭包因为它现在携带了整个counter()作用域。incr()增加了正在运行的 count 然后返回它,最后的就是counter()返回一个incr,
incr是一个(可调用的)函数对象。
使用闭包和装饰器的简单例子,用闭包将函数调用写入日志,其中 带参数的装饰器,最终决定哪一个闭包会被用的,这也是闭
包的威力的特征。
#!/usr/bin/env python
from time import time
def logged(when):
def log(f, *args, **kargs):
print """Called:
function: %s
args: %r
kargs: %r""" % (f, args, kargs)
def pre_logged(f):
def wrapper(*args, **kargs):
log(f, *args, **kargs)
return f(*args, **kargs)
return wrapper
def post_logged(f):
def wrapper(*args, **kargs):
now = time()
try:
return f(*args, **kargs)
finally:
log(f, *args, **kargs)
print " time delta: %s" % (time()-now)
return wrapper
try:
return {"pre": pre_logged,
"post": post_logged}[when]
except KeyError, e:
raise ValueError(e), 'must be "pre" or "post"'
@logged("post")
def hello(name):
print "Hello,", name
hello("World!")
其中装饰器用特殊的装饰@logged("post")将原始函数对象进行了包裹并返回这个包裹后的hello()版本;pre_logged()和post_logged()其中之一会被返回;两个*logged()函数都包括了一个名为 wrapper()的闭包,当合适将其写入日志的时候,它便会
调用目标函数。这个函数返回了包裹好的函数对象,该对象随后将被重新赋值给原始的目标函数标识符。
(4)作用域与lambda
python的lambda匿名函数遵循和标准函数一样的作用域规则,一个lambda表达式定义了新的作用域,就像函数定义。所以这个作用域除了局部lambda函数,对于程序其他部分,该作用域都是不能对进行访问的。那些声明为函数局部变量的lambda表达式在这个函数体内是可以访问的,lambda语句中的表达式有和函数相同的作用域。
>>> x = 10
>>> def foo():
... y = 5
... bar = lambda :x+y
... print bar()
...
>>> foo()
15
9、递归
如果函数包含了对其自身的调用,该函数就是递归的。如果一个新的调用能在相同过程中较早的调用结束之前开始,那么该过程就是递归的。递归广泛地应用于语言识别和使用递归函数的数学应用中。阶乘函数:N! ? factorial(N) ? 1 * 2 * 3 ... * N。
factorial(N) = N!
= N * (N-1)!
= N * (N-1) * (N-2)!
:
= N * (N-1) * (N-2) ... * 3 * 2 * 1
>>> def factorial(n):
... if n == 0 or n == 1: # 0! = 1! = 1
... return 1
... else:
... return (n * factorial(n-1))
...
>>> factorial(5)
120
10、生成器
生成器的另外一个方面甚至更加强力,协同程序的概念。协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。在有调用者和(被调用的)协同程序也有通信。什么是python式的生成器?从句法上讲,生成器是一个带yield语句的函数。一个函数或者子程序只返回一次,但一个生成器能暂停执行并返回一个中间的结果,那就是yield语句的功能,返回一个值给调用者并暂停执行。当生成器的next()方法被调用的时候,当它返回一个值以及控制给调用者时, 它会准确地从离开地方继续。
(1)简单的生成器特性
与迭代器相似,生成器以另外的方式来运作:当到达一个真正的返回或者函数结束没有更多的值返回(当调用 next()时),一个StopIteration异常就会抛出。
>>> def simpleGen():
... yield 1
... yield '2 next'
...
>>> for eachItem in simpleGen():
... print eachItem
...
1
2 next
创建一个带序列并从那个序列中返回一个随机元素的随机迭代器:
>>> def randGen(aList):
... length=len(aList)
... while length > 0:
... length -= 1
... yield aList.pop(randint(0, length))
...
>>> for item in randGen(['rock', 'paper', 'scissors']):
... print item
...
paper
scissors
rock
在面向对象编程,可以看到生成器较简单(和无限)的版本作为类的迭代器。使用生成器最好的地方就是当你正迭代穿越一个巨大的数据集合,而重复迭代这个数据集合是一个很麻烦的事,比如一个巨大的磁盘文件或者一个复杂的数据库查询。对于每行的数据,你希望执行非元素的操作以及处理,但当正指向和迭代过它的时候,你“不想失去你的地盘“。
(2) 加强的生成器特性
在python2.5中,一些加强特性加入到生成器中,所以除了next()来获得下个生成的值,用户可以将值回送给生成器[send()],在生成器中抛出异常以及要求生成器退出[close()]。
由于双向的动作涉及到叫做send()的代码来向生成器发送值(以及生成器返回的值发送回来),现在 yield 语句必须是一个表达式,因为当回到生成器中继续执行的时候,或许正在接收一个进入的对象。
>>> def counter(start_at=0):
... count = start_at
... while True:
... val = (yield count)
... if val is not None:
... count = val
... else:
... count += 1
...
>>> count = counter(5)
>>> count.next()
5
>>> count.next()
6
>>> count.next()
7
>>> count.close()
>>> count.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
生成器带有一个初始化值,对每次对生成器[next()]调用以 1 累加计数。用户已可以选择重置这个值,如果非常想要用新的值来调用send()不是调用next(),这个生成器是永远运行的,所以如果想要终结它,调用 close()方法。
三、总结
(1)Python中函数的使用非常重要,变长参数的处理有利于处理不确定长度的参数。(2)Python的新特性如生成器可以参考相应的手册文档。
(3)若有不足,请留言,在此先感谢!
最后
以上就是敏感黄蜂为你收集整理的Python编程基础之十函数和函数式编程 一、简介 二、详解 三、总结的全部内容,希望文章能够帮你解决Python编程基础之十函数和函数式编程 一、简介 二、详解 三、总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复