拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 一篇文章掌握Python生成器与匿名函式,多执行绪与多行程

一篇文章掌握Python生成器与匿名函式,多执行绪与多行程

白鹭 - 2022-02-10 2075 0 0

测验奇谭,BUG不见,

讲解之前,我先说说我的教程和网上其他教程的区别:

1 我分享的是我在作业中高频使用的场景,是精华内容;

很多人学习蟒蛇,不知道从何学起,

很多人学习寻找python,掌握了基本语法之后,不知道在哪里案例上手,

很多已经可能知道案例的人,却不怎么去学习更多高深的知识,

这三类人,我给大家提供一个好的学习平台,免费获取视频教程,电子书,以及课程的源代码!

QQ群:101677771

欢迎加入,一起讨论学习

 

2 我分享的是学习方法,亦或说,是指明你该学哪些、该重点掌握哪些内容;

3 基于1和2,你可以按照我的教程学,也可以网上找视频学,也可以看书学……你得明白,掌握学习方法比找学习资料重要得多,

前5期,我已经分享了python的基础语法,如果你按照我的文章,一步一步练习,保准你对python代码的语法特点、书写方式有所了解,并能独立的写一些简单的函式方法,甚至能尝试开始自动化测验的实践(基于python语法),

当然,如果你想更进一步掌握python语法的特点,那接下来的几篇文章一定不要错过,

这一场,主讲python的 生成器和匿名函式,

目的:掌握这两个知识点的概念和使用,

生成器

01 什么是生成器?

记住两个关键:

  • 生成器是一种特殊的函式方法, 意味着它和函式(def)密不可分,
  • 基于上一点, 只要函式中出现yield关键字,就是生成器函式 ,

初学的你,还是太难理解?

02 通俗的讲解

你可以将生成器理解为一个盒子,你可以向这个盒子里随意添加元素,当你需要的时候,再取出来用,

请看下面的例子:

# 普通函式
def func():
    return 1

f = func()
print("函式回传值:",f)
->函式回传值:1
print("函式回传值的型别:",type(f))
->函式回传值的型别:<class 'int'>

# 生成器
def gen_func():
    yield 1
    yield 2

g = gen_func()
print("生成器物件:",g)
->生成器物件:<generator object gen_func at 0x00000189B8CFF7C8>
print("生成器物件的型别:",type(g))
->生成器物件的型别:<class 'generator'>
# 读取生成器物件的值,因为生成器也是一个迭代器,实作了python的迭代协议(即实作了__iter__方法)

for i in g:
    print("生成器物件的值:",i)
->生成器物件的值: 1
->生成器物件的值: 2

03 生成器到底有什么用?

作用:惰性求值(一边回圈一边计算的机制),节省性能

04 生成器的常见用途?

  • 读大档案
  • 网络爬虫 scrapy 框架
  • 协程

举个例子:斐波那契数列(0,1,1,2,3,5...),打印斐波那契数列前50个元素

# 不使用生成器,会消耗大量存储器
def fib(idx):
   res=[]
   n, a, b = 0, 0, 1
   while n < idx:
       res.append(b)
       a, b = b, a+b
       n += 1
   return res
res = fib(100)
print(res)

# 使用生成器,可节约大量存储器
def gen_fib(idx):
   n, a, b = 0, 0, 1
   while n < idx:
       yield b
       a, b = b, a+b
       n += 1

for i in gen_fib(100):
   print(i)

匿名函式

01 什么是匿名函式?

当:

  • 函式实作比较简单
  • 函式不需要被多个地方呼叫
  • 懒得给这个函式起名字

时,我们可以使用匿名函式,

初学的你,还是太难理解?

02 通俗的讲解

你想实作一个求x的平方的函式,但是这个函式太简单,不值得专门def定义,同时,你忘记了平方的英文如何拼写,要是命名成 "pingfang",又显得自己太low,于是乎,你可以不给这个函式起名字,还能实作它,这就是匿名函式lambda表达式,

比如:求一个数的平方

# 不用 lambda 表达式
def square(x):
    return x * x
print(square(2))

# 使用 lambda 表达式
# 写法:lambda 回传值:计算表达式
s = lambda x: x * x
print(s(2))

一如既往,做个总结

01 如果你是初学者,可以先不掌握生成器和匿名函式,待学成python后,再行琢磨;

02 在实际作业中,生成器和匿名函式的使用频次,相对较高,并且在面试中是高频问点,

测验奇谭,BUG不见,

大家好,我是谭叔,

这一场,主讲python的 行程和执行绪 ,

目的:掌握初学必须的行程和执行绪知识,

行程和执行绪的区别和联系

终于开始加深难度,来到行程和执行绪的知识点~

单就这两个概念,就难倒过不少初学者——今天学了概念,明天就忘记;明天学了例子,又忘记了概念,

要理解行程和执行绪的联系和区别,我举个特简单的例子:

你的计算机有两个浏览器,一个谷歌浏览器,一个qq浏览器,

一个浏览器就是一个行程,

然后,你打开了谷歌浏览器,百度搜索了测验奇谭,又新开一个标签页,打开谭叔的文章,如下图所示:

