感谢传智dongge大神
1. 10.0.0.0~10.255.255.255
2. 172.16.0.0~172.31.255.255
3. 192.168.0.0~192.168.255.255
IP地址127.0.0.1~127.255.255.255用于回路测试,
端口号范围:
一般用到的是1到65535,其中0不使用,1-1023为系统端口,也叫BSD保留端口;
1024-65535为用户端口,又分为: BSD临时端口(1024-5000)和BSD服务器(非特权)端口(5001-65535).
0-1023: BSD保留端口,也叫系统端口,这些端口只有系统特许的进程才能使用;
1024-5000: BSD临时端口,一般的应用程序使用1024到4999来进行通讯;
5001-65535: BSD服务器(非特权)端口,用来给用户自定义端口
查看端口号:
netstat -an
lsof -i [tcp/udp]:2425
一般情况下,如果一个程序需要使用知名端口的需要有root权限
Linux中dhclient
命令可以主动要求被分配一个IP地址
在网络中,ip地址,协议,端口就可以标识网络的进程了, 使用方法都是:
import socket
socket.socket(AddressFamily, Type)
AF_INET
(用于 Internet 进程间通信) 或者 AF_UNIX
(用于同一台机器进程间通信),实际工作中常用AF_INETSOCK_STREAM
(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM
(数据报套接字,主要用于 UDP 协议)import socket
# 创建tcp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# ...这里是使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
import socket
# 创建udp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ...这里是使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
socket是全双工的
""
空字符串,就代表本机IPimport socket
udp_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# local_addr = ("127.0.0.1", 8889)
# udp_send.bind(local_addr) --- 发送方也可以选择性的绑定端口,不绑定则随机分配一个
while True:
text = input("输入内容: ")
if text == "q":
udp_send.sendto(text.encode("utf-8"), address)
break
address = ("192.168.3.2", 8888) # 地址为一个tuple,由 IP+port组成
udp_send.sendto(text.encode("utf-8"), address) # sendto函数发送,第一个参数是二进制
udp_send.close() # 要记住关闭
import socket
udp_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
local_addr = ("192.168.3.2", 8888)
udp_recv.bind( local_addr) # 接收方一定要绑定端口
while True:
text = udp_recv.recv(1024).decode('utf-8') # 数字参数为一次接收的字节数
# 也可以使用 recvfrom函数,这样返回值是一个tuple,第二个元素为发送方地址
# binarydata, remote_addr = upd_recv.recvfrom(1024)
print(text)
if text == "q":
break
udp_recv.close()
windows上使用串口调试工具的时候,它们的默认编码方式都是gbk
在同一台机器上使用udp发送和接收时:
127.0.0.1
,无法发送给接收方127.0.0.1
,发送方使用实际IP,无法发送给接收方""
,而发送方无论使用实际IP还是127.0.0.1
,都可以发送成功import socket
def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据并显示"""
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7890))
while True:
# 3. 选择功能
print("="*30)
print("1:发送消息")
print("2:接收消息")
print("="*30)
op_num = input("请输入要操作的功能序号:")
# 4. 根据选择调用相应的函数
if op_num == "1":
send_msg(udp_socket)
elif op_num == "2":
recv_msg(udp_socket)
else:
print("输入有误,请重新输入...")
if __name__ == "__main__":
main()
socket在接收数据的时候,先是由操作系统接收并且缓存起来,然后socket再去取。如果接收的时候只是调用一次recv/recvfrom
,那么这个可以被别人利用起来,发送大量数据来消耗对方电脑的内存。所以最好使用While True
来不断的接收,清除缓存区内容。
udp好比写信,tcp好比打电话
步骤:
import socket
tcp_sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_addr = "127.0.0.1", 8889
tcp_sk.connect(server_addr) # 尝试连接服务器,阻塞
while True:
text = input("输入内容: ")
if text == "q":
break
tcp_sk.send(text.encode("utf-8"))
# 也可以同时接收内容
# recvData = tcp_client_socket.recv(1024)
tcp_sk.close() # 记得关闭
步骤:
import socket
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
local_addr = ("", 8889)
tcp_server.bind(local_addr) # 服务器端记住绑定地址
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
tcp_server.listen(128) # 改变为被动监听
# 返回内容为新的 连接socket,客户端地址
# accept也可以放在 while True中,接收多个连接
connecting_sk, client_addr = tcp_server.accept()
while True:
text = connecting_sk.recv(1024)
# 当客户端断开连接(close)的时候,服务器端会 无限接收到空字符串
# 所以判断,如果接收到空的时候,则服务完成,断开
# 客户端那边,无法发送空字符串,最少要发一个字符,所以不会有假的断开连接出现
if text:
print(text.decode("utf-8"))
else:
break
# 一定记住关闭
connecting_sk.close()
tcp_server.close()
from socket import *
def main():
# 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 目的信息
server_ip = input("请输入服务器ip:")
server_port = int(input("请输入服务器port:"))
# 链接服务器
tcp_client_socket.connect((server_ip, server_port))
# 输入需要下载的文件名
file_name = input("请输入要下载的文件名:")
# 发送文件下载请求
tcp_client_socket.send(file_name.encode("utf-8"))
# 接收对方发送过来的数据,最大接收1024个字节(1K)
recv_data = tcp_client_socket.recv(1024)
# print('接收到的数据为:', recv_data.decode('utf-8'))
# 如果接收到数据再创建文件,否则不创建
if recv_data:
with open("[接收]"+file_name, "wb") as f:
f.write(recv_data)
# 关闭套接字
tcp_client_socket.close()
if __name__ == "__main__":
main()
from socket import *
import sys
def get_file_content(file_name):
"""获取文件的内容"""
try:
with open(file_name, "rb") as f:
content = f.read()
return content
except:
print("没有下载的文件:%s" % file_name)
def main():
if len(sys.argv) != 2:
print("请按照如下方式运行:python3 xxx.py 7890")
return
else:
# 运行方式为python3 xxx.py 7890
port = int(sys.argv[1])
# 创建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 本地信息
address = ('', port)
# 绑定本地信息
tcp_server_socket.bind(address)
# 将主动套接字变为被动套接字
tcp_server_socket.listen(128)
while True:
# 等待客户端的链接,即为这个客户端发送文件
client_socket, clientAddr = tcp_server_socket.accept()
# 接收对方发送过来的数据
recv_data = client_socket.recv(1024) # 接收1024个字节
file_name = recv_data.decode("utf-8")
print("对方请求下载的文件名为:%s" % file_name)
file_content = get_file_content(file_name)
# 发送文件的数据给客户端
# 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式,因此不需要encode编码
if file_content:
client_socket.send(file_content)
# 关闭这个套接字
client_socket.close()
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
由于历史原因,QQ最早是使用UDP,现在是UDP+TCP
python的thread
模块可以创建线程,但是threading
模块封装了thread
模块,提供了对线程更加方便的使用:
threading.Thread
创建线程import threading
def printA(times):
for i in range(times):
print("A")
def printB(times):
for i in range(times):
print("B")
def main():
t1 = threading.Thread(target= printA, kwargs={"times":20})
t2 = threading.Thread(target= printB, args= (20, ))
# t3 = threading.Thread(target= printB, args= (20, ), daemon=True) 设置后台线程
t1.start()
t2.start()
if __name__ == '__main__':
main()
# 结果
A
A
A
A
BA
B
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
AB
B
B
B
1.主线程运行完代码后,会等所有创建的子线程结束,才会结束
2. 如果主线程不等待别的线程结束,主线程一结束,程序就结束了
threading.Thread
创建线程继承自threading.Thread
,然后重写run
方法,最后使用start
方法开始线程。
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
print(msg)
if __name__ == '__main__':
t = MyThread()
t.start()
import threading
len(threading.enumerate()) # 返回当前环境thread的个数,包括主线程
if len(threading.enumerate()) <= 1:
pass
start
并且线程内容开始执行,线程才真正创建了import threading
def printA():
print("A")
t = threading.Thread(target= printA)
t.start()
print(len(threading.enumerate())
# 只打印出了一个 MainThread
# [<_MainThread(MainThread, started 139875311..]
使用 time.sleep()
先等到 子线程运行,再查看就可以了:
#coding=utf-8
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s'%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print('当前运行的线程数为:%d'%length)
if length<=1:
break
sleep(0.5)
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据。共享变量时出现问题:
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num += 1
print("----in work1, g_num is %d---"%g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print("----in work2, g_num is %d---"%g_num)
print("---线程创建之前g_num is %d---"%g_num)
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
# 下面这个写法不错,可以学习学习!
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
同步就是协同步调,按预定的先后次序进行运行。如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
加上锁的共享变量加法:
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
mutex.acquire() # 上锁
g_num += 1
mutex.release() # 解锁
print("---test1---g_num=%d"%g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire() # 上锁
g_num += 1
mutex.release() # 解锁
print("---test2---g_num=%d"%g_num)
# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()
# 创建2个线程,让他们各自对g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()
p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()
# 等待计算完成
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
锁的缺点:
#coding=utf-8
import threading
import time
class MyThread1(threading.Thread):
def run(self):
# 对mutexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
print(self.name+'----do1---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
mutexB.acquire()
print(self.name+'----do1---down----')
mutexB.release()
# 对mutexA解锁
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 对mutexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
print(self.name+'----do2---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
mutexA.acquire()
print(self.name+'----do2---down----')
mutexA.release()
# 对mutexB解锁
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
import socket
import threading
def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
while True:
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据并显示"""
while True:
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7890))
# 3. 创建一个子线程用来接收数据
t = threading.Thread(target=recv_msg, args=(udp_socket,))
t.start()
# 4. 让主线程用来检测键盘数据并且发送
send_msg(udp_socket)
if __name__ == "__main__":
main()
不用管控制台中,由于新接收到的消息,使得提示的文字不整齐——等有了窗口之后,这个就能解决了。
multiprocessing.Process
multiprocessing.Queue
multiprocessing.Pool
multiprocessing.Manager().Queue
程序:例如xxx.py这是程序,是一个静态的——二进制文件
进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。运行的代码+资源
进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
和使用线程很像,换个模块换个类。multiprocessing
是跨平台版本的多进程模块。
创建了2个进程之后,就一共有了3个进程,可以使用 kill
来关闭。
进程之间不共享变量,每个进程都有自己的一份拷贝
import multiprocessing
import time
def worker1():
while True:
print("A")
time.sleep(1)
def worker2():
while True:
print("B")
time.sleep(1)
if __name__=='__main__':
p1 = multiprocessing.Process(target=worker1)
p2 = multiprocessing.Process(target=worker2)
p1.start()
p2.start()
创建进程的时候,会将程序和资源复制一份,但是因为经常有数据没有变化的情况,所以为了提升性能,使用的是 写时拷贝
Process([group [, target [, name [, args [, kwargs]]]]])
参数 | 说明 |
---|---|
target | 如果传递了函数的引用,可以任务这个子进程就执行这里的代码 |
args | 给target指定的函数传递的参数,以元组的方式传递 |
kwargs | 给target指定的函数传递命名参数 |
name | 给进程设定一个名字,可以不设定 |
group | 指定进程组,大多数情况下用不到 |
Process创建的实例对象的常用方法:
方法/属性 | 说明 |
---|---|
start() |
启动子进程实例(创建子进程) |
is_alive() |
判断进程子进程是否还在活着 |
join([timeout]) |
是否等待子进程执行结束,或等待多少秒 |
terminate() |
不管任务是否完成,立即终止子进程 |
name |
当前进程的别名,默认为Process-N,N为从1开始递增的整数 |
pid |
当前进程的pid(进程号) |
使用os.getpid()
:
from multiprocessing import Process
import os
import time
def run_proc():
"""子进程要执行的代码"""
print('子进程运行中,pid=%d...' % os.getpid()) # os.getpid获取当前进程的进程号
print('子进程将要结束...')
if __name__ == '__main__':
print('父进程pid: %d' % os.getpid()) # os.getpid获取当前进程的进程号
p = Process(target=run_proc)
p.start()
multiprocessing.Queue
对象的方法:
方法 | 作用 |
---|---|
Queue(队伍大小) | 创建一个队列,如果队伍大小没有传,或者是负值,则代表队伍无穷大 |
qsize() | 返回此时队伍内的消息数量 |
get() |
从队列获取内容,如果暂时没有内容,会阻塞 |
get([block, [timeout]] | block为布尔,代表是否阻塞,默认为True;timeout为等待时间,如果时间到了还没有数据,就会抛出异常 |
get_nowait() | 从队列获取内容,如果暂时没有内容,会报错,相当于 get(False) |
put(内容) |
放入内容,如果队列满了,会阻塞 |
put( 内容 [block, [timeout] ] ) | |
put_nowait(内容) | 往队列中放置内容,如果满了,会报错 |
empty() | 判断是否队列为空 |
full() | 判断队列是否已满 |
from multiprocessing import Queue
q=Queue(3) #初始化一个Queue对象,最多可接收三条put消息
q.put("消息1")
q.put("消息2")
print(q.full()) #False
q.put("消息3")
print(q.full()) #True
#因为消息列队已满下面的try都会抛出异常,第一个try会等待2秒后再抛出异常,第二个Try会立刻抛出异常
try:
q.put("消息4",True,2)
except:
print("消息列队已满,现有消息数量:%s"%q.qsize())
try:
q.put_nowait("消息4")
except:
print("消息列队已满,现有消息数量:%s"%q.qsize())
#推荐的方式,先判断消息列队是否已满,再写入
if not q.full():
q.put_nowait("消息4")
#读取消息时,先判断消息列队是否为空,再读取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
import multiprocessing
import time
import os
def put_item(q):
print("当前是子线程,pid是:%s"%os.getpid())
for i in range(10):
q.put(i)
print("放入了第%d个消息\n"%i, end='')
def get_item(q):
while True:
msg = q.get()
print("获得了%s\n"%msg, end='')
if q.empty():
break
def main():
msg_queue = multiprocessing.Queue(10)
p1 = multiprocessing.Process(target= put_item, args= (msg_queue,) )
p1.start()
print("当前线程的pid是:%s"%os.getpid())
# time.sleep(1)
get_item(msg_queue)
if __name__ == '__main__':
main()
multiprocessing.Pool
常用函数 | 说明 |
---|---|
apply_async(func[, args[, kwds]]) |
使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表; |
close() |
关闭Pool,使其不再接受新的任务; |
terminate() | 不管任务是否完成,立即终止; |
join() |
主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用; |
进程池中的进程间通信,使用multiprocessing.Manager().Queue()
到底使用几个进程的性能最好,和机器的效能有关。
如果不让线程池join,主线程不会等待线程池内任务执行完就会直接退出程序
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号为%d" % (msg,os.getpid()))
# random.random()随机生成0~1之间的浮点数
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
if __nane__="__main__":
po = Pool(3) # 定义一个进程池,最大进程数3
for i in range(0,10):
# Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply_async(worker,(i,))
print("----start----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
注意:
if __name__ == "__main__"
:import multiprocessing
import os
import time
import random
def copy_file(queue, file_name,source_folder_name, dest_folder_name):
"""copy文件到指定的路径"""
f_read = open(source_folder_name + "/" + file_name, "rb")
f_write = open(dest_folder_name + "/" + file_name, "wb")
while True:
time.sleep(random.random())
content = f_read.read(1024)
if content:
f_write.write(content)
else:
break
f_read.close()
f_write.close()
# 发送已经拷贝完毕的文件名字
queue.put(file_name)
def main():
# 获取要复制的文件夹
source_folder_name = input("请输入要复制文件夹名字:")
# 整理目标文件夹
dest_folder_name = source_folder_name + "[副本]"
# 创建目标文件夹
try:
os.mkdir(dest_folder_name)
except:
pass # 如果文件夹已经存在,那么创建会失败
# 获取这个文件夹中所有的普通文件名
file_names = os.listdir(source_folder_name)
# 创建Queue
queue = multiprocessing.Manager().Queue()
# 创建进程池
pool = multiprocessing.Pool(3)
for file_name in file_names:
# 向进程池中添加任务
pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))
# 主进程显示进度
pool.close()
all_file_num = len(file_names)
while True:
file_name = queue.get()
if file_name in file_names:
file_names.remove(file_name)
copy_rate = (all_file_num-len(file_names))*100/all_file_num
print("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")
if copy_rate >= 100:
break
print()
if __name__ == "__main__":
main()
迭代器、生成器、装饰器——Python的三大器
gevent
就可以gevent
,要先明白greenlet
greenlet
,要先明白yield
yield
,要先明白生成器
生成器
,要先明白迭代器
from collections import Iterable
isinstance(对象, Iterable)
可迭代对象就是能被for循环调用的东西,自己创建的可迭代对象,需要在内部实现__iter__
,返回一个迭代器
class Person(object):
def __init__(self):
self.names = []
def __iter__(self):
"""只要有这个方法,就是可迭代对象,并且能被for调用"""
pass
迭代器保存的是生成数据的方法,而不是保存的数据本身。。
from colletions import Iterator
isinstance(对象, Iterator)
迭代器本身是一个可迭代对象,自己定义的迭代器需要实现__iter__
和__next__
方法:
class Person(object):
"""这是可迭代对象"""
def __init__(self):
self.names = []
def add(self,item):
self.names.append(item)
def __iter__(self):
"""只要有这个方法,就是可迭代对象,并且能被for调用
此方法应该返回一个迭代器
"""
return PersonIterator(self) # 将自身传入,才能使迭代器获取自己的names列表
class PersonIterator(object):
"""这是一个迭代器,next方法返回内容"""
def __init__(self, person):
self.person = person
self.iterindex = 0
def __iter__(self):
pass
def __next__(self):
if self.iterindex < len(self.person.names):
item = person.names[self.iteridex]
self.iterindex += 1
return item
else:
# 此异常标识 已经取完了
raise StopIteration
p = Person()
p.add("A")
p.add("B")
p.add("C")
isinstance(p, Iterable) # True
piterator = iter(p) # 通过 iter方法能调用 __iter__,接收到返回的迭代器
isinstance(piterator, Iterator) # True
next(piterator) # 通过next方法能调用迭代器的 __next__,接收到返回数据
next(piterator)
next(piterator)
next(piterator) # 异常StopIteration
__iter__
)iter
方法,得到迭代器next
方法,从迭代器获取内容next
方法抛出异常StopIteration
,则会停止迭代类本身当作调用自身的迭代器
def __init__(self):
self.names = []
self.iterindex = 0
def add(self,item):
self.names.append(item)
def __iter__(self):
"""只要有这个方法,就是可迭代对象,并且能被for调用
此方法应该返回一个迭代器
"""
return self
def __next__(self):
if self.iterindex < len(self.names):
item = self.names[self.iterindex]
self.iterindex += 1
return item
else:
raise StopIteration
class FibbIterator(object):
def __init__(self, max_index):
self.count = 0
self.max_index = max_index
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.count < self.max_index:
self.a, self.b = self.b, self.a + self.b
self.count+= 1
return self.a
else:
raise StopIteration()
fib = FibbIterator(10)
for i in fib:
print(i)
使用如下代码:
l = list(FibbIterator(10))
t = tuple(FibbIterator(10))
生成代码,实际上是产生了一个新的list:
iter
获取迭代器next
方法获取数据,直到抛出异常生成器是一种特殊的迭代器
gen = ( a for a in range(10))
def create_num(all_num):
a, b = 0,1
current_num = 0
while current_num < all_num:
# 函数内部有yield,这个就是创建生成器
yield a
a, b = b, a+b
current_num += 1
return "ok"
# 调用得到的是一个生成器
obj = create_num(50)
while True:
try:
# 对生成器使用 next来获取值
ret = next(obj)
print(ret)
#没有值就会抛出异常
except Exception as ret:
# 生成器函数如果有返回值,就会在这里获取
print(ret.value)
break
obj2= create_num(20)
for i in obj2:
print(i)
yield
之后,这就是个生成器__iter__
或者_next__
next
方法来依次获取值,获取的是yield之后的值StopIteration
异常没有值就会抛出StopIteration
异常value
属性中send
来启动生成器使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。
使用send函数的意思是这样的:
yield 值
的位置yield 值
的结果返回给 =
左边的值yield
位置,直接使用send(值)
会报错,除非是send(None)
def create_num(all_num):
a, b = 0,1
current_num = 0
while current_num < all_num:
# 函数内部有yield,这个就是创建生成器
res = yield a
print(res)
a, b = b, a+b
current_num += 1
return "ok"
generator = create_num(10)
next(generator) # 得到0
value = generator.send("哈哈") # 打印哈哈, value得到 1
value = generator.send("呵呵") # 打印呵呵, value得到 2
import time
def task_1():
while True:
print("A")
time.sleep(0.1)
yield
def task_2():
while True:
print("B")
time.sleep(0.1)
yield
def main():
t1 = task_1() # 创建生成器1
t2 = task_2() # 创建生成器2
while True:
next(t1)
next(t2) # 使用next,让函数执行一部分,然后交出控制权
if __name__ == "__main__":
main()
这就是一个协程,函数切换,资源调用最少,性能最好。线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住
python中的greenlet模块是对 yield
进行了简单封装,能够让用户在定义任务的时候不需要自己写yield:
sudo pip3 install greenlet
使用方法:
greenlet.greenlet
switch
方法开始调用from greenlet import greenlet
import time
def test1():
while True:
print "---A--"
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print "---B--"
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到gr1中运行
gr1.switch()
gevent是一个网络并发库,内部是使用了协程,它进一步的封装了greenlet
pip3 install gevent
import gevent
def f(n):
for i in range(n):
# 可以获取当前gevent
print(gevent.getcurrent(), i)
g1 = gevent.spawn(f, 5) # 使用spawn传入函数和参数,产生greenlet对象来使用
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join() # 使用 这个对象.join方法,能够开始调用,先去等待g1执行完
g2.join()
g3.join()
运行结果:
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
上面可以看出,这个代码是先去执行完g1,再切换g2,再执行g3.
gevent
的特点join
方法的时候,才会去开始执行这个代码greenlet
或者yield
中,如果有 延时/阻塞操作,那整个程序会等待,而gevent
遇到延时就会自动切换到别的协程中去执行延时和阻塞都需要使用 gevent
库的内容,比如 gevent.socket, gevent.sleep
,而普通的time.sleep, socket.socket
则无法完成自动切换
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
#用来模拟一个耗时操作,注意不是time模块中的sleep
gevent.sleep(1)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
运行结果:
<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4
gevent.monkey.patch_all()
自动转换所有普通的代码,变成gevent
能够自动切换的代码gevent.joinall
创建多个对象,而不是每个对象调用join
from gevent import monkey
import gevent
import random
import time
# 对整个代码打包,将普通代码换成gevent代码
# 这样遇到 阻塞/延时 就会自动切换协程
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
def coroutine_work(coroutine_name):
for i in range(10):
print(coroutine_name, i)
time.sleep(random.random())
# 协程直接写在这个里面,这样就不用手动依次调用join了
gevent.joinall([
gevent.spawn(coroutine_work, "work1"),
gevent.spawn(coroutine_work, "work2")
])
运行结果:
work1 0
work2 0
work1 1
work1 2
work1 3
work2 1
work1 4
work2 2
work1 5
work2 3
work1 6
work1 7
work1 8
work2 4
work2 5
work1 9
work2 6
work2 7
work2 8
work2 9
from gevent import monkey
import gevent
import urllib.request
# 有耗时操作时需要
monkey.patch_all()
def my_downLoad(url):
print('GET: %s' % url)
resp = urllib.request.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(my_downLoad, 'http://www.baidu.com/'),
gevent.spawn(my_downLoad, 'http://www.itcast.cn/'),
gevent.spawn(my_downLoad, 'http://www.itheima.com/'),
])
协程 --> 线程(不考虑GIL情况下) --> 进程