数据类型&推导式&生成器&迭代器
一、元组和列表
1.元祖和列表的性能分析
- 测试环境:ipython 中使用 timeit模块
- 计算时间模块介绍:
# ipython环境下
In [1]: timeit list1=[1,2,3]
42.3 ns ± 1.2 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [2]: timeit tuple1=(1,2,3)
12.3 ns ± 0.0096 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
# 总结:可以看到初始化列表一千万次是初始化一千万次元组时间的三倍多,所以在数据确认的情况下尽可能使用元组储存数据
import timeit # python下用来性能分析
def func():
for i in range(10):
print(i)
# 函数等需要传函数名
times = timeit.timeit(func, number=5)
print(times)
# 数据类型需要传字符串,number默认值一千万次
times=timeit.timeit("[1,2,3]")
print(times)
2.命名元组
正常元组的取值方式是通过下标,不够人性化,命名元组就实现可以像字典那样取值
- 方式一(没事找事)
# 方式一 ,先把先把下标赋值给key,然后就可以通过key取值了 太low
tu=["小明",18,"男"]
name=0
age=1
gender=2
print(tu[name])
print(tu[age])
print(tu[gender])
- 方式二 :collections 模块里的 namedtuple方法
from collections import namedtuple
# typename:类型名字 ,field_names:这个元组元素的名称,传一个列表
info_tuple = namedtuple(typename="info_tuole", field_names=["name", "age", "gender"])
# 单个元组
tu = info_tuple("小明", 18, "男")
print(tu)
print(tu.age)
二、字典和集合的原理和应用
dict和set实现原理是一样的都是无序的,都是将实际的值放到list中,唯一不通的在于hash函数操作对象不同,对于dict,hash函数操作的是其key,而set是直接操作的它的元素,假设操作元素为“X”,其作为变量,放入hash函数,通过运算后取list的余数,作为list的下标存储起来,set就直接存放set本身元素,叫做hash set,而对于dict则是创建两个list,一个list存key,一个list存value,叫做hash map(map就是通过key找value的过程)
- 定义集合set
- 最大作用自动去重
- 关系性测试数据:交集,并集,差集,对称差集
# 定于空集合
se = set()
se.add(1) # 单个添加
se.update([1, 2, 3, 4, 2, 2]) # 添加多个
se.remove(1)
print(se)
a=set([1,2,3,4,5,6])
b=set([4,5,7,8,9])
# 交集intersection
print(a.intersection(b)) #>>>{5, 6}
# 并集union
print(a.union(b)) # >>>{1, 2, 3, 4, 5, 6, 7, 8, 9}
# 差集:在a中不在b中的
print(a.difference(b)) # >>>{1, 2, 3, 4}
# 对称差集:反向交集
print(a.symmetric_difference(b)) # >>>{1, 2, 3, 4, 7, 8, 9}
# 超集:判断a是不是完全包含b a>=b
print(a.issuperset(b))
# 子集:判断a是不是b的子集 a<=b
print(a.issubset(b))
- 字典
- 性能分析
- 从时间上(快-慢):集合、字典、元组、列表
- 从内存上(少-多):元组、列表、集合、字典
三、推导式
- 列表推导式
# 如生成0-100的单数的平方列表
list1 = [i*i for i in range(1, 101) if i % 2 == 1]
print(list1)
- 字典推导式
cook_str = 'BIDUPSID=D0727533D7147B7;PSTM=1530348042;BAIDUID=B1005C9BC2EB28;sugstore=0'
dict_str = {i.split("=")[0]: i.split("=")[1] for i in cook_str.split(";")}
四、生成器
- 生成器表达式
# 生成器表达式 内部实现了生成器协议-有__next__方法
gen = (i for i in range(20))
print(gen)
print(dir(gen))
# 通过next()方法生成一个取一个,取玩在取报错StopIteration
print(next(gen))
print(next(gen))
print(next(gen))
# 可以通过for 循环取值
for i in gen:
print(i)
- 函数生成器
- send方法
- close方法
- throw方法
def gen_fun():
for i in range(1,10):
send_value = yield i
print(send_value)
# 赋值生成器对象
res = gen_fun()
print(res)
# 执行到yield 返回1
print(next(res))
# 继续执行 给send_value赋值 因为不没有传 所以是None 执行打印None 在继续执行循环到yield,返回2
print(next(res))
# 继续执行 给send_value赋值 send的值100 打印100 执行打印100 在继续执行循环到yield,返回3
# send方法在去过一次值后,程序执行到yield后菜能使用
print(res.send(100))
#继续执行 给send_value赋值 因为不没有传 所以是None 执行打印None 在继续执行循环到yield,返回2
print(next(res))
res_1=gen_fun()
print(next(res_1))
res_1.close() # close 方法关闭生成器
print(next(res_1)) # >>>StopIteration 报错
res_2=gen_fun()
# 让生成器抛错机制 第一个参数:异常类型 ;第二个参数:异常信息
res_2.throw(ValueError,"value异常") # >>>ValueError: value异常
五、迭代器
- 可迭代对象是序列不是迭代器,可被for循环遍历取值,内部有__iter__()方法,没有__next__()方法
- 可迭代对象可以通过iter方法实现可迭代协议,将可迭代对象转换成迭代器,内部有__next__()
- 生成器是一种特殊的迭代器
- 生成器相对迭代器多了几种方法(__del__,__qualname__,'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw')
list_1=[1,2,3,4,5]
iterator=iter(list_1)
print(iterator)
print(next(iterator))
print(next(iterator))
六、案例应用
1、用生成器来观测一波数学公式
示例,数学中有一个恒等式,(1 + 2 + 3 + ... + n)^2 = 1^3 + 2^3 + 3^3 + ... + n^3 的证明
def generator(k):
i = 1
while True:
yield i ** k
i += 1
gen_1 = generator(1)
gen_3 = generator(3)
print(gen_1)
print(gen_3)
def get_sum(n):
sum_1, sum_3 = 0, 0
for i in range(n):
next_1 = next(gen_1)
next_3 = next(gen_3)
print('next_1 = {}, next_3 = {}'.format(next_1, next_3))
sum_1 += next_1
sum_3 += next_3
print(sum_1 * sum_1, sum_3)
get_sum(8)
########## 输出 ##########
# <generator object generator at 0x10c30d3d0>
# <generator object generator at 0x10c6d61d0>
# next_1 = 1, next_3 = 1
# next_1 = 2, next_3 = 8
# next_1 = 3, next_3 = 27
# next_1 = 4, next_3 = 64
# next_1 = 5, next_3 = 125
# next_1 = 6, next_3 = 216
# next_1 = 7, next_3 = 343
# next_1 = 8, next_3 = 512
# 1296 1296
2、判断子序列:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
示例 1:
s = "abc", t = "ahbgdc"
返回 true.
示例 2:
s = "axc", t = "ahbgdc"
返回 false.
方法一: 双指针,逻辑清晰,代码多
思路:建立s,t序列得指针计数器,然后对t序列一路扫过去(每次指针+1),如果某个数字和s指针指的一样,那么就把s指针前进一步
def is_subsequence(s, t):
index_1 = 0
index_2 = 0
while index_1 < len(s) and index_2 < len(t):
if s[index_1] == t[index_2]:
index_1 += 1
index_2 += 1
if index_1 == len(s):
return True
else:
return False
方法二: 迭代器,两行代码
def is_subsequence(s, t):
t = iter(t)
return all(i in t for i in s)
# 说明:
# i in t 每次对比会去取一个数据出来。t中数据自动减少,不需要维护指针,也没有索引得概念
# all:用于判断给定的可迭代参数 iterable 中的所有元素是否都为 True,如果是返回 True,否则返回 False
七、总结
1、可迭代对象
所有的容器都是可迭代的(iterable)。这里的迭代,和枚举不完全一样。迭代可以想象成是你去买苹果,卖家并不告诉你他有多少库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为:要么给你拿一个苹果;要么告诉你,苹果已经卖完了。你并不需要知道,卖家在仓库是怎么摆放苹果的。
2、迭代器
严谨地说,迭代器(iterator)提供了一个 next 的方法。调用这个方法后,你要么得到这个容器的下一个对象,要么得到一个 StopIteration 的错误(苹果卖完了)。你不需要像列表一样指定元素的索引,因为字典和集合这样的容器并没有索引一说。比如,字典采用哈希表实现,那么你就只需要知道,next 函数可以不重复不遗漏地一个一个拿到所有元素即可。
而可迭代对象,通过 iter() 函数返回一个迭代器,再通过 next() 函数就可以实现遍历。for in 语句将这个过程隐式化,所以,你只需要知道它大概做了什么就行了。
3、生成器
这里,你只需要记着一点:生成器是懒人版本的迭代器
生成器可以想象成是你去买苹果,卖家并没有库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为,立马生成 1 个苹果(生成速度极快):要么给你拿一个苹果;要么告诉你,苹果已经卖完了。