Python函数

简介

Python中的函数作为Python中的一个类对象,功能强大,灵活度高。本文从函数基本属性入手,谈一谈Python中的函数

Python函数的基本属性

Python中函数的属性可以使用dir函数进行查看,例如对于下面定义的cubic函数

1
2
3
4
5
6
7
8
def cubic(x):
"""return cubic of given number"""
return x**3

>>> cubic
<function cubic at 0x0000000002E23F98>
>>> dir(cubic)
['__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']

上面列出了cubic函数所有的属性,下面谈几个重要的属性

__doc__ :返回制定函数的文档字符串

对于上面的cubic函数,查看它的doc属性如下:

1
2
>>> cubic.__doc__
'return cubic of x'

__module__ :返回函数所在模块的名字

1
2
>>> cubic.__module__
'__main__'

__name__ :返回函数的名字

1
2
>>> cubic.__name__
'cubic'

func_globals:返回一个包含函数全局变量的字典引用

1
2
>>> cubic.func_globals
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'cubic': <function cubic at 0x0000000002E23EB8>, '__doc__': None, '__package__': None}

func_defaults:返回一个包含默认参数值的元组

1
2
>>> cubic.func_defaults
>>>

上面没有显示任何信息,因为cubic函数不包含默认参数,后面再详细讨论该属性

func_closure: 返回一个胞体元组,胞体中包含了对函数自由变量的绑定,这属于闭包的范畴

1
2
>>> cubic.func_closure
>>>

可以看到,上面的例子过于简单,也没有涉及到闭包,后面详细探讨

函数迭代

函数可以作为参数传递给其他函数,以其他函数作为参数的函数成为高阶函数,在Python中有一些自带的函数就具有这样的特点。如map函数,map函数接受一个函数和一个可迭代对象作为它的参数,将函数应用于迭代对象中的每一项,并将作用后的迭代对象作为列表返回。例如还以前面举的cubic函数为例,看一下map函数的作用结果

1
2
>>> map(cubic,range(12))
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331]

函数嵌套定义

可以在函数的内部定义函数,也可以将函数赋值给变量,下面的例子

1
2
3
4
5
6
7
8
9
def outer():
outer_var="in outer function"
def inner():
return outer_var
return inner
>>> function=outer()
>>> function
<function inner at 0x0000000002E23B38>
>>>

上面的例子中,外层函数返回内部函数并将其赋值给变量,现在function所引用的是内部函数的一个实例对象。可以像调用普通函数一样调用它,例如下面:

1
2
3
>>> function()
'in outer function '
>>>

可以正确调用inner函数

函数的定义

函数定义的执行会绑定当前本地名称空间中的函数名到一个函数对象,该对象是一个对函数中可执行代码的包装器。该对象包含了一个对全局名称空间(有别于当前名称空间,后者是该函数调用的全局名称空间)的引用,函数只有在调用时才会真正执行

函数的参数

Python中的函数支持固定参数和可变参数,后者可以是列表、字典或者其他迭代对象。

默认参数

使用默认参数时,用户可以省去一些重复输入,同时代码本身健壮性可以显著增强。下面是一个函数默认参数的函数的例子

1
2
3
4
5
6
def repeat(str,times=3):
print str*times
>>> repeat("haha")
hahahahahaha
>>> repeat("haha",5)
hahahahahahahahahaha

可以看到,当没有给times传入值时,就会使用默认的参数值;而当指定times值之后,就会使用指定值。

注意:当默认参数为可变参数时要格外小心,由于函数定义只执行一次,因此相同的可变结构会用于后续所有的函数调用中。例如

1
2
3
4
5
6
7
8
9
def mutable_show(args,default_args=[]):
default_args.append(args)
print "default_args:{}".format(default_args)

>>> mutable_show("once")
default_args:['once']
>>> mutable_show("twice")
default_args:['once', 'twice']
>>>

上面的代码中两次输入的args被先后加到了default_args中,所以使用可变就够作为默认参数时要小心出错。

关键字参数

采用”arg=value”的形式调用函数称之为关键词参数,注意这里指的是调用的时候采用”arg=value”的形式而非”value”这样的形式。
例如对于上面的repeat函数中,可以采用下面的集中方法来调用该函数

1
2
3
4
5
6
>>> repeat(str="hehe")
hehehehehehe
>>> repeat(times=5,str="heihei")
heiheiheiheiheiheiheiheiheihei
>>> repeat(str="heihei",times=5)
heiheiheiheiheiheiheiheiheihei

上面代码中第一个参数使用了关键字参数的方式,第二个参数采用默认参数值,函数正确执行。第二个和第三个参数顺序颠倒但是都可以正确执行,说明关键字参数的先后顺序不影响函数执行。但要注意关键字参数不能放在非关键字参数的前面,例如下面的方式就会出错

1
2
3
>>> repeat(str="heihei",5)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg

上面的第二个参数是非关键字参数,它必须放在关键字参数的前面。但是如果改成下面这样也是不对的。

1
2
3
4
>>> repeat(5,str="heiheihei")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: repeat() got multiple values for keyword argument 'str'

因为按照函数参数匹配的方式,第一个参数5被复制给形参str(专业的说法是:这里的5是位置参数,会被赋值给相应位置的形参),而后面再次给str赋值,给同一个参数多次赋值是不可以的。

任意的参数列表

Python中函数可以接受以元组形式传递的任意数量的参数,下面是一个相应的例子

1
2
3
4
5
def mutable_show(separator,*args):
print separator.join(args)
>>> mutable_show("",'haha','hehe','heihei')
hahaheheheihei
>>>

上面的’haha’,’hehe’,’heihei’被捆绑为一个元组,可以通过args访问该元组。

解包函数参数

函数调用的参数可以是元组、列表或者字典,这是需要使用*或者**将这些参数解包到函数内部。首先看一个基本的例子:

1
2
3
4
5
6
7
8
def print_args(a,b,c):
print a
print b
print c
>>> print_args(1,2,3)
1
2
3

如果参数本来就在一个list中,可以将list作为参数传递进去,像下面这样:

1
2
3
4
5
>>> a=range(1,4)
>>> print_args(*a)
1
2
3

类似的,对于字典形式的参数可以使用**的方式传递给函数,像下面的例子

1
2
3
4
5
6
def print_dict(var1,var2,var3="hehe"):
print var2,var3,var1
>>> d={'var1':"wenqiang","var2":"My name ","var3":"is "}
>>> print_dict(**d)
My name is wenqiang
>>>

*和**应用场景:参数个数不定时

有的时候不知道调用的时候参数的个数,这时可以使用*和**作为参数。
对于下面的形式

1
print_args(arg,*args,**kwargs)

