写在篇前
函数能提高应用的模块性,和代码的重复利用率,是编程必须具备的基本抽象能力。python函数更是奇妙灵活,与很多特性值得探讨,本篇文章就来详细看看python 函数那些巧妙之处。首先,在篇前简单说说python函数的形式。比如说我们要实现一个函数:计算一个数m的n次方,那么函数可以定义如下:
def exponentiation(m, n):
return m^n
# def 是函数定义关键字
# exponentiation 是函数名
# (m,n)是函数参数,后面冒号之后是函数体
# return 表示返回值,一个函数也可以没有函数体,此时返回值为None
函数参数
-
必选参数
必选参数,又称为位置参数,我们上面定义的函数
exponentiation
中m, n
就是必选参数。当调用该函数时,每一个必选参数都必须传入合适的值。 -
默认参数
修改函数
exponentiation
为:def exponentiation(m, n=2): return m^n
这样,
n
便是一个默认参数,当计算一个数的二次方时,只需要传入参数m
即可:>>> exponentiation(3) 9
需要注意的是,必选参数必须在前,默认参数在后,否则Python的解释器会报错;为了避免不必要的坑,默认参数必须指向不变对象。 另外,在函数定义的时候就会确定默认参数的值,当我们改变x的值的时候对默认参数值并没有影响,即:
>>> x = 42 >>> def spam(a, b=x): ... print(a, b) ... >>> spam(1) 1 42 >>> x = 23 >>> spam(1) 1 42 >>>
-
可变参数
可变参数是python函数灵活性的表现之一,如果有这样一个需求,编写一个函数计算若干个数的和,这时可变参数便是一种好的选择:
def calc_sum(*args): sum = 0 for arg in args: sum += arg return sum print(calc_sum(1, 2, 3)) # 可变参数可传入0个或任意个参数,在函数调用时自动组装为一个tuple nums = [1, 2, 3] print(calc_sum(*nums)) # *nums意思是将nums中的所有元素以可变参数的形式传入函数
-
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,在函数内部会自动组装为一个dict。
def print_info(pc_id, pc_name, **kwargs): print(pc_id, pc_name, kwargs, sep='\n') other_info = {'city': 'NanJing', 'company': 'APPLE'} print_info('0001', 'IMac-2018', city='NanJing', company='APPLE') print_info('0001', 'IMac-2018', **other_info) 0001 IMac-2018 {'city': 'NanJing', 'company': 'APPLE'} 0001 IMac-2018 {'city': 'NanJing', 'company': 'APPLE'}
关键字参数可以传入任意参数名,那我们可不可以限制他传入指定的参数名呢?答案是当然可以,采用命名关键字可以实现该需求。但是这里需要搞清楚一个逻辑上的问题。这里的指定的参数名要和前面的必选参数区分开来。命名关键字参数如果有默认值可以不传入,但是如果传入就只能是已经限定的关键字参数。
def print_info(pc_id, pc_name, *, city, company):
# 用一个 *,区分必选参数和关键字参数
print("pc_id: %s\tpc_name: %s" % (pc_id, pc_name), end='\t')
if city:
print('city:%s' % city, end='\t')
if company:
print('company:%s' % company)
other_info = {'city': 'NanJing', 'company': 'APPLE'}
print_info('0001', 'IMac-2018', city='NanJing', company='APPLE')
print_info('0001', 'IMac-2018', **other_info)
# 但是如果函数定义中已经有了一个可变参数,命名关键字参数就不再需要分隔符*了
# def print_info(pc_id, pc_name, *args, city, company):
-
小结
以上四种参数可以灵活的进行组合应用,需要注意的是,参数定义的顺序必须是:必选参数、默认参数、可变参数、(命名关键字参数)关键字参数。
def f(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
特殊函数
匿名函数
在python中可以通过lambda
来定义匿名函数,其返回一个函数表达式,类似于def,但是比def更轻巧,可以没有名字。
# 关键字`lambda`表示匿名函数,冒号前面的`x`,`y`表示函数参数
# 匿名函数有个限制:只能有一个表达式,不用写return,返回值就是该表达式的结果。
>>>add = lambda x,y: x+y
>>>add(2,3)
5
需要注意的是,lambda表达式中的参数x、y
等是自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。 因此在下面例子中,调用这个lambda表达式的时候,x的值是执行时的值。
>>> x = 10
>>> a = lambda y: x + y
>>> x = 20
>>> b = lambda y: x + y
>>> a(10)
30
>>> b(10)
30
高阶函数
一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order Functions)。python中有几个常用的内置高阶函数:
-
filter
filter()
函数接收两个参数,一个函数和一个序列,其中传入的函数会依次作用于序列的每一个元素,根据返回值是True或则False决定是否保留该元素。filter(lambda x: x>10, [1, 56, 3, 36, 9]) >>> g = filter(lambda x: x>10, [1, 56, 3, 36, 9]) >>> g <filter object at 0x10207b470> >>> type(g) <class 'filter'> >>> list(g) [56, 36]
-
map/reduce
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回;reduce()
把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。>>> r = map(lambda x:x*x, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> type(r) <class 'map'> >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81] from functools import reduce >>>reduce(lambda x,y: x+y, [1,2,3,4]) 10
-
sorted
用于可迭代对象的排序,如:
# 实例1 >>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]
-
返回函数
高阶函数除了可以接受函数作为参数,也可以将函数作为返回值,实现“延迟计算”。
def calc_sum(lst): def lazy_sum(): return sum(lst) return lazy_sum >>> f = calc_sum([1, 2, 3, 4]) >>>f <function calc_sum.<locals>.lazy_sum at 0x10e9bd158> >>>f() 10
偏函数
偏函数的作用是为函数某些参数设置默认值,使调用更加方便,以下会是一个好的例子:
points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
import math
def distance(p1, p2):
x1, y1 = p1
x2, y2 = p2
return math.hypot(x2 - x1, y2 - y1)
>>> pt = (4, 3)
>>> points.sort(key=partial(distance,p2=pt)) # 注意这里必须是p2=pt
>>> points
[(3, 4), (1, 2), (5, 6), (7, 8)]
回调函数
在计算机程序设计中,回调函数,是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。关于理解可以参考回调函数是什么。这里我们也给出一个例子辅助理解:
def greeting(name):
print('hello %s!' % name)
def someone_coming(callback):
name = input()
callback(name)
def main():
someone_coming(greeting)
if __name__ == '__main__':
main()
内置函数
这里确切的应该说是其他内置函数,因为上面也涉及很多内置函数,如sorted
、filter
、map
等。另外,python内置函数非常之多,如果想了解更全面的内部函数,可以参考内置函数1,内置函数2
-
zip()
用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。需要注意的是,当多个可迭代数据length不同时,取最小长度,其他忽略。
>>>a = [1,2,3] >>> b = [4,5,6] >>> c = [4,5,6,7,8] >>> zipped = zip(a,b) # 打包为元组的列表 [(1, 4), (2, 5), (3, 6)] >>> zip(a,c) # 元素个数与最短的列表一致 [(1, 4), (2, 5), (3, 6)] >>> zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式 [(1, 2, 3), (4, 5, 6)]
-
reversed()
用于反转序列,生成新的可迭代对象
>>> a = reversed(range(10)) # 传入range对象 >>> a # 类型变成迭代器 <range_iterator object at 0x035634E8> >>> list(a) [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
-
enumerate()
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
>>>seq = ['one', 'two', 'three'] >>> for i, element in enumerate(seq): ... print i, element 0 one 1 two 2 three