1.迭代器&生成器
迭代器
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件。
特点:
- 1. 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
- 2. 不能随机访问集合中的某个值 ,只能从头到尾依次访问
- 3. 访问到一半时不能往回退
- 4. 便于循环比较大的数据集合,节省内存
可直接作用于for循环的对象,统称为可迭代对象:Iterable。
使用下面这个方法可以判断一个对象是否迭代
from collections import Iterable print(isinstance('',Iterable)) # 字符串可迭代 print(isinstance(123,Iterable)) # 数字不可迭代 print(isinstance((),Iterable)) print(isinstance([],Iterable)) print(isinstance({},Iterable)) print(isinstance((x for x in range(10)),Iterable)) # 这个是生成器,可迭代
生成器不但可以作用于for循环,还可以next,不断调用返回下一个值。
可以被__next__()调用并不断返回下一个值的对象称为迭代器:Iterator。
可以被__next__()调用并不断返回下一个值的对象称为迭代器:Iterator。
使用下面的方法再判断一下之前的对象是否是迭代器
from collections import Iterator print(isinstance('',Iterator)) print(isinstance(123,Iterator)) print(isinstance((),Iterator)) print(isinstance([],Iterator)) print(isinstance({},Iterator)) print(isinstance((x for x in range(10)),Iterator))
只有最后一个可以__next__(),只有最后一个是True,是迭代器。
上面的这些可迭代对象,目前都不是迭代器。通过iter()就可以把这些可迭代对象变成迭代器。
from collections import Iterator a = [1,2,3,4,5,6] print(isinstance(a,Iterator)) b = iter(a) print(isinstance(b,Iterator)) print(b.__next__()) print(b.__next__()) print(b.__next__()) # 超出范围就会报错
__next__()方法:
重复调用迭代器的__next __()方法(或将其传递给内置函数next())将返回流中的连续项。当没有更多的数据可用时,将引发StopIteration异常。此时,迭代器对象已经耗尽,并且对其__next __()方法的进一步调用只会引发StopIteration aga
a = iter([1,2,3,4,5,6,7]) print(a,type(a)) print(a.__next__()) print(a.__next__()) print(a.__next__()) print(a.__next__()) # 超出范围会报错 输出 <list_iterator object at 0x035CB3F0> <class 'list_iterator'> 1 2 3 4
列表生成式
先看2段代码
a = [ i*2 for i in range(10) ] print(a) b = [] for i in range(10): b.append(i*2) print(b)
a和b的效果一样,但是a使用的代码更加简洁
列表生成式也可以使用函数,生成更加复杂的列表
a = [ max(i,6) for i in range(10) ]
print(a)
上面的是铺垫,主要讲下面的生成器
生成器
生成器的概念及用途
- 1. 定义:一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator),如果函数中包含yield语法,那这个函数就会变成生成器 (定义不容易理解)
- 2. 用列表生成式,我们可以直接创建一个列表。等列表创建完之后,我们可以访问列表中的元素。但是这都得等列表生成完之后。如果列表很大很复杂,就需要耗费很长的时间和内存空间,然后我们才能访问列表中的元素。
- 3. 如果列表元素可以按照某种算法推算出来,我们不必创建完列表在进行后续的操作,而是一边循环一边引用每一个新创建的元素。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
代码1
a = [ i*2 for i in range(10) ] for i in a: print(i) b = ( i*2 for i in range(10) ) # 这个是生成器 for i in b: print(i) print(type(a),type(b))
注释:上面的代码中,a和b的效果是一样的。但是a的机制是先生成完整列表,再执行for循环。而b中只是记录了一个算法,并没有生成任何数据。等到for循环调用b的时候,才一边循环一边计算每一个元素。
这里我们感觉不出两种机制的差别,但是当列表很大或者计算很复杂的情况下,就能发现两者的差别。我们强行来增加生成列表的时间。
代码2
import time def f(n): time.sleep(1) #设置休眠时间 return n*2 a = [ f(i) for i in range(10) ] for i in a: print(i) print("-->") #分割线 b = ( f(i) for i in range(10) ) # 这个是生成器 for i in b: print(i)
注释:这样就能比较出来,a是等待了很长时间来生成列表,然后快速的把结果输出。而b是计算一个元素,输出一个元素,开始没有很长的等待时间,但是每次输出之间是要等待1秒来生成新的元素。
代码3
import time def f(n): time.sleep(1) return n*2 t = time.time() #开始时间 a = [ f(i) for i in range(10) ] for i in a: print(i) print(time.time()-t) #结束所使用时间 t = time.time() #开始时间 b = ( f(i) for i in range(10) ) # 这个是生成器 for i in b: print(i) print(time.time()-t) #结束所使用时间
注意:两者的效率,理论上是差不多的,但是测试下来a要耗时10.02秒,b要耗时10.005秒,如此看来看来使用生成器,也是一个更加效率的方法。
生成器是一边循环一边计算的,所有的元素需要一个一个计算出来。只能一个一个的计算出来,并且只记住了当前的位置,在当前位置你只能取到下一个值,不能退回去,也不能跳过。所以,生成器只有一个方法.__next__
b = ( i*2 for i in range(5) ) print(b.__next__()) print(b.__next__()) print(b.__next__()) print(b.__next__()) # 超出范围会报错
斐波那契数列
指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21,这个数列从第3项开始,每一项都等于前两项之和。
def fib(n): i,a,b = 0,0,1 while i < n: print(b) a,b = b,a+b i += 1 return "结束" fib(10)
把上面的函数的print(b)替换成yield b,就实现了用函数做了一个生成器。
def fib(n): i,a,b = 0,0,1 while i < n: # print(b) yield b # 替换成这句 a,b = b,a+b i += 1 return "结束" f = fib(10) print(f,type(f))
注释:这是定义生成器的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器。
生成器在语句执行遇到yield的时候会返回,但是会记住当前的位置,如果你使用.__next__()则会在之前的位置继续执行。也就是生成器在执行并且返回之后,并没有完全结束。跳出生成器后可以正常执行别的语句,在需要的时候再从之前生成器返回的位置继续执行下一次循环。这样的话生成器最后的return就没有意义了。接着看,我们先试着打印出生成器中的所有元素。
def fib(n): i,a,b = 0,0,1 while i < n: yield b a,b = a,a+b i += 1 return "结束" f = fib(10) print(f,type(f)) print(f.__next__()) print("随时插入你的语句") print(f.__next__()) print(f.__next__()) print("生成器会记住之前的位置继续循环") print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print("等着超出范围") print(f.__next__()) print(f.__next__())
如果超出了继续取,就会报错。仔细看一下报错的内容,最后的StopIteration:后的内容就是你return的内容。我们可以用try来捕获这个错误从而获取return的值。try还没讲到,后面应该会细讲。
def fib(n): i,a,b = 0,0,1 while i < n: #print(b) yield b a,b = b,a+b i += 1 return "结束" f = fib(10) while 1: try: x = next(f) # x = f.__next__() print(x) except StopIteration as e: print("返回值是:",e.value) break
注释:这里x=next(f)和x=f,__next__()效果一样
通过yield还可以实现单线程下的并行效果,这个不叫并行,叫协程。
import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield # 这里中断,通过send传值进来继续执行 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer('A') # 定义了一个consumer('A'),但是并没有运行 c2 = consumer('B') # 定义了一个consumer('B') c.__next__() # consumer('A')启动运行,运行到yield之前,打印"准备吃包子啦" c2.__next__() # consumer('B')启动运行 print("老子开始准备做包子啦!") for i in range(10): time.sleep(1) print("做了2个包子!") c.send(i) # send是给yield传值 c2.send(i) producer("C")
注释:上面的例子里.send(i)之前没提过,和.__next__()一样,但是send可以传一个值回去。
上面的例子就是producer启动以后,又启动了2个consumer。consumer运行到yield中断,等待。producer将值通过send传给consumer后,consumer就执行一次循环,然后再中断,等待新的值传入。
2.装饰器
装饰器详细内容请移步:http://www.cnblogs.com/ljohn-/articles/8335674.html
装饰器总结
提供两个终极模板,按下面说的调整一下,就算不理解应该也能套用了。
将最外层的函数名改成你的装饰器的名字,为你的装饰器起一个合适的名字
内层的函数名无所谓
最内层的代码块做替换成你自己的代码,res=func(*args,**kwargs)和最后的return res这两句不用修改
两层装饰器模板:
''' 两层装饰器,提供了传任意参数,原函数有返回值 ''' import time def run_time(func): # 最外层的函数名字换成你装饰器的名字 def wrapper(*args,**kwargs): "计算参数的函数的运行时间" t = time.time() # 这里替换成你的代码段 res = func(*args,**kwargs) # 这句不变 print(time.time() - t) # 这里换成你的代码段 return res # 这句不变 return wrapper @run_time def sleep(n): "运行后等待n秒" print("wait for %d seconds"%n) time.sleep(n) return "END" print(sleep(2))
三层装饰器模板:
''' 三层装饰器,提供了传任意参数,原函数有返回值,并且装饰器也带有参数 ''' import time def run_time(n): # 最外层的函数名字换成你装饰器的名字 def decorator(func): # 内层的函数名字无所谓 def wrapper(*args,**kwargs): "计算参数的函数的运行时间" t = time.time() # 这里替换成你的代码段 res = func(*args,**kwargs) # 这句不变 print(round((time.time() - t),n)) # 这里替换成你的代码段 return res # 这句不变 return wrapper return decorator @run_time(8) def sleep(n): "运行后等待n秒" print("wait for %d seconds"%n) time.sleep(n) return "END" print(sleep(2))
参考文档:http://blog.51cto.com/steed/1979827
3.递归(recursion)
特点
递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。
递归算法解决问题的特点:
- (1) 递归就是在过程或函数里调用自身。
- (2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
- (3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
- (4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。
要求
递归算法所体现的“重复”一般有三个要求:
- 一是每次调用在规模上都有所缩小(通常是减半);
- 二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
- 三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。
程序实例
(1)斐波纳契数列(Fibonacci Sequence)
求解Fibonacci数列的第n个位置的值?(斐波纳契数列(Fibonacci Sequence),又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F1=1,F2=1,Fn=F(n-1)+F(n-2)(n>2,n∈N*))
常规做法:
In [7]: def fact(n): ...: result = n ...: for i in range(1,n): ...: result *=i ...: return result ...: In [8]: fact(8) Out[8]: 40320
递归做法:
In [1]: def factorial(n): ...: if n == 1: ...: return 1 ...: else: ...: return n *factorial(n-1) ...: In [2]: factorial(8) Out[2]: 40320
(2)幂运算
n的阶乘,这里求5的阶乘
简单的实现方式:
In [1]: def power(x,n): ...: result = 1 ...: for i in range(n): ...: result *=x ● ...: return result ...: In [2]: power(2,3) Out[2]: 8 In [3]: power(5,5) Out[3]: 3125
递归方式:
In [4]: def power(x,n): ...: if n == 0: ...: return 1 ...: else: ...: return x* power(x,n-1) ...: In [5]: power(2,3) Out[5]: 8 In [6]: power(5,5) Out[6]: 3125
(3)二元查找
该算法是也是一种递归案例
例如猜1~100之间的某个数字,只需要通过值等分来判断。
def f(n): if 0==n: # n=0 的话直接返回空,对用户输入的零进行判断 return None elif 1==n: # n=1 的话就不再递归 return n else: return n*f(n-1) # 递归在执行f(n-1) 直到f(1) print(f(5)) # 120 ''' f(5)的执行过程如下 ===> f(5) ===> 5 * f(4) ===> 5 * (4 * f(3)) ===> 5 * (4 * (3 * f(2))) ===> 5 * (4 * (3 * (2 * f(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120 '''
注意:在做递归算法的时候,一定要把握住出口,也就是做递归算法必须要有一个明确的递归结束条件。这一点是非常重要的。其实这个出口是非常好理解的,就是一个条件,当满足了这个条件的时候我们就不再递归了。
4.算法基础:二分查找、二维数组转换
二维数组
要求:生成一个4*4的2维数组并将其顺时针旋转90度
#!_*_coding:utf-8_*_ array=[[col for col in range(5)] for row in range(5)] #初始化一个4*4数组 #array=[[col for col in 'abcde'] for row in range(5)] for row in array: #旋转前先看看数组长啥样 print(row) print('-------------') for i,row in enumerate(array): for index in range(i,len(row)): tmp = array[index][i] #get each rows' data by column's index array[index][i] = array[i][index] # print tmp,array[i][index] #= tmp array[i][index] = tmp for r in array:print r print('--one big loop --')
冒泡排序
将一个不规则的数组按从小到大的顺序进行排序
data = [10,4,33,21,54,3,8,11,5,22,2,1,17,13,6] print("before sort:",data) previous = data[0] for j in range(len(data)): tmp = 0 for i in range(len(data)-1): if data[i] > data[i+1]: tmp=data[i] data[i] = data[i+1] data[i+1] = tmp print(data) print("after sort:",data)
5.Json & pickle 数据序列化
用于序列化的两个模块
- json,用于字符串 和 python数据类型间进行转换
- pickle,用于python特有的类型 和 python的数据类型间进行转换
Json模块提供了四个功能:dumps、dump、loads、load
pickle模块提供了四个功能:dumps、dump、loads、load
序列化:把数据对象变成字符串的形式,这样可以保存在文件中。反之就是反序列化
python自带的str()可以完成序列化,然后eval()可以反序列化,但是我们先把他们忘记。不知道适用范围是多大。
import json data = { 'name':'Ljohn', 'age':21, 'from':'ShangHai' } str = json.dumps(data) #序列化 print(type(data),str) data2 = json.loads(str) #反序列化 print(type(data2),data2) str2 = '{"name":"Jack","age":22,"fron":"BeiJing"}' # JSON只认双引号,所以字符串内部要双引号 print(type(str2),str2) str3= json.loads(str2) #反序列化 print(type(str3),str3)
一种需求是序列化成字符串之后存入文件保存起来。下次要用的时候再读取文件,反序列化生成之前的数据。对于这种情况,对应有两个便捷的方法可以直接完成。
序列化:
import json data = { 'name':'Ljohn', 'age':22, 'from':'ShangHai' } with open('test.json','w',encoding='utf-8') as f: json.dump(data,f) 结果:当前目录创建test.json 内容为:{"name": "Ljohn", "age": 22, "from": "ShangHai"} 反序列化 import json with open('test.json','r',encoding='utf-8') as f: data = json.load(f) print(type(data),data) 结果:<class 'dict'> {'name': 'Ljohn', 'age': 22, 'from': 'ShangHai'}
上面的JSON的序列化并不支持python所有的数据类型。但是JSON是通用的规范,也就是JSON序列化之后的数据到其他语言环境也能识别。
对于不支持的数据类型,应该可以加一步编解码,但是如果别的语言环境也不支持这个数据类型,那么即使能序列化也没有用。
不过python序列化保存之后再给python反序列化使用,就没有数据类型的问题,那么可以使用pickle。
python的pickle模块实现了python的所有数据序列和反序列化。
import pickle data = { 'name':'Ljohn', 'age':21, 'from':'ShangHai' } str = pickle.dumps(data) #序列化 print(type(data),str) data2 = pickle.loads(str) #反序列化 print(type(data2),data2) 结果: <class 'dict'> {'name': 'Ljohn', 'age': 22, 'from': 'ShangHai'}
6.软件目录结构规范
假设项目名称是Foo,项目名称的首字母大写。下面是一个简单的目录结构:
Foo/ |-- bin/ | |-- foo | | |-- __init__.py | | |-- main.py | | |-- conf/ | |-- __init__.py | |-- main.py | | |-- foo/ | |-- tests/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- docs/ | |-- conf.py | |-- abc.rst | |-- setup.py |-- requirements.txt |-- README
简要解释一下:
bin/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。
conf/: 存放配置文件的目录。
foo/: 存放项目的所有源代码。
- (1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。
- (2) 其子目录tests/存放单元测试代码;
- (3) 程序的入口最好命名为main.py。
docs/: 存放一些文档。
setup.py: 安装、部署、打包的脚本。
requirements.txt: 存放软件依赖的外部Python包列表。
README: 项目说明文件。
7. 作业
ATM项目开发
作业需求:
模拟实现一个ATM + 购物商城程序 额度 15000或自定义 实现购物商城,买东西加入 购物车,调用信用卡接口结账 可以提现,手续费5% 支持多账户登录 支持账户间转账 记录每月日常消费流水 提供还款接口 ATM记录操作日志 提供管理接口,包括添加账户、用户额度,冻结账户等。。。 用户认证用装饰器