🚀 作者 :“码上有前”
🚀 文章简介 :深度学习
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬
python--迭代生成器闭包面向对象继承多态
- 往期内容
- 1.迭代
- for...in
- 字典的迭代
- 列表迭代
- 生成器
- 推导式的弊端
- 创建生成器
- 生成器的迭代
- 生成器的调用
- 生成器对象值的获取
- 迭代器
- 高阶函数
- map
- reduce
- filter
- sorted
- 匿名函数lambda
- 装饰器
- 模块与包
- 模块
- 面向对象
- 访问控制
- 继承与多态
- 静态语言和动态语言
- 小结
往期内容
【Python–vscode常用快捷键,必收藏!】
【Python–代码规范 】
【Python --优雅代码写法】
【Python–Python2.x与Python3.x的区别】
【Python–Web应用框架大比较】
【Python—内置函数】
【Python—六大数据结构】
【python–迭代生成器闭包面向对象继承多态】
【Python–定时任务的四种方法】
【Python–迭代器和生成器的区别】
【Python–读写模式全解】
【Python–高级教程】
【Python–网络编程之DHCP服务器】
【Python–网络编程之Ping命令的实现】
【Python–网络编程之TCP三次握手】
1.迭代
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
可迭代对象: 包括字符串,列表,元组,,集合,字典
from collections.abc import Iterable
is_str_Iterable = isinstance('abc', Iterable) # str可迭代
print("字符串是否是可迭代对象:", is_str_Iterable)is_int_Iterable = isinstance(1234, Iterable) # int不可迭代
print("整数是否是可迭代对象:", is_int_Iterable)is_float_Iterable = isinstance(1234.5, Iterable) # float不可迭代
print("浮点数是否是可迭代对象:", is_float_Iterable)is_complex_Iterable = isinstance(1234+5j, Iterable) # complex不可迭代
print("复数是否是可迭代对象:", is_complex_Iterable)is_list_Iterable = isinstance([], Iterable) # list不可迭代
print("列表是否是可迭代对象:", is_list_Iterable)is_tuple_Iterable = isinstance((), Iterable) # 元组可迭代
print("元组是否是可迭代对象:", is_tuple_Iterable)is_dict_Iterable = isinstance({}, Iterable) # 字典可迭代
print("字典是否是可迭代对象:", is_dict_Iterable)# 判断生成器是否是可迭代对象
g = (x * x for x in range(10))
is_g_Iterable = isinstance(g, Iterable) # 集合可迭代
print("生成器是否是可迭代对象:", is_g_Iterable)set_type = {2,32,'12','34'}
print("set_type类型", type(set_type)) # type类型 <class 'set'>
is_dict_Iterable = isinstance(set_type, Iterable) # 集合可迭代
print("集合是否是可迭代对象:", is_dict_Iterable)# 验证一下
for s in set_type:print(s)
for…in
# d表示可迭代对象
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:print(key)
字典的迭代
# 字典的item()
# 这个方法列表和元组都没有这个方法
dict = {"name":"li","school":"nux","city":"yinchuan"}
l = ['a', 'b', 'c']
tup = ('a', 'b', 'c')
for key,value in dict.items():print(key,value)for value in dict.values():print(f"value:{value}")for key in dict.keys():print(f"key:{key}")
列表迭代
# 提供了一种可列举的 键值对的方法enumerate
# 使用enumberate(['a', 'b', 'c'])将列表变成对应的索引对
for k, v in enumerate(['a', 'b', 'c']):print(k,v)
生成器
推导式的弊端
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
缺点:容量有限,访问元素开销大,浪费空间
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建生成器
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
L = [x * x for x in range(10)]
print(list(range(10)), type(list(range(10))))
print(L)g = (x * x for x in range(10))
print(g, type(g)) # <class 'generator'>
# <generator object <genexpr> at 0x00000229A6939A10>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
g = (x * x for x in range(10))
print(g, type(g)) # <class 'generator'>
# <generator object <genexpr> at 0x00000229A6939A10>
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 4
# 没有更多的元素时,抛出StopIteration的错误。
当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象
生成器的迭代
g = (x * x for x in range(10))
for n in g:print("生成器对象开始迭代",n)# 所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。# 举例 :斐波那契
def fib(max):n,a,b = 0,0,1while n<max:print("---",b)a,b = b,a+bn+=1return 'done' fib(6)
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
# 也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator函数,只需要把print(b)改为yield b就可以了
def fib(max):n, a, b = 0, 0, 1while n < max:yield b # print(b)a, b = b, a + bn = n + 1return 'done'f= f(6)
print(f,type(f)) # <generator object fib at 0x0000018D563F9A10> <class 'generator'># 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator:
生成器的调用
# 可以看到,odd不是普通函数,而是generator函数,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。
def odd():print('step 1')yield 1print('step 2')yield(3)print('step 3')yield(5)next(odd())# step 1 1
next(odd()) # step 1 1next(odd())# step 1 1
生成器对象值的获取
def fib(max):n, a, b = 0, 0, 1while n < max:yield b # print(b)a, b = b, a + bn = n + 1return 'done'g= f(6)
# 将生成器函数赋值给一个变量,然后通过该变量进行遍历生成数
while True:try:x = next(g)print('g:', x)except StopIteration as e:print('Generator return value:', e.value)break
迭代器
我们知道不是生成器对象的str,list,tuple,set,dict都是可以迭代的。而生成器对象也是,有什么方法可以使不是生成器对象str,list,tuple,set,dict变成生成器对象
你可能会问,为什么list、dict、str等数据类型不是Iterator?
#这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。# Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
高阶函数
# 既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
# 接受另一个函数作为参数的函数称为高阶函数
map
# 接受两个参数,一个是函数,另一个是iterable# map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。# 如求x的平方
def f(x):return x * xr = map(f, [1, 2, 3, 4])
print(r) # <map object at 0x000001866456AB60>
l = list(r)
print(l, type(l)) # [1, 4, 9, 16] <class 'list'>
还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串
l_s_l = list(map(str,[1,2,3,4,5]))
print(l_s_l) # ['1', '2', '3', '4', '5']
reduce
# 再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
# reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)# 在使用前需要导入from functools import reduce
from functools import reduce
# 序列求和reduce(add, [1, 3, 5, 7, 9])
filter
Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
# 只保留奇数,删除偶数
def is_odd(n):return n % 2 == 1# <filter object at 0x0000025C70D5B8E0>
print(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))# 第一个参数是函数,第二个参数是序列
# 要使用list将filter变成序列
l_filter = list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
print(l_filter) # [1, 5, 9, 15]
又一实例:计算素数 利用埃氏算法
# 计算素数
# 不断递增
def _odd_iter():n = 1while True:n = n + 2yield n# 定义筛选器
def _not_divisible(n):return lambda x: x % n > 0# 定义一个生成器 不断生成下一个
def primes():yield 2it = _odd_iter() # 初始序列while True:n = next(it) # 返回序列的第一个数yield nit = filter(_not_divisible(n), it) # 构造新序列
# 这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。# 由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:
# 打印1000以内的素数:
for n in primes():if n < 1000:print(n)else:break
# 注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。
sorted
匿名函数lambda
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
现在,假设我们要增强()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下
待定
模块与包
模块
在使用Python时,我们经常需要用到很多第三方库,例如,上面提到的Pillow,以及MySQL驱动程序,Web框架Flask,科学计算Numpy等。用pip一个一个安装费时费力,还需要考虑兼容性。我们推荐直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,我们装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。
廖雪峰
面向对象
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
def print_score(std):print('%s: %s' % (std['name'], std['score']))
假设处理学生成绩可以通过函数实现,比如打印学生的成绩,面向过程考虑的是事件的处理流程。而面向对象首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
访问控制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Student(object):def __init__(self, name, score):# 通过_init_方法把self的属性变成私有的属性self.__name = nameself.__score = scoredef print_score(self):print('%s: %s' % (self.__name, self.__score))# 外部通过调用内部方法获取内部属性
# 但是如果外部代码要获取name和score怎么办?
# 可以给Student类增加get_name和get_score这样的方法:def get_name(self):return self.__namedef get_score(self):return self.__score# 外部通过修改内部方法获取内部属性
# 你也许会问,原先那种直接通过bart.score = 99也可以修改啊,
# 为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:def set_score(self, score):self.__score = score# 外界就不能调用了
bart = Student('Bart Simpson', 59)
bart.__name # AttributeError: 'Student' object has no attribute '__name'# 需要注意的是,在Python中,变量名类似__xxx__的,
# 也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。# 有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。# 双下划线开头的实例变量是不是一定不能从外部访问呢?
# 其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:
# 但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。# Python本身没有任何机制阻止你干坏事,一切全靠自觉。
继承与多态
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
class Animal(object):def run(self):print('Animal is running...')# 对父类的方法进行改进
class Dog(Animal):def run(self):print('Dog is running...')def eat(self):print('Eating meat...')class Cat(Animal):passdog = Dog()
dog.run()# 当子类和父类都存在相同的run()方法时,
# 我们说,子类的run()覆盖了父类的run(),
# 在代码运行的时候,总是会调用子类的run()。
# 这样,我们就获得了继承的另一个好处:多态。
静态语言和动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):def run(self):print('Start...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
小结
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写(覆写)。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。