Haste makes waste

Python-Liao-02-【重要】函数-切片-生成器-迭代器

Posted on By lijun

[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。

notebook

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)

默认参数能够简化函数的调用,但使用的时候要注意:

  1. 固定参数在前,默认参数在后。
  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.默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

要注意定义可变参数和关键字参数的语法:

  1. *args 是可变参数,args 接收的是一个 tuple;
  2. **kw 是关键字参数,kw 接收的是一个 dict。

另外,

  1. 可变参数既可以直接传入:func(1, 2, 3),又可以先组装 list 或 tuple, 再通过args 传入:func((1, 2, 3));

  2. 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装 dict,再通 过kw 传入:func({‘a’: 1, ‘b’: 2})。

  3. 使用*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 是永远不可能存储全体自然数的。

  1. 可以作用于for循环的是Iterable对象。
  2. 凡是可作用于 next()函数的对象都是 Iterator 类型,它们表示一个惰性计算的序列.
  3. 集合数据类型如 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>