【python--迭代生成器闭包面向对象继承多态】

🚀 作者 :“码上有前”
🚀 文章简介 :深度学习
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬
在这里插入图片描述

往期内容

【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,1
	while n<max:
		print("---",b)
		a,b = b,a+b
		n+=1
	return 'done'	

fib(6)  

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

# 也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator函数,只需要把print(b)改为yield b就可以了
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b # print(b)
        a, b = b, a + b
        n = n + 1
    return '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 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
    
next(odd())# step 1 1
next(odd()) # step 1 1

next(odd())# step 1 1

生成器对象值的获取

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b # print(b)
        a, b = b, a + b
        n = n + 1
    return '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 * x

r = 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 = 1
    while True:
        n = n + 2
        yield n

# 定义筛选器
def _not_divisible(n):
    return lambda x: x % n > 0


# 定义一个生成器 不断生成下一个
def primes():
    yield 2
    it = _odd_iter()  # 初始序列
    while True:
        n = next(it)  # 返回序列的第一个数
        yield n
        it = 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 = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

# 外部通过调用内部方法获取内部属性
# 但是如果外部代码要获取name和score怎么办?
# 可以给Student类增加get_name和get_score这样的方法:

    def get_name(self):
        return self.__name

    def 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):
    pass


dog = Dog()
dog.run()

# 当子类和父类都存在相同的run()方法时,
# 我们说,子类的run()覆盖了父类的run(),
# 在代码运行的时候,总是会调用子类的run()。
# 这样,我们就获得了继承的另一个好处:多态。

静态语言和动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):
    def run(self):
        print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

小结

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写(覆写)

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上有前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值