Day4 函数、列表生成式、生成器、迭代器

温故而知新:

1. 集合

主要作用: 

  1. 去重
  2. 关系测试, 交集\差集\并集\反向(对称)差集

2. 元组  

只读列表,只有count, index 2 个方法

作用:如果一些数据不想被人修改, 可以存成元组,比如身份证列表

3. 字典(key-value对

  1. 特性:
  2. 无顺序
  3. 去重
  4. 查询速度快,比列表快多了

4. 字符编码

先说python2

  1. py2里默认编码是ascii
  2. 文件开头那个编码声明是告诉解释这个代码的程序 以什么编码格式 把这段代码读入到内存,因为到了内存里,这段代码其实是以bytes二进制格式存的,不过即使是2进制流,也可以按不同的编码格式转成2进制流,你懂么?
  3. 如果在文件头声明了#_*_coding:utf-8*_,就可以写中文了, 不声明的话,python在处理这段代码时按ascii,显然会出错, 加了这个声明后,里面的代码就全是utf-8格式了
  4. 在有#_*_coding:utf-8*_的情况下,你在声明变量如果写成name=u"大保健",那这个字符就是unicode格式,不加这个u,那你声明的字符串就是utf-8格式
  5. utf-8 to gbk怎么转,utf8先decode成unicode,再encode成gbk

再说python3

  1. py3里默认文件编码就是utf-8,所以可以直接写中文,也不需要文件头声明编码了,干的漂亮
  2. 你声明的变量默认是unicode编码,不是utf-8, 因为默认即是unicode了(不像在py2里,你想直接声明成unicode还得在变量前加个u), 此时你想转成gbk的话,直接your_str.encode("gbk")即可以
  3. 但py3里,你在your_str.encode("gbk")时,感觉好像还加了一个动作,就是就是encode的数据变成了bytes里,我擦,这是怎么个情况,因为在py3里,str and bytes做了明确的区分,你可以理解为bytes就是2进制流,你会说,我看到的不是010101这样的2进制呀, 那是因为python为了让你能对数据进行操作而在内存级别又帮你做了一层封装,否则让你直接看到一堆2进制,你能看出哪个字符对应哪段2进制么?什么?自己换算,得了吧,你连超过2位数的数字加减运算都费劲,还还是省省心吧。  
  4. 那你说,在py2里好像也有bytes呀,是的,不过py2里的bytes只是对str做了个别名(python2里的str就是bytes, py3里的str是unicode),没有像py3一样给你显示的多出来一层封装,但其实其内部还是封装了的。 这么讲吧, 无论是2还是三, 从硬盘到内存,数据格式都是 010101二进制到-->b'xe4xbdxa0xe5xa5xbd' bytes类型-->按照指定编码转成你能看懂的文字

编码应用比较多的场景应该是爬虫了,互联网上很多网站用的编码格式很杂,虽然整体趋向都变成utf-8,但现在还是很杂,所以爬网页时就需要你进行各种编码的转换,不过生活正在变美好,期待一个不需要转码的世界。

最后,编码is a piece of fucking shit, noboby likes it.

函数基本语法及特性

背景提要

现在老板让你写一个监控程序,监控服务器的系统状况,当cpu\memory\disk等指标的使用量超过阀值时即发邮件报警,你掏空了所有的知识量,写出了以下代码

while True:
    if cpu利用率 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
     
    if 硬盘使用空间 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
     
    if 内存占用 > 80%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

这样写是不是有问题呢?是的,存在以下两个问题

  1. 代码重复过多,一个劲的copy and paste不符合高端程序员的气质
  2. 如果日后需要修改发邮件的这段代码,比如加入群发功能,那你就需要在所有用到这段代码的地方都修改一遍

要想解决这个问题,其实很简单,只需要把重复的代码提取出来,放在一个公共的地方,起个名字,以后谁想用这段代码,就通过这个名字调用就行了,如下:

def 发送邮件(内容)
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接
     
while True:
     
    if cpu利用率 > 90%:
        发送邮件('CPU报警')
     
    if 硬盘使用空间 > 90%:
        发送邮件('硬盘报警')
     
    if 内存占用 > 80%:
        发送邮件('内存报警')

函数是什么?

定义:函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。

特性:

  1. 减少重复代码
  2. 使程序变的可扩展
  3. 使程序变得易维护

语法:定义函数使用 def 关键字,一般格式如下:

def 函数名(参数列表):
    函数体

注意:你可以定义一个由自己想要功能的函数,以下是简单的规则:

函数定义:

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()
  • .函数名:任何有效的Python标识符
  • 参数列表:调用参数时传递给它的值
    • 参数的个数大于等于零
    • 多个参数由逗号分隔
  • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
  • 形式参数:定义函数时,函数名后面圆括号中的变量,简称“形参”。形参只在函数内部有效!
  • 实际参数:调用函数时,函数名后面圆括号中的变量,简称“实参”
  • 函数体:函数被调用时执行的代码,由一个或多个语句组成。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • 默认情况下,参数值和参数名称是按函数声明中定义的的顺序匹配起来的。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None
  • Python中的函数可以返回任意数据类型
  • 函数调用的一般形式:函数名(参数)

函数分为两部分:函数定义和函数调用【以上是函数定义】

如下所示:

def hello():
    print("Hello World!")

hello() #函数调用

下面是有参数的函数定义和调用:

# 计算面积函数
def area(width, height):
    return width * height
 
def print_welcome(name):
    print("Welcome", name)

print_welcome("Runoob")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
View Code

函数参数(形参和实参)

1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量

2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值

位置参数和关键字(标准调用:实参与形参位置一一对应;关键字调用:位置无需固定)

形参又分为三中不同的参数:

  • 普通参数(位置参数)
  • 默认参数
  • 动态参数

如下所示:

def hello():
    print("say hello")

hello()
无参
def hello(name,age):
    print("name:",name," age:",age)

hello(12,"张安")
普通参数
默认参数

发现 country 这个参数 基本都 是"CN", 就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就会是 中国, 这就是通过默认参数实现的,把country变成默认参数非常简单。

def stu_register(name,age,course,country="CN"):

这样,这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。

 另外,在没有动态参数的情况下,你可能注意到了,在把country变成默认参数后,我同时把它的位置移到了最后面!

若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数,非固定参数是元组!

def stu_register(name,age,*args): # *args 会把多传入的参数变成一个元组形式
    print(name,age,args)
 
stu_register("Alex",22)
#输出
#Alex 22 () #后面这个()就是args,只是因为没传值,所以为空
 
stu_register("Jack",32,"CN","Python")
#输出
# Jack 32 ('CN', 'Python')
*args

注意:当形参是非固定参数的时候,实参就不要使用关键参数了,因为

def stu_register(name, age, *args, **kwargs):  # *kwargs 会把多传入的参数变成一个dict形式
    print(name, age, args, kwargs)

stu_register("Alex", 22)
# 输出
# Alex 22 () {}#后面这个{}就是kwargs,只是因为没传值,所以为空
stu_register("Jack", 32, "CN", "Python", sex="Male", province="ShanDong")
# 输出
# Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}
**kwargs

从上面这个例子中可以看出,*args接收的是普通参数,**kwargs是接收的是关键参数!

实参:

  • 普通参数 按着顺序给形参传值就好
  • 关键参数 关键参数要放在实参的最后,否则有错!
def stu_register(name, age,sex):  # *kwargs 会把多传入的参数变成一个dict形式
    print(name, age,sex)

stu_register("张三",sex="Man",age=12)
关键参数

总结:对于实参,关键参数要放在实参最后!

全局、局部变量、参数

在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量,参数就是方法传递过来的值。
全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序。
当全局变量与局部变量同名时:
在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。
参数和局部变量都是在一个函数内部作用域范围有效;

 代码演示如下:

name = "Alex Li"
def change_name(name1):
    sex = "Man"
    global name
    name = "hah"
    print(name1,sex,name)

    print("after change", name)


change_name("汪峰")
View Code

区分:参数变量和局部变量、全局变量

 返回值  

函数按着有无返回值分为:有返回值函数和无返回值函数,要想获取函数的执行结果,就可以用return语句把结果返回

注意:

  1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
  2. 如果未在函数中指定return,那这个函数的返回值为None 

强行插入知识点: 嵌套函数 

看上面的标题的意思是,函数还能套函数?of course

name = "Alex"

def change_name():
    name = "Alex2"
    def change_name2():
        name = "Alex3"
        print("第3层打印", name)
    change_name2()  # 调用内层函数
    print("第2层打印", name)

change_name()
print("最外层打印", name)

此时,在最外层调用change_name2()会出现什么效果?

没错, 出错了, 为什么呢?

嵌套函数的用法会了,但它有什么用呢?下节课揭晓。。。

匿名函数 

匿名函数就是不需要显式的指定函数

匿名函数的语法:

: 为分隔符,左侧为参数【普通参数,默认参数,可变参数都可以】,右侧为表达式

为什么要用匿名函数? 
1. 程序一次行使用,所以不需要定义函数名,节省内存中变量定义空间
2. 如果想让程序更加简洁时。

# 这段代码
def calc(n):
    return n ** n

print(calc(10))
# 换成匿名函数
calc = lambda n: n ** n
print(calc(10))

匿名函数的几个规则:

1. 一般也就一行表达式,必须有返回值
2. 不能有return 
3. 可以没有参数,可以有一个或多个参数

eg:无参匿名函数

t = lambda : True #分号前无任何参数

带参匿名函数

lambda x: x**3 #一个参数
lambda x,y,z:x+y+z #多个参数

lambda x,y=3: x*y #允许参数存在默认值

匿名函数调用

#直接赋值给一个变量,然后再像一般函数调用

c = lambda x,y,z: x*y*z
d= c(2,3,4)
print(d)

直接后面传递实参

 (lambda x,y: x if x> y else y)(101,102)

嵌套使用

def increment(n):
    return lambda x: x+n

f=increment(4)
f(2)

案例:求最小值

lower = lambda x,y: x if x<y else y
c =lower('aa','ab')
print(c)

函数式编程介绍  

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

一、定义

简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。

主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:

(1 + 2) * 3 - 4

传统的过程式编程,可能这样写:

var a = 1 + 2;
var b = a * 3;
var c = b - 4;

函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:

var result = subtract(multiply(add(1,2), 3), 4);

这段代码再演进以下,可以变成这样

add(1,2).multiply(3).subtract(4)

这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:

merge([1,2],[3,4]).sort().search("2")

因此,函数式编程的代码更容易理解。

要想学好函数式编程,不要玩py,玩Erlang,Haskell, 好了,我只会这么多了。。。

高阶函数

变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数或者函数的返回值为函数,这种函数就称之为高阶函数。

def add(x,y,f):
    return f(x) + f(y)
 
 
res = add(3,-6,abs)
print(res)

内置函数

内置参数详解 https://docs.python.org/3/library/functions.html?highlight=built#ascii 

#compile
f = open("函数递归.py")
data =compile(f.read(),'','exec')
exec(data)


#print
msg = "又回到最初的起点"
f = open("tofile","w")
print(msg,"记忆中你青涩的脸",sep="|",end="",file=f)


# #slice
# a = range(20)
# pattern = slice(3,8,2)
# for i in a[pattern]: #等于a[3:8:2]
#     print(i)
#
#


#memoryview
#usage:
#>>> memoryview(b'abcd')
#<memory at 0x104069648>
#在进行切片并赋值数据时,不需要重新copy原列表数据,可以直接映射原数据内存,
import time
for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print('bytes', n, time.time()-start)

for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print('memoryview', n, time.time()-start)
几个内置方法用法提醒

递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

def calc(n):
    print(n)
    if int(n / 2) == 0:
        return n
    return calc(int(n / 2))

calc(10)

递归特性:

1. 必须有一个明确的结束条件

2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少

3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html

递归函数实际应用案例,二分查找

data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35]
 
 
def binary_search(dataset,find_num):
    print(dataset)
 
    if len(dataset) >1:
        mid = int(len(dataset)/2)
        if dataset[mid] == find_num:  #find it
            print("找到数字",dataset[mid])
        elif dataset[mid] > find_num :# 找的数在mid左面
            print("33[31;1m找的数在mid[%s]左面33[0m" % dataset[mid])
            return binary_search(dataset[0:mid], find_num)
        else:# 找的数在mid右面
            print("33[32;1m找的数在mid[%s]右面33[0m" % dataset[mid])
            return binary_search(dataset[mid+1:],find_num)
    else:
        if dataset[0] == find_num:  #find it
            print("找到数字啦",dataset[0])
        else:
            print("没的分了,要找的数字[%s]不在列表里" % find_num)
 
 
binary_search(data,66)

列表生成式,迭代器&生成器

列表生成式

孩子,我现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],我要求你把列表里的每个值加1,你怎么实现?你可能会想到2种方式 :

>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = []
>>> for i in a:b.append(i+1)
... 
>>> b
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
普通青年版
a = [1,3,4,6,7,7,8,9,11]

for index,i in enumerate(a):
    a[index] +=1
print(a)
 内置enumerate函数 原值修改
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101d2c630>
>>> for i in a:print(i)
... 
3
5
7
9
11
文艺青年版

其实还有一种写法,如下:

>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

这就叫做列表生成

生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

(expr for iter_var in iterable if cond_exp

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

1 L = [x * x for x in range(10)]
2 print(L)
3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
4 g = (x * x for x in range(10))
5 print(g)
View Code

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

g = (x * x for x in range(5))
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
View Code

我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

g = (x * x for x in range(10))
for n in g:
    print(n)
View Code

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

注意,赋值语句:

a, b = b, a + b

相当于:

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但不必显式写出临时变量t就可以赋值。

上面的函数可以输出斐波那契数列的前N个数:

fib(10)

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b
        a,b = b,a+b

        n += 1

    return 'done'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

f = fib(6)
print(f)

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b
        a,b = b,a+b

        n += 1
    return 'done'

data = fib(10)
print(data)

print(data.__next__())
print(data.__next__())
print("干点别的事")
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())
View Code

在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

1 for n in fib(6):
2      print(n)
3 print(data.__next__())
4 print(data.__next__())
5 print(data.__next__())
6 print(data.__next__())

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

 1 def fib(max):
 2     n,a,b = 0,0,1
 3 
 4     while n < max:
 5         #print(b)
 6         yield  b
 7         a,b = b,a+b
 8 
 9         n += 1
10     return 'done'
11 g = fib(6)
12 while True:
13     try:
14         x = next(g)
15         print('g:', x)
16     except StopIteration as e:
17         print('Generator return value:', e.value)
18         break
View Code

关于如何捕获错误,后面的错误处理还会详细讲解。

还可通过yield实现在单线程的情况下实现并发运算的效果

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Alex Li'
 3 
 4 import time
 5 def consumer(name):
 6     print("%s 准备吃包子啦!" %name)
 7     while True:
 8        baozi = yield
 9 
10        print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
11 
12 
13 def producer(name):
14     c = consumer('A')
15     c2 = consumer('B')
16     c.__next__()
17     c2.__next__()
18     print("老子开始准备做包子啦!")
19     for i in range(10):
20         time.sleep(1)
21         print("做了2个包子!")
22         c.send(i)
23         c2.send(i)
24 
25 producer("alex")
通过生成器实现协程并行运算

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行 next()方法时从当前位置继续运行。

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。。

迭代器是一个可以记住遍历的位置的对象。也就是:可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:__iter()__ 和 next()

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如listtupledictsetstr等;

一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代的:Iterable

区分:可迭代的和迭代器

可以使用isinstance()判断一个对象是否是Iterable【可迭代的】

1 from collections import Iterable
2 print(isinstance([], Iterable))
3 print(isinstance({}, Iterable))
4 print(isinstance('abc', Iterable))
5 print(isinstance(tuple(),Iterable))
6 print(isinstance((x for x in range(10)), Iterable))
7 print(isinstance(100, Iterable))
View Code

可以使用isinstance()判断一个对象是否是Iterator对象:

from collections import Iterator
print(isinstance((x for x in range(10)), Iterator))
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance('abc', Iterator))
View Code

可以看到生成器都是Iterator即是可迭代的,又是迭代器,但listdictstr,tuple虽然是Iterable,却不是Iterator

简而言之,生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了,所以生成器也是一个迭代器

listdictstr,tupleIterable变成Iterator可以使用iter(),然后就可以通过next()方法来访问list中的元素了。

from collections import  Iterator
print(isinstance(iter([]), Iterator))
print(isinstance(iter('abc'), Iterator))
print(isinstance(iter(tuple()),Iterator))
print(isinstance({"name":"age"},Iterator)) #注意字典是不能转换为迭代器的!
View Code

你可能会问,为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr,tuple等是Iterable但不是Iterator,不过可以通过iter()函数将这些类型转换为Iterator对象【可迭代的(Iterable)】,然后就可以通过next()方法来访问其中的元素了。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

迭代器是访问集合元素的一种方式。迭代器的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。

迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。

迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退。

整合:

自定义迭代器:

既然迭代器就是满足支持迭代器协议,满足迭代器协议就是支持__next__和__iter__方法

1、迭代器基于下面两个个方法:

(1)__next__ 返回容器的下一个项目

(2)__iter__ 返回迭代器本身

2、当序列遍历完时,将抛出StopIteration异常,所以通过捕获这个异常来停止循环

下面是自定义迭代器的示例代码:

# encoding:UTF-8
class MyIterator(object):
    def __init__(self, step):
        self.step = step

    def __next__(self):
        if self.step == 0:
            raise StopIteration
        self.step -= 1
        return self.step

    def __iter__(self):
        return self


for i in MyIterator(6):
    print(i)
自定义迭代器

本节作业

有以下员工信息表

当然此表你在文件存储时可以这样表示

1,Alex Li,22,13651054608,IT,2013-04-01

现需要对这个员工信息文件,实现增删改查操作

  1. 可进行模糊查询,语法至少支持下面3种:
    1. select name,age from staff_table where age > 22
    2. select  * from staff_table where dept = "IT"
    3. select  * from staff_table where enroll_date like "2013"
    4. 查到的信息,打印后,最后面还要显示查到的条数 
  2. 可创建新员工纪录,以phone做唯一键,staff_id需自增
  3. 可删除指定员工信息纪录,输入员工id,即可删除
  4. 可修改员工信息,语法如下:
    1.   UPDATE staff_table SET dept="Market" WHERE where dept = "IT"

 注意:以上需求,要充分使用函数,请尽你的最大限度来减少重复代码!

原文地址:https://www.cnblogs.com/python-machine/p/6825952.html