后面的args和kwargs是可选的,但要注意函数定义时*args必须位于**kwargs的前面
下面是一个使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def print_args(arg,*args,**kwargs):
print arg
for item in args:
print args
for item in kwargs:
print item,kwargs[item]
>>> args=range(1,6)
>>> kwargs={'name':"program",'language':"Python",'content':"function"}
>>> print_args("test",*args,**kwargs)
test
(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
content function
name program
language Python

匿名函数

可以使用lambda创建匿名函数,lambda表达式返回函数对象,该函数可以想普通函数一样被调用,见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>>cubic=lambda x: x**3
>>> for i in range(10):
... cubic(i)
...
0
1
8
27
64
125
216
343
512
729
>>>

函数的嵌套和闭包

在函数内部定义函数形成了函数的嵌套,像前面的例子

1
2
3
4
5
def outer():
outer_var="in outer function"
def inner():
return outer_var
return inner

每次调用外部函数时,都会创建一个内部函数的实例并返回到外部(原本inner函数作用域只在outer函数内部,通过返回内部函数inner可以在其他地方调用该函数),因为每次执行调用代码时inner函数都执行函数定义,但不会执行其函数体,只有当调用inner函数时函数体才会执行。
inner函数可以访问outer函数的变量,这是Python允许的操作,像之前举的例子,inner函数调用后会返回outer函数中定义的变量。对于嵌套函数,当内部函数引用外部函数的变量时,我们认为嵌套函数相对于引用变量是封闭的。对于这种情形,我们可以使用函数属性__closure__来访问该变量,如下所示:

1
2
3
4
5
6
7
8
9
>>> a=outer()
>>> a
<function inner at 0x0000000002E23EB8>
>>> va=a.__closure__
>>> va
(<cell at 0x0000000002E3E948: str object at 0x0000000002E4F030>,)
>>> va[0].cell_contents
'in outer function '
>>>

上面的例子中,使用__closure__属性得到的是一个包含全部闭包变量的元组,使用cell_contents方法可以查看变量的值

注意:Python 3之前的版本中指向不可变类型的变量无法在闭包中反弹。下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def counter():
count = 0
def c():
count += 1
return count
return c

>>> a=counter()
>>> a()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
UnboundLocalError: local variable 'count' referenced before assignment
>>>

一种解决办法是用可变类型代替上面的变量,例如:

1
2
3
4
5
6
7
8
9
10
11
12
def counter():
count = [0]
def c():
count[0]+= 1
return count[0]
return c

>>> a=counter()
>>> a()
1
>>> a()
2

在Python 3中引入了nonlocal关键字解决了上面的问题,在Python 3中可以如下定义:

1
2
3
4
5
6
7
def counter():
count = 0
def c():
nonlocal count
count += 1
return count
return c

内建函数 apply() filter() map() reduce()

这些函数实现了函数式编程语言中的一些功能结合lambda表示式可以用很精简的代码完成很丰富的功能。这里只简要的提一下,后面会有专门的博文讲述这几个函数。

apply()函数

函数完整的定义如下:

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

其中第一个参数是一个函数,第二个可选参数为非关键字参数,第三个可选参数为关键字参数。apply的返回值是func执行后的返回值。说的直白一些就是相当于func([nkw][,kw])因此使用这个函数未必会简化代码量,所以在1.6之后的版本中已经不推荐使用,《Python核心编程》中说这个函数会逐步淘汰掉。
下面举个例子对比一下用和不用这个函数的效果

1
2
3
4
5
6
7
def repeat(str,times):
print str*times

>>> repeat("hehe",times=6)
hehehehehehehehehehehehe
>>> apply(repeat,("hehe",),{"times":6})
hehehehehehehehehehehehe

上面用两种方式对repeat函数进行了相同目的的调用,第一种是常规的函数调用形式,传入的第一个参数是位置参数,第二个是关键字参数,调用格式简洁明了;第二种方式采用apply函数的形式,第一个参数是函数名,第二个参数是位置参数,这里需要以元组的形式传入,第三个参数是关键字参数,传入的关键字参数要以字典的形式传进来。可以看到利用apply反而会使问题变得更加复杂和繁琐。

filter函数

filter函数的定义形式如下

1
filter(func,seq)

func是一个布尔函数,即返回值为bool值(True或者False);seq是一个序列。filter函数会将seq中的每个元素作为参数传进func函数中,并且将func返回值为True的元素加进最终要返回的序列中,最终返回由所有满足func返回值为True的元素组成的序列。
下面举一个例子

1
2
3
4
5
def func(num):
return num%2==0

>>> filter(func,range(10))
[0, 2, 4, 6, 8]

上面定义的函数当num为偶数时返回True,否则返回false。通过filter调用,最终返回使得func值为True的所有元素,即序列中所有的偶数。其实在Python中引入list comprehension后利用list comprehension的特性来实现这种功能同样十分轻松,甚至后者更为便捷。假如想用list comprehension来实现这样的功能,只需要像下面这样

1
2
>>> [i for i in range(10) if i%2==0]
[0, 2, 4, 6, 8]

所以自从引入了列表推导后,函数式编程的函数的作用就变小了。

map函数

map函数的定义如下

1
map(func,seq1[,seq2...])

如果seq只有一个,则map函数的功能是把seq中的每个元素逐一代入到func中,并将作用后的结果以列表的形式返回。当seq不止一个时,即参数为seq1,seq2,……,此时map的操作是将seq1和seq2,……中对应位置的元素提取出来供func调用,并将调用后的结果加入到返回值列表。例如有两个序列,那么首先各取出第一个元素,seq1[0]和seq2[0],并将这两个值作为参数注入到函数func中,得到一个结果,放到结果列表中,然后再取seq1[1]和seq2[1],这样一直进行到两个序列元素都迭代过为止。如果两个序列长度不同,则较短的序列会用None值补全。下面举一个例子

1
2
3
4
5
def func(x,y):
return y**x

>>> map(func,range(10),range(15,25))
[1, 16, 289, 5832, 130321, 3200000, 85766121, 2494357888L, 78310985281L, 2641807540224L]

如果func为None(注意map里的func必须明确指定,可以明确指定为None),那么map此时的作用和zip很像,都是将后面的序列按位置匹配到元组中,不过zip会以序列中长度最短的为准,一旦有序列到末位,迭代就结束了。看下面的代码演示

1
2
3
4
5
6
>>> map(None,range(10),range(15,25))
[(0, 15), (1, 16), (2, 17), (3, 18), (4, 19), (5, 20), (6, 21), (7, 22), (8, 23), (9, 24)]
>>> map(None,range(10),range(15,26))
[(0, 15), (1, 16), (2, 17), (3, 18), (4, 19), (5, 20), (6, 21), (7, 22), (8, 23), (9, 24), (None, 25)]
>>> zip(range(10),range(15,26))
[(0, 15), (1, 16), (2, 17), (3, 18), (4, 19), (5, 20), (6, 21), (7, 22), (8, 23), (9, 24)]

可以看到如果后面的序列长度相同且func为None,则map函数作用和zip完全相同。

reduce函数

reduce函数定义如下

1
reduce(func,seq[,init])

func一定要是个二元函数(即接收两个参数),seq为输入序列,这里面的init是可选的。如果没有提供init参数,reduce的作用过程是,首先将seq中的前两个元素代入到函数func中,将函数的返回值和seq中的第三个元素作为参数如果到func中,再将执行后函数的返回结果和seq中的第四个元素作为参数传入到函数func中……依此类推,直到将序列迭代完。如果提供了init参数,reduce作用的区别仅在于初始时将seq中的第一个元素和init作为参数传入到func中,而不是seq的前两个元素,后面的操作完全相同。下面举个例子

1
2
3
4
def multi(x,y):
return x*y
>>> reduce(multi,range(1,7))
720

上面的例子中没有指定初始化参数,因此函数作用过程是先把序列中的前两个元素(1和2)作为参数传入到函数multi中,返回值2和序列的下一个元素3作为参数再传入multi中,得到6……依此类推得到720。

文章目录
  1. 1. 简介
  2. 2. Python函数的基本属性
    1. 2.1. __doc__ :返回制定函数的文档字符串
    2. 2.2. __module__ :返回函数所在模块的名字
    3. 2.3. __name__ :返回函数的名字
    4. 2.4. func_globals:返回一个包含函数全局变量的字典引用
    5. 2.5. func_defaults:返回一个包含默认参数值的元组
    6. 2.6. func_closure: 返回一个胞体元组,胞体中包含了对函数自由变量的绑定,这属于闭包的范畴
  3. 3. 函数迭代
  4. 4. 函数嵌套定义
  5. 5. 函数的定义
  6. 6. 函数的参数
    1. 6.1. 默认参数
    2. 6.2. 关键字参数
    3. 6.3. 任意的参数列表
  7. 7. 解包函数参数
  8. 8. *和**应用场景:参数个数不定时
  9. 9. 匿名函数
  10. 10. 函数的嵌套和闭包
  11. 11. 内建函数 apply() filter() map() reduce()
    1. 11.1. apply()函数
    2. 11.2. filter函数
    3. 11.3. map函数
    4. 11.4. reduce函数
,