python函数

函数

函数定义

定义函数使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后在缩进块中编写函数体,函数的返回值用return语句返回。

1
2
def function(arg1,arg2,...):
pass

pass表示什么也不干。一般用作占位符,比如还没想好代码怎么写,可以先写pass让代码可以运行。
return返回结果。若没有return函数也会返回结果,只是返回结果为Nonereturn None可以简写为return
exit(n)表示强行退出。

函数参数

位置参数

对于下面计算任意数的任意次方的函数

1
2
3
4
5
6
7
8
9
10
11
>>> def power(x, n):
... s = 1
... while n > 0:
... n = n - 1
... s = s * x
... return s
...
>>> power(5,2)
25
>>> power(2,5)
32

其中的参数xn都是位置参数,也就是调用函数时,传入的参数按照先后顺序依次赋值给xn

默认参数

对于上面的power(x, n)函数,当调用的时候只输入1个参数会如何呢?

1
2
3
4
>>> power(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'

可见少参数是不行的。此时可以通过默认参数简化问题。

1
2
3
4
5
6
7
8
9
10
11
>>> def power(x, n=2):    #设置n默认值为2
... s = 1
... while n > 0:
... n = n - 1
... s = s * x
... return s
...
>>> power(5) #此时相当于power(5,2)
25
>>> power(2,5) #当需要计算n不是2的情况时,可以自己输入。
32

设置默认参数时
必选参数在前,默认参数在后,否则python编辑器报错。
函数有多个参数时,变化大的参数放前面,变化小的参数放后面。变化小的参数可以作为默认参数。
有多个默认参数时,调用的时候既可以按照顺序提供默认参数,也可以不按顺序提供部分默认参数。当不按顺序提供默认参数时,需要把参数名写上。
默认参数必须指向不变的参数。
不可变类型:整数、字符串、元组。

1
变量赋值a=5 后再赋值a=10,这里实际是新生成一个int值对象10,再让a指向它,而5被丢弃。不是改变a的值,相当于重新生成了a。

可变类型: 列表、字典。

1
变量赋值la=[1,2,3,4]后再赋值la[2]=5 则是修改list la的第三个元素值,本身la没动,只是其内部的部分值被修改了。

使用可变类型作为默认参数的坑

1
2
3
4
def f(x,l=[]):
for i in range(x):
l.append(i*i)
print(l)

对于上面的函数,我们看下不同情况下调用的结果

1
2
3
4
5
6
7
8
9
10
11
12
>>> f(2)    #只传入第一个参数,列表使用默认值
[0, 1]
>>> f(3, [3, 2, 1]) #传入所有参数,不使用默认值
[3, 2, 1, 0, 1, 4]
>>> f(3) #列表使用默认值,此时结果为f(2)得到的列表上追加,而不是在空列表追加
[0, 1, 0, 1, 4]
>>> f(3, [3, 2, 1]) #列表不适用默认值,结果和预期一致
[3, 2, 1, 0, 1, 4]
>>> f(4) #列表使用默认值,此时结果为在f(3)结果的列表追加
[0, 1, 0, 1, 4, 0, 1, 4, 9]
>>> f(4, [3, 2, 1]) #列表不使用默认值,结果和预期一致
[3, 2, 1, 0, 1, 4, 9]

原因:python的函数在定义的时候,默认参数L已经被计算出来了,就是[],也就是变量l指向对象[]。每次调用函数f()的时候,假如改变了l的内容,那么下次调用时默认参数l的内容就变了,不再是默认的[]

可变参数

可变参数就是参数个数是可变的,可以是1个、2个到任意个,也可以是0个。
如计算传入数字的平方和。由于参数个数不一定,可以通过传入列表或者元组。

1
2
3
4
5
6
7
8
>>> def calc(numbers):
... sum = 0
... for n in numbers:
... sum = sum + n * n
... return sum
...
>>> calc([1, 2, 3])
14

但是调用的时候需要生成列表或者元组。此时可以使用可变参数方式简化。
定义可变参数就是在参数前添加一个*号。

1
2
3
4
5
6
7
8
9
10
>>> def calc(*numbers):
... sum = 0
... for n in numbers:
... sum = sum + n * n
... return sum
...
>>> calc(0)
0
>>> calc(1, 2, 3)
14

python允许在列表或者元组前加一个*号,把列表或者元组的元素编程可变参数传进去。

1
2
3
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

上面的*nums表示把列表nums中的所有元素按照可变参数传进去。

关键字参数

可变函数允许传入任意个参数,这些可变参数在函数调用时自动组装成一个元组。
关键字参数允许传入任意个含参数名的参数,这些关键字在函数内部自动组装成一个字典。

1
2
3
4
5
>>> def person(name, age, **kw):    #kw就是关键字参数
... print('name:', name, 'age:', age, 'other:', kw)
...
>>> person('xiaohh', 30, city='beijing')
name: xiaohh age: 30 other: {'city': 'beijing'}

命名关键字参数

对于关键字参数,调用者可以传入任意不受限制的关键字参数。如果要限制关键字参数的名字,就可以用命名关键字参数。
命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。

1
2
3
4
5
>>> def person(name, age, *, city, job):    #星号前面的name和age属于位置参数
... print(name, age, city, job)
...
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*。命名关键字参数必须传入参数名,若没有传入参数名调用将报错。

1
2
3
4
5
6
7
8
9
>>> def person(name, age, *args, city, job):
... print(name, age, args, city, job)
...
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 () Beijing Engineer
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'

组合参数

定义函数时可以选用必选参数、默认参数、关键字参数和命名关键字参数,这5种参数可以组合使用。但是,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
调用函数的时候,python解释器会自动按照参数位置和参数名称把对应的参数传进去。

1
2
3
4
5
6
7
8
9
>>> def f1(a, b, c=0, *args, **kw):
... print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
...
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

通过元组或者列表也可以调用上述函数

1
2
3
4
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

总结

默认参数一定要使用不可变对象,若使用可变对象可能出现逻辑错误。
*args是可变参数,args接受的是一个元组。
**kw是关键字参数,kw接受的是一个字典。
使用*args**kw是习惯写法,也可以使用其他参数名,但建议使用习惯写法。
可变参数既可以直接传入,也可以先组装列表或者元组,再通过*args传入。
关键字参数是既可以直接输入,也可以先组装字典,再通过**kw传入。

匿名函数

匿名函数就是没有名字。python使用lambda创建匿名函数。

  • lambda函数是一种快速定义单行的最小函数,可以用在任何需要函数的地方。
  • lambda只是一个表达式,函数体比def简单很多。
  • lambda函数拥有自己的命名空间,且不能访问自己参数列表之外的参数。

语法格式如下

lambda [arg1 [,arg2,…..argn]]:expression

示例

1
2
3
4
5
>>> sum = lambda arg1, arg2: arg1 + arg2;
>>> sum(20,20)
40
>>> sum(10,10)
20

递归函数

在函数内部可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
举个例子,计算阶乘n! = 1 x 2 x 3 x...x n,用函数fact(n)表示,那么
fact(n) = n! = 1 x 2 x 3 x …x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以fact(n)可以表示为n x fact(n-1),只有 n = 1时需要特殊处理。
那么这个函数可以写成

1
2
3
4
def fact(n):
if n == 1:
return 1
return n * fact(n - 1)

比较典型的使用递归函数处理的问题,除了阶乘之外还有汉诺塔问题。
具体处理及解决办法见python处理汉诺塔问题

高阶函数

map()

map(func, *iterables)
第一个参数为函数,第二个参数为可迭代对象。返回值为map对象。
函数作用是将可迭代对象的每个元素作为参数传递给函数进行计算。

1
2
3
4
5
6
def add(x):    #定义函数
return x + x

result = map(add, [1, 2, 3, 4]) #将列表中的每个参数传入
for i in result: #由于返回值为map对象,通过循环读出
print(i)

执行结果为

1
2
3
4
2
4
6
8

reduce()

使用该函数首先需要from functools import reduce
reduce(function, sequence[, initial])
第一个参数为函数,第二个参数为序列,第三个参数可选。
函数作用:序列中的元素从左向右传递给函数进行计算,第一次传递序列前2个元素,其通过函数的计算结果和第三个元素作为第二次传递参数,以此类推。要求函数参数个数为2个。

1
2
3
4
5
def add(x, y):
return x + y

result = reduce(add, [1, 2, 3])
print( result)

上述语句和下面语句结果一直,均显示计算结果为6

1
print(reduce(lambda x, y: x+y, [1, 2, 3]))

其计算方式为先传递1和2至函数,计算(1+2),得到的结果是3,再和序列下一个值作为参数传递3+3

1
print(reduce(lambda x, y: x*y, [1, 2, 3]))  #输出结果为6

其计算方式为先传递1和2至函数,计算(1*2),得到的结果是2,再和序列下一个值作为参数传递2*3

filter()

filter(function or None, iterable) 返回为filter对象
第一个参数为函数,也可为空。第二个参数为可迭代对象。
函数返回可迭代对象中的满足函数要求的元素。若函数不存在则返回可迭代对象中为True的元素。

1
2
3
s = filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5])
print(s) #输出结果为<filter object at 0x00000202A7D72828>
print(list(s)) #输出结果为[2, 4]

sorted()

sorted(iterable, key, reverse) 返回值为新的列表
第一个参数是可迭代对象;第二个参数为排序对象,第三个为布尔值,True为反序,False为正序,默认为False。

1
2
3
4
list1 = ['d', 'a', 'c', 'b']
print(sorted(list1)) #输出结果为['a', 'b', 'c', 'd']
print(sorted(list1,reverse=True)) #输出结果为['d', 'c', 'b', 'a']
print(list1) #输出结果为['d', 'a', 'c', 'b']

可以排序并不改变列表本身,而是返回一个新的列表对象。
对于字典,默认会按照key进行排序,若需要指定排序的对象,可以使用下面方法。

1
2
m = dict(a=1, d=20, c=10, e=400)
print(sorted(m.items(), key=lambda d: d[1], reverse=True)) #输出结果为[('e', 400), ('d', 20), ('c', 10), ('a', 1)]

练习题目:对/etc/passwd文件按照用户UID进行排序。
原始文件内容,其中第三列为UID
python-sort-source
示例代码

1
2
3
4
5
6
7
import codecs    #此处codecs模块为编码自动转换,防止乱码
with codecs.open("passswd.txt", "r") as result:
passwd = sorted(result.readlines(), key=lambda item : int(item.split(":")[2]))
print(passwd)

with codecs.open("passwd1.txt", "w") as f:
f.writelines(passwd)

print(passwd)输出结果
python-sort-print
排序后输出的新文件内容
python-sorted

列表生成式

列表生成式简单强大,用来快速生成满足条件的列表。
[exp for val in collection if condition]
exp表示表达式,后面跟for循环,还可以有if条按键判断。
要生成[1x1, 2x2, 3x3,...,10x10],使用普通for循环如下

1
2
3
4
moon = []
for i in range(1, 11):
moon.append(i*i)
print(moon) #输出结果为[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

使用列表生成式可以一行语句就生成

1
print([x * x for x in range(1, 11)])    # 输出结果为[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

上面结果为1-10的所有数的平方,要是想只输出偶数的平方就可以使用if条件

1
print([x * x for x in range(1, 11) if x % 2 == 0])    #输出结果为[4, 16, 36, 64, 100]

列表生成式也可以同时使用2个或者多个变量,例如针对dict

1
2
d = {"x": "A", "y": "B", "z": "D"}
print([k + "=" + v for k, v in d.items()]) #输出结果为['x=A', 'y=B', 'z=D']

生成器

列表生成式是根据规则一次性将列表中的元素全部生成。当列表中元素过多但我们又只需要访问列表前几个元素的时候,使用列表生成式就很浪费。这时我们可以使用生成器,也就是按照规则边循环边计算,而不是一次性生成列表。这种一边循环一遍计算的机制,也就是生成器(generator)。 生成方式有2种,分别是列表生成式中的[]改成()yield

(exp for val in collection if condition)

以前面生成[1x1, 2x2, 3x3,...,10x10]为例,改用生成器

1
2
g = (x * x for x in range(1, 11))
print(g) #输出结果为<generator object <genexpr> at 0x000001FB49446FC0>

此时生成的为生成器对象,那么我们如何访问生成器中的元素呢?
要是一个接一个访问可以使用next()方法

1
2
3
print(next(g))    #输出结果为1
print(next(g)) #输出结果为4
print(next(g)) #输出结果为9

另外在遇到next()函数时需要注意,当调用next()之后再次调用生成器对象时,生成器对象将从下一个元素开始。

1
2
3
4
5
g = (x for x in range(1, 11) if x % 2 == 1)
print(next(g))
print("####")
for i in g:
print(i)

输出结果

1
2
3
4
5
6
1
####
3 #此处从3开始,而不是从1开始
5
7
9

其实生成器也是可迭代对象(iterator),也可以使用for循环来访问

1
2
3
g = (x * x for x in range(1, 11))
for i in g:
print(i)

输出结果为

1
2
3
4
5
1
4
...(略)
81
100

yield

如果一个函数中包含关键字yield,那么这个函数就是一个生成器。
普通函数和含有yield关键字的生成器区别在于执行流程不一样。
函数是顺序执行,遇到return语句或最后一行函数语句就返回。
变成生成器的函数,遇到yield语句时返回,再次执行的时候从上次返回的yield语句的下一行继续执行。
对于下面的函数

1
2
3
4
5
6
7
def odd():
print("step 1")
yield 1
print("step 2")
yield 3
print("step 3")
yield 5

调用的时候,输出结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> n = odd()
>>> print(next(n)) #输出第一步,返回1
step 1
1
>>> print(next(n)) #继续从yield 1下一行开始执行
step 2
3
>>> print(next(n)) #继续从yield 3下一行开始执行
step 3
5
>>> print(next(n)) #当最后一个元素结束时会抛出StopIteration异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

关于生成器的练习题目,点击生成器练习题目

总结

生成器是在for循环过程中不断计算出下一个元素,并在适当的条件结束for循环。
对于函数改成的生成器,遇到return语句或者执行到函数体最后一句,就是结束生成器的指令,for循环也就结束了。
普通函数调用直接返回结果。
生成器调用返回一个生成器对象。

Recommended Posts