python 并发
并发与并行的区别
并发(Concurrency)和 并行(Paralle)是两个相关但不同的概念。
并行:指的是在同一时刻,多个任务可以同时进行,彼此之间互不干扰。在这种情况下,多个处理单元(如多核处理器)同时执行不同的任务。并行处理意味着任务之间真正同时执行。
并发:指的是在一段时间内,多个任务交替进行,每个任务都在一段时间内得到执行。尽管这些任务可能在同一时刻被处理,但它们之间可能交替执行或并行执行(如果有多个处理单元)。并发强调的是多个任务之间存在时间上的交替,而不一定是真正的同时执行。
举例来说:
假设有一条高速公路,有双向4车道。每辆车(数据)可以在自己的车道上行驶(传输),这是并行。在同一时刻,每条车道上可能同时有车辆行驶,这是并行。而在一段时间内,有多辆车要通过,这是并发。
总之,并行是指多个任务在同一时刻真正同时执行,而并发是指多个任务在一段时间内交替执行,可能同时发生,也可能不同时发生。并行通常是通过水平扩展方式来解决并发问题的一种手段。
进程和线程
进程(Process)
进程是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,也是操作系统结构的基础。以下是关于进程的一些重要概念:
- 定义:进程是程序被操作系统加载到内存中后的执行实例,包含指令和数据,是程序执行的环境。
- 进程和程序关系:程序是源代码编译后的文件,而进程是程序在内存中的执行实例,也是线程的容器。
- 特点:每个进程都有自己独立的内存空间,相互之间不会直接影响,可以独立运行和管理。
- 父子关系:Linux 中的进程有父进程和子进程之分,而在 Windows 中,进程之间是平等关系。
线程(Thread)
线程是操作系统能够进行运算调度的最小单位,是进程中的实际运作单位。以下是关于线程的一些重要概念:
- 定义:线程是程序执行流的最小单元,有时被称为轻量级进程(Lightweight Process,LWP)。
- 组成:一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆、栈组成。
- 特点:线程是进程的一部分,共享相同的地址空间和其他资源,但拥有独立的栈空间和指令执行流程。
- 创建效率:在许多系统中,创建一个线程比创建一个进程快 10-100 倍。
进程、线程的理解
- 现代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源;
- 进程就是独立的王国,进程间不可以随便的共享数据;
- 线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的堆栈。
线程的状态
状态 | 含义 |
---|---|
就绪(Ready) | 线程能够运行,但在等待被调度。可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程抢占。 |
运行(Running) | 线程正在运行。 |
阻塞(Blocked) | 线程等待外部事件发生而无法运行,如 I/O 操作。 |
终止(Terminated) | 线程完成,或退出,或被取消。 |
python中的进程和线程
运行程序会启动一个解释器进程,线程共享一个解释器进程;
python的线程开发
python的线程开发使用标准库threading
;
进程靠线程执行代码、至少有一个主线程,其它线程是工作线程;
主线程是第一个启动的线程;
父线程: 如果线程A中启动了一个线程B,A就是B的父线程;
子线程: B就是A的子线程。
Thread类
# 签名 def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
参数名 | 含义 |
---|---|
target |
线程调用的对象,就是目标函数。 |
name |
为线程起个名字。 |
args |
为目标函数传递实参,元组。 |
kwargs |
为目标函数关键字传参,字典。 |
线程启动
import threading import time # 定义一个函数 def worker(): print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.3) print('working...' + '===' * i + '>' + '###' * (int(num) - i - 1), end='\r') print('I am finished ~~~') t = threading.Thread(target=worker, name='worker') # target 是一个函数,name 是线程的名字,只创建了一个线程管理对象 t.start() # 启动线程 ,调用系统调用,创建真正的操作系统的线程,启动运行target函数
线程退出
Python没有提供退出的方法, 线程在下面情况时退出;
- 线程函数内语句执行完毕;
- 线程函数中抛出未处理的异常;
import threading import time # 定义一个函数 def worker(): print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.3) print('working...' + '===' * i + '>' + '###' * (int(num) - i - 1), end='\r') print('I am finished ~~~') t = threading.Thread(target=worker, name='worker') # target 是一个函数,name 是线程的名字,只创建了一个线程管理对象 t.start() # 启动线程 ,调用系统调用,创建真正的操作系统的线程,启动运行target函数
因此在线程中应该尽可能捕获异常
线程传参
# 线程的传参问题 def add(x,y): print() print (x + y) return x + y # 目前情况下, 线程的返回值拿不到 # t = threading.Thread(target=add, args=(3,4), name='add') t = threading.Thread(target=add, name='add', args=(), kwargs={'x':3, 'y':4}) t.start()
threading的属性和方法
名称 | 含义 |
---|---|
current_thread() |
返回当前线程对象。 |
main_thread() |
返回主线程对象。 |
active_count() |
当前处于alive状态的线程个数。 |
enumerate() |
返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程。 |
get_ident() |
返回当前线程的ID,非0整数。 |
# # 定义一个函数 def showthreadinfo(): print(threading.main_thread(), threading.current_thread(), threading.active_count()) # 返回主线程的线程对象,返回当前线程的线程对象,返回当前活跃的线程数 def worker(): showthreadinfo() print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.2) if i != 20: print('working...' + '===' * i + '>' + '###' * (int(num) - i - 1), end='\r') else: print('\rNow 初始化工具 完成了 !!!', end='\r') time.sleep(1) print('I am finished ~~~') t = threading.Thread(target=worker, name='worker') # target 是一个函数,name 是线程的名字,只创建了一个线程管理对象 time.sleep(2) showthreadinfo() t.start() # 启动线程 ,调用系统调用,创建真正的操作系统的线程,启动运行target函数 # <_MainThread(MainThread, started 7982456512)> <_MainThread(MainThread, started 7982456512)> 1 # <_MainThread(MainThread, started 7982456512)> <Thread(worker, started 6106722304)> 2 # I am working ~~~ # I am finished ~~~============================================================================================================================================>
import threading import time class MyThread(threading.Thread): def start(self) -> None: print('start ----') super().start() def run(self) -> None: print('run ----') super().run() def worker(): print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.2) if i != 20: print('working...' + '===' * i + '>' + '###' * (int(num) - i - 1), end='\r') else: print('\r]\t\t\t\t\tNow 初始化工具 完成了 !!!', end='\r') time.sleep(1) print('I am finished ~~~') t = MyThread(target=worker,name='worker') time.sleep(2) # t.start() 调用 系统调用,创建真正的操作系统的线程,启动运行target函数,且只能start一次,否则报错: RuntimeError: threads can only be started once #t.run() # run是直接调函数,不会创建新的线程,只是调用了target函数 t.start() # start 是创建线程后,并启动线程 x = threading.main_thread() print(type(x).mro()) print(x.name, x.ident, x.is_alive()) # 正常情况下,主线程就要是管理线程的,如果有任务,就创建一个线程管理对象,然后启动子线程 while True: time.sleep(1) if t.is_alive(): print(threading.active_count(),threading.enumerate()) if threading.active_count() == 1: break #print('I will restart t ~~~') # t.start() 因此要注意,只要线程启过一次,就不能再启动了,否则报错: RuntimeError: threads can only be started once # start ---- # run ---- # I am working ~~~ # [<class 'threading._MainThread'>, <class 'threading.Thread'>, <class 'object'>] # MainThread 7982456512 True # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]##################################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]=====>############################################################### # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]====================>################################################ # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]===================================>################################# # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]==================================================>################## # 2 [<_MainThread(MainThread, started 7982456512)>, <MyThread(worker, started 6146437120)>]=================================================================>### # I am finished ~~~============================================================================================================================================> # start() 创建一个系统线程,并把一个未来要执行的函数扔进去,然后启动线程,为线程关联目标函数、因此就是并行的效果; # run() 只是调用了target函数,不会创建新的线程,只是调用了target函数,因此是串行的效果;
- 同时启动多个线程、查看对应交替运行
当使用start()
方法启动后,进程内有多个活动的线程并行工作,就是多线程;
一个进程中至少且一个线程,并作为程序的入口,这个线程就是主线程;
一个进程至少有一个主线程;
其他线程称为工作线程;
import threading import time import sys from termcolor import colored class MyThread(threading.Thread): def start(self) -> None: print('start ----') super().start() def run(self) -> None: print('run ----') super().run() def worker(f='green'): print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.2) if i != 20 and f == 'green': print(colored('working...' + '===' * i + '>' + '###' * (int(num) - i - 1),'green'), end='\r') elif i != 20 and f == 'red': print(colored('working...' + '===' * i + '>' + '###' * (int(num) - i - 1),'red'), end='\r') print('I am finished ~~~') t = MyThread(target=worker,name='worker') time.sleep(2) # t.start() 调用 系统调用,创建真正的操作系统的线程,启动运行target函数,且只能start一次,否则报错: RuntimeError: threads can only be started once #t.run() # run是直接调函数,不会创建新的线程,只是调用了target函数 t.start() # start 是创建线程后,并启动线程 t = MyThread(target=worker,name='new_worker',kwargs={'f':'red'}) t.start() x = threading.main_thread() print(type(x).mro()) print(x.name, x.ident, x.is_alive())
daemon
t = threading.Thread(target=worker, name='worker', daemon=True) t.start() #t.setDaemon(threading.main_thread().daemon) time.sleep(3) # daemon=True 守护线程,主线程没有任务退出,守护线程也会退出,就不管子线程的任务是否完成 # daemon=False 主线程退出,子线程还会继续执行 # 除主线程外,回眼看一下子线程的状态,只要有一个non-daemon线程还在运行,主线程就不会退出,但不会管daemon线程是否还在运行 # 主线程会等到最后一个non-daemon线程退出后,再退出,但不会管daemon线程是否还在运行; print(threading.main_thread().daemon,'state') # 主线程的状态 False 主线程是non-daemon线程
名称 | 含义 |
---|---|
daemon |
表示线程是否是daemon线程,这个值必须在 start() 之前设置,否则引发 RuntimeError 。 |
isDaemon() |
是否是daemon线程。 |
setDaemon() |
设置为daemon线程,必须在 start() 方法之前设置。 |
线程的 daemon 属性总结
以下是关于线程的 daemon 属性的总结:
- 线程具有一个
daemon
属性,可以手动设置为 True 或 False,也可以不设置,此时取默认值 None。 - 如果不设置
daemon
属性,那么会取当前线程的daemon
属性来设置它。 - 主线程是 non-daemon 线程,即
daemon=False
。 - 从主线程创建的所有线程如果不设置
daemon
属性,则默认都是daemon=False
,即非守护线程。 - 当 Python 程序中没有活跃的非守护线程运行时,程序会退出。换句话说,除了主线程之外,剩下的所有线程都是守护线程,主线程只有等待它们结束后才能退出。
这个特性可以用于控制程序的退出行为,通过合理设置守护线程和非守护线程,可以确保程序在需要时可以正常退出,而不会因为线程未结束而被阻塞。
join()方法
import threading import time import sys from termcolor import colored class MyThread(threading.Thread): def start(self) -> None: print('start ----') super().start() def run(self) -> None: print('run ----') super().run() def worker(f='green'): print('I am working ~~~') num = 50 for i in range(int(num)): time.sleep(0.2) if i != 20 and f == 'green': print(colored('[ working... ]' + '===' * i + '>' + '###' * (int(num) - i - 1),'green'), end='\r') elif i != 20 and f == 'red': print(colored('[ working... ]' + '===' * i + '>' + '###' * (int(num) - i - 1),'red'), end='\r') print('\nI am finished ~~~') x = threading.main_thread() t = threading.Thread(target=worker, name='worker', daemon=True) t.start() print(type(x).mro()) print(x.name, x.ident, x.is_alive()) time.sleep(3) #t.join() # 主线程阻塞态,一直到条件满足,主线程才会继续执行, join 谁 ,join t 就是等待t线程执行完毕,主线程才会继续执行,即阻塞等t线程执行完毕 print('main thread is finished ~~~') # t.join()执行结果 # I am working ~~~ # [<class 'threading._MainThread'>, <class 'threading.Thread'>, <class 'object'>] # MainThread 7982456512 True # [ working... ]===================================================================================================================================================> # I am finished ~~~ # main thread is finished ~~~ # 注释掉t.join()执行结果 # I am working ~~~ # [<class 'threading._MainThread'>, <class 'threading.Thread'>, <class 'object'>] # MainThread 7982456512 True # main thread is finished ~~~==========================>############################################################################################################
线程的 join()
方法总结
join()
方法是线程的标准方法之一,以下是关于 join()
方法的总结:
- 当一个线程调用另一个线程的
join()
方法时,调用者线程将被阻塞,直到被调用线程终止,或者等待超时。 - 一个线程可以被多个线程调用其
join()
方法,这意味着一个线程可以等待多个其他线程的结束。 timeout
参数可以指定调用者等待的时间,如果没有设置超时,调用者将一直等待被调用线程结束。- 调用谁的
join()
方法,就是等待谁的结束,即调用者线程将等待被调用线程的结束。
join()
方法常用于控制线程的执行顺序,确保某些线程在其他线程结束后再执行。它可以协调线程之间的执行顺序,避免出现数据竞争等问题。
daemon 线程的应用场景
daemon 线程是一种特殊类型的线程,在某些场景下非常适用。以下是 daemon 线程的主要应用场景:
后台任务:如发送心跳包、监控等后台任务。这种场景下,daemon 线程可以在后台默默地执行任务,不会影响主线程的执行。
主线程工作才有用的线程:有些线程的工作依赖于主线程,如果主线程退出,那么这些线程也就没有存在的必要了。此时可以将这些线程设置为 daemon=True,这样当主线程退出时,这些工作线程也会随之退出。
随时可以被终止的线程:有些线程的工作是随时可以被终止的,不需要等待所有任务完成。这种情况下,可以将这些线程设置为 daemon,以便在主线程退出时一起退出。
daemon 线程的特点是一旦创建就可以忘记它,不需要手动关闭线程,只需要关心主线程的退出即可。这样简化了程序员手动关闭线程的工作,提高了程序的健壮性和可维护性。
Thread-Local
import threading import time import sys from termcolor import colored import logging FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO, format=FORMAT) class MyThread(threading.Thread): def start(self) -> None: print('start ----') super().start() def run(self) -> None: print('run ----') super().run() class A(): def __init__(self): self.x = 0 global_data = A() # 那么使用threading.local()创建的对象,每个线程都有自己的数据,互不影响 global_data = threading.local() def worker(f='green'): logging.info('I am working ~~~') num = 50 global_data.x = 0 # x = 0 # x 是局部变量,局部变量和每一次函数调用的栈帧有关,每次调用函数都会创建一个新的栈帧,所以x的值不会受到其他线程的影响 # 如果多个线程运行,使用的都是局部变量,是很安全的 for i in range(int(num)): time.sleep(0.1) global_data.x += 1 if f == 'green': print(colored('[ working... ]' + '===' * i + '>' + '###' * (int(num) - i - 1),'green'), end='\r') elif f == 'red': print(colored('[ working... ]' + '===' * i + '>' + '###' * (int(num) - i - 1),'red'), end='\r') logging.info('\nI am finished ~~~ {} ==> {} '.format(global_data.x, threading.current_thread().name)) for i in range(10): # 判断i 为偶数时,创建线程对象 if i % 2 == 0: t = threading.Thread(target=worker,name='work-{}'.format(i),kwargs={'f':'red'},daemon=False) t.start() else: t = threading.Thread(target=worker,name='work-{}'.format(i),kwargs={'f':'green'},daemon=False) t.start() print('main thread is finished ~~~')
作业
- 用面向对象实现 LinkedList 链表
单向链表
实现方法
append(value)
: 在链表尾部添加节点iternodes()
: 遍历链表,返回所有节点的值
双向链表
实现方法
append(value)
: 在链表尾部添加节点pop()
: 弹出尾部节点insert(value, index)
: 在指定位置插入节点remove(index)
: 移除指定位置的节点iternodes()
: 遍历链表,返回所有节点的值
提供的特殊方法
__getitem__(index)
: 获取指定位置节点的值__iter__()
: 迭代链表,返回节点的值__setitem__(index, value)
: 设置指定位置节点的值
- 链表
实现LinkedList链表
链表有单向链表、双向链表
对于链表来说
- 每一个结点是一个独立的对象,结点对象有内容,还知道下一跳是什么。
- 链表则是一个容器,它内部装着一个个结点对象。
所以,建议设计2个类,一个是结点Node类,一个是链表LinkedList类。
如同一个箱子是容器,里面放的小球就是一个个节点。
如同一串珠子,节点对象是一个个珠子,每一颗珠子关联其前后的珠子,所有珠子形成串珠的概念,相当于容器。一串珠子可以从固定一头提起,这是单向链表;如果这串珠子可以从两头中的任意一头提起,这就是双向链表。
- 单向链表
from __future__ import annotations # 使用打补丁的方式,使得Node类可以在定义的时候使用Node类型提示 class Node: def __init__(self,value,next:Node=None): #3.10之后的版本,可以使用Node作为类型提示 self.item = value self.next = next def __repr__(self) -> str: return str(self.item) class LinkedList: # 容器类 def __init__(self): self.head = None self.tail = None def append(self,item): node = Node(item) # empty if self.head is None: self.head = node self.tail = node else: # else not empty self.tail.next = node self.tail = node return self def internodes(self): current:Node = self.head while current: yield current current = current.next ll = LinkedList() ll.append(1).append(2).append(3) for node in ll.internodes(): print(node) # 1 2 3
双向链表
pop
方法实现
from __future__ import annotations # 使用打补丁的方式,使得Node类可以在定义的时候使用Node类型提示 # 双向链表 class Node: def __init__(self,value,next:Node=None,prev:Node=None): #3.10之后的版本,可以使用Node作为类型提示 self.item = value self.next = next self.prev = prev def __repr__(self) -> str: return "{} <== {} ==> {}".format( #self.prev,# 注意这个是实例,会导致递归调用 self.prev.item if self.prev else None, self.item, self.next.item if self.next else None ) # def __str__(self) -> str: # return str(self.item) class LinkedList: # 容器类 def __init__(self): self.head = None self.tail = None def append(self,item): # 实现尾部添加 node = Node(item) # empty if self.head is None: #empty self.head = node else: self.tail.next = node node.prev = self.tail # 原来的尾节点 self.tail = node # 更新尾节点 return self # 链式调用 def pop(self): # 实现尾部弹出 if self.head is None: raise Exception('empty') # 不为空 # 假设只有一个元素 node = self.tail if self.head is self.tail: self.head = None self.tail = None else: # 假设有两个元素 prev = node.prev prev.next = None self.tail = prev return node def internodes(self,reverse=False): current:Node = self.head if not reverse else self.tail while current: yield current current = current.next if not reverse else current.prev ll = LinkedList() ll.append(1).append(2).append(3).append('end') ll.pop() for node in ll.internodes(): print(node) # 1 2 3 # None <== 1 ==> 2 # 1 <== 2 ==> 3 # 2 <== 3 ==> None
insert
方法
from __future__ import annotations # 使用打补丁的方式,使得Node类可以在定义的时候使用Node类型提示 # 双向链表 class Node: def __init__(self,value,next:Node=None,prev:Node=None): #3.10之后的版本,可以使用Node作为类型提示 self.item = value self.next = next self.prev = prev def __repr__(self) -> str: return "{} <== {} ==> {}".format( #self.prev,# 注意这个是实例,会导致递归调用 self.prev.item if self.prev else None, self.item, self.next.item if self.next else None ) # def __str__(self) -> str: # return str(self.item) class LinkedList: # 容器类 def __init__(self): self.head = None self.tail = None def append(self,item): # 实现尾部添加 node = Node(item) # empty if self.head is None: #empty self.head = node else: self.tail.next = node node.prev = self.tail # 原来的尾节点 self.tail = node # 更新尾节点 return self # 链式调用 def insert(self,index,value): # 指定索引位置插入 if index< 0: raise IndexError('Not negative index {}'.format(index)) current = None for i,node in enumerate(self.internodes()): if index == i: current = node break else: self.append(value) # 尾部插入写完了 return # 说明了什么 ?: index >= 0 and index < length 找到插入点,至少一个元素 # 只有一个元素 node = Node(value) prev = current.prev if index == 0: # 2 头部插入 self.head = node else: # 3 中间插入 node.prev = prev prev.next = node node.next = current current.prev = node def pop(self): # 实现尾部弹出 if self.head is None: raise Exception('empty') # 不为空 # 假设只有一个元素 node = self.tail if self.head is self.tail: self.head = None self.tail = None else: # 假设有两个元素 prev = node.prev prev.next = None self.tail = prev return node def internodes(self,reverse=False): current:Node = self.head if not reverse else self.tail while current: yield current current = current.next if not reverse else current.prev ll = LinkedList() ll.append(1).append(2) ll.append(3).append('end') for node in ll.internodes(True): print(node) # 1 2 3 print('-----------') ll.pop() ll.pop() for node in ll.internodes(True): print(node) ll.insert(0,'start') ll.insert(3,'end') ll.insert(3,3) print('-----------') for node in ll.internodes(): print(node) # 1 2 3 # 3 <== end ==> None # 2 <== 3 ==> end # 1 <== 2 ==> 3 # None <== 1 ==> 2 # ----------- # 1 <== 2 ==> None # None <== 1 ==> 2 # ----------- # None <== start ==> 1 # start <== 1 ==> 2 # 1 <== 2 ==> 3 # 2 <== 3 ==> end # 3 <== end ==> None
remove
方法
from __future__ import annotations # 使用打补丁的方式,使得Node类可以在定义的时候使用Node类型提示 # 双向链表 class Node: def __init__(self,value,next:Node=None,prev:Node=None): #3.10之后的版本,可以使用Node作为类型提示 self.item = value self.next = next self.prev = prev def __repr__(self) -> str: return "{} <== {} ==> {}".format( #self.prev,# 注意这个是实例,会导致递归调用 self.prev.item if self.prev else None, self.item, self.next.item if self.next else None ) # def __str__(self) -> str: # return str(self.item) class LinkedList: # 容器类 def __init__(self): self.head = None self.tail = None def append(self,item): # 实现尾部添加 node = Node(item) # empty if self.head is None: #empty self.head = node else: self.tail.next = node node.prev = self.tail # 原来的尾节点 self.tail = node # 更新尾节点 return self # 链式调用 def insert(self,index,value): # 指定索引位置插入 if index< 0: raise IndexError('Not negative index {}'.format(index)) current = None for i,node in enumerate(self.internodes()): if index == i: current = node break else: self.append(value) # 尾部插入写完了 return # 说明了什么 ?: index >= 0 and index < length 找到插入点,至少一个元素 # 只有一个元素 node = Node(value) prev = current.prev if index == 0: # 2 头部插入 self.head = node else: # 3 中间插入 node.prev = prev prev.next = node node.next = current current.prev = node def pop(self): # 实现尾部弹出 if self.head is None: raise Exception('empty') # 不为空 # 假设只有一个元素 node = self.tail if self.head is self.tail: self.head = None self.tail = None else: # 假设有两个元素 prev = node.prev prev.next = None self.tail = prev return node def remove(self,index): # 删除指定索引位置的元素 if self.head is None: raise Exception('empty') if index < 0: raise IndexError('Not negative index {}'.format(index)) current = None for i , node in enumerate(self.internodes()): if index == i: current = node break else: # 没有找到,该追加了 raise IndexError('Index out of range:{}'.format(index)) # 找到了 prev = current.prev next = current.next if self.head is self.tail: # only one element self.head = None self.tail = None elif prev is None: # 不止一个元素, 或者current is self.head self.head = next next.prev = None elif next is None: #不止一个元素, 移除末尾 self.tail = prev prev.next = None else:# 不止一个元素,中能是中间 prev.next = next next.prev = prev del current # 释放内存,回收垃圾 def internodes(self,reverse=False): current:Node = self.head if not reverse else self.tail while current: yield current current = current.next if not reverse else current.prev ll = LinkedList() ll.append(1).append(2) ll.append(3).append('end') for node in ll.internodes(True): print(node) # print('-----------') ll.pop() ll.pop() for node in ll.internodes(True): print(node) ll.insert(0,'start') ll.insert(3,'end') ll.insert(3,3) print('-----------') for node in ll.internodes(): print(node) # print('============') ll.remove(4) ll.remove(0) for node in ll.internodes(): print(node)
- 开始完善容器功能
from __future__ import annotations # 使用打补丁的方式,使得Node类可以在定义的时候使用Node类型提示 # 双向链表 class Node: def __init__(self,value,next:Node=None,prev:Node=None): #3.10之后的版本,可以使用Node作为类型提示 self.item = value self.next = next self.prev = prev def __repr__(self) -> str: return "{} <== {} ==> {}".format( #self.prev,# 注意这个是实例,会导致递归调用 self.prev.item if self.prev else None, self.item, self.next.item if self.next else None ) # def __str__(self) -> str: # return str(self.item) class LinkedList: # 容器类 def __init__(self): self.head = None self.tail = None self._size = 0 # 用于记录链表的长度 size = property(lambda self: self._size) # 只读属性, 重写size方法 def __len__(self): return self.size # 重写len方法 def append(self,item): # 实现尾部添加 node = Node(item) # empty if self.head is None: #empty self.head = node else: self.tail.next = node node.prev = self.tail # 原来的尾节点 self.tail = node # 更新尾节点 self._size += 1 return self # 链式调用 def insert(self,index,value): # 指定索引位置插入 # if index< 0: # raise IndexError('Not negative index {}'.format(index)) # current = None # for i,node in enumerate(self.internodes()): # if index == i: # current = node # break # else: # self.append(value) # 尾部插入写完了 # return if index >= len(self): self.append(value) return if index < -len(self): index = 0 current = self[index] # 1. 重写__getitem__方法 # 说明了什么 ?: index >= 0 and index < length 找到插入点,至少一个元素 # 只有一个元素 node = Node(value) prev = current.prev if index == 0: # 2 头部插入 self.head = node else: # 3 中间插入 node.prev = prev prev.next = node node.next = current current.prev = node self._size += 1 def pop(self): # 实现尾部弹出 if self.head is None: raise Exception('empty') # 不为空 # 假设只有一个元素 node = self.tail if self.head is self.tail: self.head = None self.tail = None else: # 假设有两个元素 prev = node.prev prev.next = None self.tail = prev self._size -= 1 return node def remove(self,index): # 删除指定索引位置的元素 if self.head is None: raise Exception('empty') # if index < 0: # raise IndexError('Not negative index {}'.format(index)) # current = None # for i , node in enumerate(self.internodes()): # if index == i: # current = node # break # else: # 没有找到,该追加了 # raise IndexError('Index out of range:{}'.format(index)) current = self[index] # 找到了 prev = current.prev next = current.next if self.head is self.tail: # only one element self.head = None self.tail = None elif prev is None: # 不止一个元素, 或者current is self.head self.head = next next.prev = None elif next is None: #不止一个元素, 移除末尾 self.tail = prev prev.next = None else:# 不止一个元素,中能是中间 prev.next = next next.prev = prev self._size -= 1 del current # 释放内存,回收垃圾 def internodes(self,reverse=False): current:Node = self.head if not reverse else self.tail while current: yield current current = current.next if not reverse else current.prev # def __iter__(self): # yield from self.internodes() # # return self.internodes() __iter__ = internodes # 重写__iter__方法, 等价于上面的代码 def __reversed__(self): # 1. 实现该方术方法,reversed 优先使用 # 2. 如果没有实现该方法,那么使用__len__ __getitem__方法 return self.internodes(reverse=True) def __getitem__(self,index): if index >= len(self) or index < -len(self): raise IndexError('Index out of range: {}'.format(index)) # if index >= 0: # for i ,node in enumerate(self.internodes(False),0): # if index == i: # return node # if index < 0: # for i ,node in enumerate(self.internodes(True),1): # if -index == i: # return node # 开始简化以上判断 reverse = False if index >= 0 else True start = 0 if index >= 0 else 1 for i,node in enumerate(self.internodes(reverse),start): if abs(index) == i: return node def __setitem__(self,index,value): self[index].item =value ll = LinkedList() ll.append(0).append(1).append(2) ll.append(3).append('end') ll[-1] = 'end2' for i in range(5): print(ll[i]) print('-'* 30 ) for i in range(-1,-6,-1): print(ll[i])