python自动化之路 day4 py函数基础4

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
'''
View Code

注意:在做递归算法的时候,一定要把握住出口,也就是做递归算法必须要有一个明确的递归结束条件。这一点是非常重要的。其实这个出口是非常好理解的,就是一个条件,当满足了这个条件的时候我们就不再递归了。


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 --')
View Code

冒泡排序
将一个不规则的数组按从小到大的顺序进行排序

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记录操作日志 
提供管理接口,包括添加账户、用户额度,冻结账户等。。。
用户认证用装饰器
posted on 2018-01-23 15:03 ljohnmail 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/ljohn-/p/8335876.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值