[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。
13. 函数
13.1 函数调用
函数名也是一个变量,这个变量指向函数体,完全可以像普通变量一样赋值。
myfunc = abs
myfunc(-10)
输出: 10
13.2 定义函数
def func1(): # 没有返回值,默认返回一个None
a = 1
def func2(): # 只有return,也会默认返回None
a = 1
return
def func3():
a = 1
return None
print("func1:{}".format(func1()))
print("func2:{}".format(func2()))
print("func3:{}".format(func3()))
输出: func1:None
func2:None
func3:None
-
函数的导入,
from myfunc import func1
,通过这样导入了myfunc.py中的fun1函数。 -
空函数, 用关键字 pass 可以作为一个占位符,即什么也不做,或是还没有考虑好如何处理的部分。
def test1(a):
if a > 100:
print("")
else:
pass
- 函数参数检查
数据类型检查可以用内置函数 isinstance()实现。
def my_abs(a):
if not isinstance(a,(int,float)):
raise TypeError("Bad type!")
else:
if a > 0:
return a
else:
return (-a)
my_abs("42r")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-83-212c736ab73f> in <module>()
----> 1 my_abs("42r")
<ipython-input-82-65b62c875de0> in my_abs(a)
1 def my_abs(a):
2 if not isinstance(a,(int,float)):
----> 3 raise TypeError("Bad type!")
4 else:
5 if a > 0:
TypeError: Bad type!
- 返回多个值
表面是返回了多个值,实际上返回的是一个包含多个值的tuple。
def myfunc2(a,b):
return -a,-b
x,y = myfunc2(1,2)
print("{},{}".format(x,y))
print(type(myfunc2(1,2)))
16. 【重要!】函数的参数
Python 的函数定义非常简单,但灵活度却非常大。除了正常定义的必选 参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义 出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
16.1 位置参数
def mypower(a,b):
result = 1
for i in range(b):
result = result * a
return result
mypower(2,5)
函数mypower中的两个参数a和b,都是位置参数,调用函数时传入的参数,按顺序赋值给a和b。
16.2 默认参数
def mypower(a,b=2):
result = 1
for i in range(b):
result = result * a
return result
mypower(2)
默认参数能够简化函数的调用,但使用的时候要注意:
- 固定参数在前,默认参数在后。
- 变化大的参数在前,变化小的参数在后。
默认参数降低了函数调用的难度,而一旦需要更复杂的调用时, 又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只 需要定义一个。
当然也可以不按参数顺序调用,但是要指定参数名。
mypower(b=3,a=2)
- 默认参数必须指向不变的对象。
def add_end(L=[]):
L.append("end")
return L
add_end([1,2,3])
add_end() # 将默认参数L增加了一次end
add_end() # 将默认参数L增加了二次end
add_end([1,2,3]) # 这时没有使用 默认参数L
add_end() # 再次使用默认参数L,增加了三次end
Python 函数在定义的时候,默认参数 L 的值就被计算出来了,即[],因 为默认参数 L 也是一个变量,它指向对象[],每次调用该函数,如果改 变了 L 的内容,则下次调用时,默认参数的内容就变了,不再是函数定 义时的[]了。
def add_end2(L=None):
if L is None:
L = []
L.append("end")
return L
add_end2()
add_end2() # 这样无论调用多少次都不会重复追加
add_end2([1,2,3,4])
16.3 可变参数
在参数中添加*表示数目可变的参数。
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。
def calc(*num):
sum = 0
for n in num:
sum = sum + n*n
return sum
calc(1,2,3,4)
如果有一个已有的list要使用这个函数的话,只需要在使用时添加*在变量参数前。
这种写法很有用,也很常见。
listA = [1,2,3,4,5]
calc(*listA)
16.4 关键字参数
有关键字的可变参数: 关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict。请看示例:
def add_info(name,gender,**kw):
print(type(kw))
print("name:",name," gender:",gender," other:",kw)
# 注意下面传入关键字参数的方式
add_info("lijun","male",age=33,tel="080")
关键字参数能够扩展函数的功能,比如只有name和gender是必选,其他是可选时,就非常方便的接收参数。
16.5 命名关键字参数
通过上面方式,能够接受任意的带key的value,但是如果需要限制某些参数时呢?可以在函数中处理,也可以使用命名关键字参数。
def add_info(name,gender,*,age=18,add):
print("name:",name," gender:",gender," age:",age,"address:",add)
add_info("lijun","male",age=33,add="kawasaki")
add_info("lijun","male",add="kawasaki")
16.5 参数组合
在 Python 中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这 5 种参数都可以组合使用,除了可变参数无法和命名关键字参数混合。
但是请注意,参数定义的顺序必须是:
必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f1(1, 2)
f1(1, 2, 3, 'a', 'b', x=99)
16.6 小结
Python 的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。 1.默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
- *args 是可变参数,args 接收的是一个 tuple;
- **kw 是关键字参数,kw 接收的是一个 dict。
另外,
-
可变参数既可以直接传入:func(1, 2, 3),又可以先组装 list 或 tuple, 再通过args 传入:func((1, 2, 3));
-
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装 dict,再通 过kw 传入:func({‘a’: 1, ‘b’: 2})。
-
使用*args 和**kw 是 Python 的习惯写法,当然也可以用其他参数名,但 最好使用习惯用法。
17. 递归函数
def fact(n):
if n == 1:
return n
return n*fact(n-1)
fact(50)
输出: 30414093201713378043612608166064768844377641568960512000000000000
- 尾递归
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。 尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 上面的 fact(n)函数由于 return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
def fact(n):
return fact_iter(n,1)
def fact_iter(num,product):
if num == 1:
return product
return fact_iter(num-1,num*product)
fact(50)
输出: 30414093201713378043612608166064768844377641568960512000000000000
但是当前python解释器没有对尾递归进行优化,仍然会发生栈溢出的问题。
18. python高级特性
python支持很多高级特性,能简化代码,在python中,代码越少开发效率越高越好。
19. 切片
- list的切片
listA = ["01lj","02wl","03uta","04utasuke"]
listA[:3] # 0,1,2 三个元素
输出: ['01lj', '02wl', '03uta']
# 反向取,倒数第三个到倒数第一个,索引的顺序仍然是下标小 到 下标 大,即从左到右。
# 只是起点变了,不需要计算list总长度,就可以反向取
listA[-3:-1]
输出: ['02wl', '03uta']
listB = list(range(20))
listB[-10:] # 倒数10个
输出: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
listB[:10:2] # 前10个,隔2个取一个
输出: [0, 2, 4, 6, 8]
listB[::3] # 所有元素,隔3个取一个
输出: [0, 3, 6, 9, 12, 15, 18]
listB[::] # 复制所有元素
输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
- tuple的切片
(0,1,2,3,4)[:3] # 取前三个,返回的仍是tuple
输出: (0, 1, 2)
- 字符串的切片
"wanglinglijun"[:3] # 字符串也是list
输出: 'wan'
20. 迭代
python中的迭代,比java等其他语言中的迭代更抽象,不仅能对list/tuple等迭代,还能对其他可迭代对象迭代,比如dict/string等。
- 对dict的迭代
对dict的key迭代
mydict = {"lj":33,"wanling":30,"uta":4,"utasuke":1}
for name in mydict:
print(name)
输出: lj
wanling
uta
utasuke
对dict的value迭代
for age in mydict.values():
print(age)
输出: 33
30
4
1
对dict的key和value同时迭代
for name,age in mydict.items():
print("{}:{}".format(name,age))
输出: lj:33
wanling:30
uta:4
utasuke:1
- 字符串的迭代
for ch in "ABCD":
print(ch)
输出:A
B
C
D
- 判断某个对象是否迭代
from collections import Iterable
isinstance([1,2],Iterable)
输出:True
isinstance("ABC",Iterable)
输出:True
isinstance(2,Iterable)
输出:False
- 如果要对list进行下标操作
for key,value in enumerate([10,20,30]):
print("{}:{}".format(key,value))
输出: 0:10
1:20
2:30
coordinate = [(1,2),(3,4),(5,6)]
for x,y in coordinate:
print("{},{}".format(x,y))
输出: 1,2
3,4
5,6
21. 列表生成式
listA = [x*x for x in range(1,11)]
listA
输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
listB = [x*x for x in range(1,11) if x%2 == 0]
listB
输出:[4, 16, 36, 64, 100]
isinstance(listB,Iterable)
输出:True
还可以进行两层排列
listC = [a+b for a in "ABC" for b in "123"]
listC
输出:['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
通过列表生成式,可以写出非常简洁的代码,比如下面是列举当前目录下所有的文件。
import os
filelist = [d for d in os.listdir("C:\TEMP")] # 一个点.表示当前目录,两个点..表示上一级目录,/表示根目录,还可以自己指定目录,如 C:\TEMP
filelist
输出:['test', 'testttt.txt']
也可以针对两个元素使用列表生成式:
mydict = {"lj":33,"wanling":30,"uta":4,"utasuke":1}
mylist = [k + ":" + str(v) for k,v in mydict.items()]
mylist
输出:['lj:33', 'wanling:30', 'uta:4', 'utasuke:1']
将所有字符串变成小写:
namelist = ["Lijun","Wangling"]
namelistLower = [name.lower() for name in namelist]
namelistLower
输出:['lijun', 'wangling']
22. 生成器
上述的生成表达式,十分方便,但是如果数据量大,就会占用大量内存,而若我们只需要使用其中的几个值,就会造成内存浪费。 通过生成器,能实现只有需要用的时候才去计算,生成器与生成表达式的唯一区别就是,生成表达式用[]表达,而生成器用()。
- 生成器方式(1)
调用生成器用next(a),如下:
gtA = (x*x for x in range(10) if x%2 == 0)
next(gtA)
next(gtA)
next(gtA)
next(gtA)
next(gtA)
输出:64
下面过程中,超出生成器上限会发生error。
next(gtA)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-59-7c335854303d> in <module>()
----> 1 next(gtA)
StopIteration:
生成器也是一种迭代器,见如下:
isinstance(gtA,Iterable)
输出:True
既然是迭代器,就可以通过for循环进行处理,这样就不会出现越界的error了。
# 注意gtA是个生成器,通过上面的next处理,已经将所有的都生成了
# 如果不再重新定义gtA,使用之前gtA的话,是无法取得元素的
gtA = (x*x for x in range(10) if x%2 == 0)
for a in gtA:
print(a)
输出: 0
4
16
36
64
- 生成器方式(2)
def fib(max):
n, a, b = 0, 0 , 1
while n < max:
yield b
a, b = b, a+b
n = n + 1
return "done"
可以看到上面的fib不是普通函数,而是generator.
x = fib(5)
isinstance(x,Iterable)
输出:True
next(x)
输出:1
next(x)
输出:1
next(x)
输出:2
next(x)
输出:3
next(x)
输出:5
在执行中,遇到yeild就中断,下次继续执行,至到yeild被循环完毕,再次执行就会出现error。
next(x) # 超出了5次
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-88-88c9b640948e> in <module>()
----> 1 next(x) # 超出了5次
StopIteration:
参考上面的生成器,也可以使用for进行循环,如下:
x = fib(4)
for a in x:
print(a)
输出: 1
1
2
3
但是你会发现,无法得到上面fib中的return的值,这时需要通过捕获StopIteration来得到:
x1 = fib(4)
while True:
try:
print(next(x1))
except StopIteration as e:
print("from return: {}".format(e.value))
break
输出: 1
1
2
3
from return: done
- 小结:
generator 是非常强大的工具,在 Python 中,可以简单地把列表生成式改成 generator,也可以通过函数实现复杂逻辑的 generator。
要理解 generator 的工作原理,它是在 for 循环的过程中不断计算出下一个元素,并在适当的条件结束 for 循环。对于函数改成的 generator 来说,遇到 return 语句或者执行到函数体最后一行语句,就是结束 generator的指令,for 循环随之结束。
-
请注意区分普通函数和 generator 函数,普通函数调用直接返回结果:
r = abs(6)
r →6
-
generator 函数的“调用”实际返回一个 generator 对象:
g = fib(6)
g →
<generator object fib at 0x1022ef948>
23. 【重要】迭代器
通过上面的讲述,已经接触了如下可以在for循环中处理的对象:
- 第一类是集合类对象,比如list,tuple,dict,set,str等
- 第二类就是生成器,通过()产生的生成器表达式,或是通过yeild的function。
这类可以通过for循环处理的叫做可迭代对象,Iterable;通过IsInstance函数可以进行判断,上面已有很多这种例子。
isinstance((x for x in range(10)), Iterable)
输出:True
而生成器不仅能通过for循环处理,还能使用next()函数不断返回下一个计算结果,至到出现StopIteration异常,可以被next()函数调用并不断返回下一个值的对象,称为迭代器 Iterator。
from collections import Iterator
isinstance((x for x in range(10)), Iterator)
输出:True
isinstance([1,2,3], Iterator)
输出:False
- 将 list / dict / str等Iterable变成Iterator,可以使用iter()函数。例如 :
isinstance("ABCD",Iterator)
输出:False
isinstance(iter("ABCD"),Iterator)
输出:True
- 小结:
Python 的 Iterator 对象表示的是一个数据流,Iterator 对象可以被 next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration 错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next()函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而使用list 是永远不可能存储全体自然数的。
- 可以作用于for循环的是Iterable对象。
- 凡是可作用于 next()函数的对象都是 Iterator 类型,它们表示一个惰性计算的序列.
- 集合数据类型如 list、dict、str 等是 Iterable 但不是 Iterator,不过可 以通过 iter()函数获得一个 Iterator 对象。
Python 的 for 循环本质上就是通过不断调用 next()函数实现的.
for x in range(3):
print(x)
输出: 0
1
2
上面的for循环与下面的处理实际上是等价的:
x = iter([0,1,2])
while True:
try:
i = next(x)
print(i)
except StopIteration:
print(StopIteration.value)
break
输出:<member 'value' of 'StopIteration' objects>