一篇文章掌握Python生成器与匿名函式,多执行绪与多行程

 

你可以这样理解—— 在同一个浏览器打开的两个网页就是两个执行绪 , 如果我关闭了浏览器,这两个执行绪也没有了,

好了,当你有了概念后,我才好讲两者的区别和联系,

行程

  • 优点稳定性高 ,当程序崩溃后,不影响其他行程的使用,即谷歌浏览器崩溃后,qq浏览器还可以正常使用,
  • 缺点创建行程的代价大 ,即当你开了各种各样的浏览器后,你的计算机会特别卡,因为计算机存储器和CPU有上限,

执行绪

  • 优点多执行绪通常比多行程快一点,但优势不大
  • 缺点任何一个执行绪挂掉会造成整个行程崩溃,因为执行绪共享行程的存储器(浏览器的例子不再适用,可以理解为绑在一条船上的蚂蚱)

多行程

因为大多数小伙伴用的Windows作业系统,所以针对Unix/Linux的fork()呼叫抛开不谈,

在python,一般使用multiprocessing实作多行程,

import os
from multiprocessing import Process

def run_proc(name):
    print('开始执行子行程 %s (%s)…' % (name, os.getpid()))

# 子行程要执行的代码
if __name__ == '__main__':
    print('父行程 %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',)) # 创建一个Process实体
    print('子行程即将开始.')
    p.start()  # 用start()方法启动实体
    p.join()   # join()方法可以等待子行程结束后再继续往下运行,通常用于行程间的同步
    print('子行程结束.')

要细讲吗?

其实,我的备注写得很全,你只需copy代码下来执行一次,或者debug一次,就能明白,

执行绪池

如何理解?

即代码可运行的行程数量,有个地方管控它,

from multiprocessing import Pool
import os
import time
import random

def long_time_task(name):
    print('开始 task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s 执行花费 %0.2f 秒.' % (name, (end - start)))
if __name__ == '__main__':
    print('父亲行程 %s.' % os.getpid())
    p = Pool(3)
    # 因为Pool的默认大小是4,故task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,但最多同时执行4个行程
    # 由于Pool的默认大小是CPU的核数,如果你拥有8核CPU,要提交至少9个子行程才能看到等待效果
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('等待子行程结束...')
    p.close()
    # 对Pool物件呼叫join()方法会等待所有子行程执行完毕
    # 呼叫join()之前必须先呼叫close(),呼叫close()之后就不能继续添加新的Process了
    p.join()
    print('所有子行程已执行.')

以上,是必知必会的,

当然,最好的学习时机是:当你要用时,再来复盘学,效果最佳,

如果你学了,没有使用场景,我建议缓一缓学或者作为知识储备,

多执行绪

多执行绪有三点必须提前明确:

  • 多任务需求可以由多行程完成,也可以由一个行程内的多执行绪完成
  • 行程是由若干执行绪组成
  • 一个行程至少有一个执行绪

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,我们一般使用高级模块threading(对_thread进行过封装),

启动一个执行绪就是把一个函式传入并创建Thread实体,然后呼叫start()开始执行,我们看一个简单的例子:

import time
import threading

# 新执行绪执行的代码
def loop():
    # 由于任何行程默认就会启动一个执行绪,我们把该执行绪称为主执行绪,主执行绪又可以启动新的执行绪,
    # Python的threading模块有个current_thread()函式,它永远回传当前执行绪的实体
    print('执行绪ss %s 运行中…' % threading.current_thread().name)
    n = 0
    # 主执行绪实体的名字叫MainThread,子执行绪的名字在创建时指定,我们用LoopThread命名子执行绪,在打印输出的时候显示名字
    while n < 5:
    n = n + 1
    print('执行绪ss %s >>> %s' % (threading.current_thread().name, n))
    time.sleep(1)

print('执行绪ss %s 已结束.' % threading.current_thread().name)
print('执行绪 %s is 运行中…' % threading.current_thread().name)

t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()

'''
对于 join()方法而言,其另一个重要方面是其实它根本不需要呼叫,
一旦执行绪启动,它们 就会一直执行,直到给定的函式完成后退出,
如果主执行绪还有其他事情要去做,而不是等待这些执行绪完成(例如其他处理或者等待新的客户端请求),
就可以不呼叫 join(),join()方法只有在你需要等待执行绪完成的时候才是有用的
'''

print('执行绪 %s 已结束.' % threading.current_thread().name)

同样,以上内容,是必知必会的,

并且,作业场景,我们一般会使用多执行绪处理问题,而非多行程,(注意:是一般)

至于行程间通信、执行绪锁、GIL锁、多执行绪变量、执行绪间通信、异步协程等知识,讲起来比较复杂,也不是极简教程的核心,你可以先自学,或者当你真正要使用它的时候再去看,再去学,

一如既往,做个总结

01 多执行绪、多行程,是必知必会的;

02 学习进度自己琢磨,既不能死磕,亦不能简单跳过;

03 小伙伴比较关心,面试时会不会被问到,答:一般公司不会,so?

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *