同步、异步无障碍:Python异步装饰器指南
一、引言
Python异步开发已经非常流行了,一些主流的组件像MySQL、Redis、RabbitMQ等都提供了异步的客户端,再处理耗时的时候不会堵塞住主线程,不但可以提高并发能力,也能减少多线程带来的cpu上下文切换以及内存资源消耗。但在业务开发的时候一些第三方库没有异步的处理方式,例如OSS、CV、其他第三方提供的SDK以及自己封装的函数有耗时等,此时还是需要借助线程来加速,再异步中就不会堵塞主线程,因此封装一个异步装饰器可以更好的处理异步,让代码更简洁。
二、功能分析
-
支持同步函数使用线程加速
-
异、同步函数需支持 await 语法等待返回结果
-
异、同步函数需支持后台任务,无需等待
同步函数使用线程加速
同步函数使用线程,这还是挺简单的使用,内置库的 threading.Thread 就可以实现
import time
import threadingdef task1(name):print(f"Hello {name}")time.sleep(1)print(f"Completed {name}")t1 = threading.Thread(target=task1, args=("hui",))
t2 = threading.Thread(target=task1, args=("wang",))t1.start()
t2.start()t1.join()
t2.join()>>> out
Hello hui
Hello wang
Completed hui
Completed wang
- start()方法用于启动线程执行函数。
- join()方法用于等待线程执行结束。
但这样直接开线程的方式比较暴力,也不太好管理,因此可以想到线程池,进行线程复用与管理。Python内置的 concurrent.futures 模块提供了线程池和进程池的实现与封装。
import time
from concurrent.futures import ThreadPoolExecutordef task2(name):print(f"Hello {name}")time.sleep(1)return f"Completed {name}"with ThreadPoolExecutor(max_workers=2) as executor:future1 = executor.submit(task2, "hui")future2 = executor.submit(task2, "zack")print("ret1", future1.result())
print("ret2", future2.result())>>> out
Hello hui
Hello zack
ret1 Completed hui
ret2 Completed zack
异、同步函数需支持 await 语法
异、同步函数需支持 await 语法等待返回结果,异步函数本身就支持 await语法,这里主要是实现同步函数支持
await 语法,在python中可以await语法的对象有如下几大类:
- 协程对象(coroutine):定义了__await__方法的对象,异步框架中的协程函数都是此类型。
- 任务对象(Task):封装了协程的对象, 如 asyncio 中的 Task, trio中的Task。
- Future对象:表示异步操作结果的对象, 如 concurrent.futures.Future。
- 协程装饰器封装的对象:一些装饰器可以将普通函数或对象包装成可await的对象,如@asyncio.coroutine。
综上,凡是实现了__await__魔术方法的对象或者封装了协程/任务的对象,都可以被await,await会自动把对象交给事件循环运行,等待其完成。
常见的可await对象包括协程函数、任务对象、Future、被@coroutine装饰的函数等,这可以使异步代码更简洁。await对象可以暂停当前协程,等待异步操作完成后再继续执行。
import asyncioasync def coro_demo():print("await coroutine demo")async def task_demo():print("await task demo")async def coro():print("in coro task")# 创建 Task 对象task = asyncio.create_task(coro())await taskasync def future_demo():print("await future demo")future = asyncio.Future()await future# 这个装饰器已经过时
@asyncio.coroutine
def coro_decorated_demo():print("await decorated function demo")async def main():await coro_demo()await task_demo()await future_demo()await coro_decorated_demo()if __name__ == '__main__':asyncio.run(main())>>> out
DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" insteaddef coro_decorated_demo():await coroutine demo
await task demo
in coro task
await future demo
这个 @asyncio.coroutine 协程装饰器已经过时了,都是使用 async、await 语法替代。
下面是实现 __await__ 方法的demo
import asyncioclass AsyncDownloader:def __init__(self, url):self.url = urlself.download_ret = Nonedef __await__(self):print(f'Starting download of {self.url}')loop = asyncio.get_event_loop()future = loop.run_in_executor(None, self.download)yield from future.__await__()return selfdef download(self):print(f'Downloading {self.url}...')# 模拟下载过程import timetime.sleep(2)self.download_ret = f'{self.url} downloaded ok'async def main():print('Creating downloader...')downloader = AsyncDownloader('https://www.ithui.top/file.zip')print('Waiting for download...')downloader_obj = await downloaderprint(f'Download result: {downloader_obj.download_ret}')if __name__ == '__main__':asyncio.run(main())>>> out
Creating downloader...
Waiting for download...
Starting download of https://www.ithui.top/file.zip
Downloading https://www.ithui.top/file.zip...
Download result: https://www.ithui.top/file.zip downloaded ok
用 yield from 来迭代 future对象(符合__await__逻辑),并在结束时return self
异、同步函数需支持后台任务
异步、后台任务的好处与场景
-
减少主程序的等待时间
异步函数可以通过后台任务的方式执行一些耗时操作,如IO操作、网络请求等,而主程序无需等待这些操作完成,可以继续执行其他任务,从而减少程序总体的等待时间。
-
提高程序响应性能
后台任务的异步执行,可以避免主程序被长时间阻塞,从而改善程序的整体响应性能。用户无需长时间等待才能得到响应。
-
解决IO密集型任务阻塞问题
对于网络、文件IO等密集型任务,使用同步执行可能会导致长时间阻塞,而异步后台任务可以很好地解决这个问题,避免资源浪费。
-
良好的用户体验
后台任务的异步处理,给用户的感觉是多个任务同时在执行,实际上CPU在切换处理,这相比线性等待任务完成,可以提供更好的用户体验。
-
适用于不需要实时结果的任务
邮件发送、数据批处理、文件处理等不需要用户即时等待结果的任务非常适合通过异步方式在后台完成。
在python中同异步函数实现后台任务
-
异步函数可以通过 asyncio.create_task 方法实现后台任务
-
同步函数可以通过线程、线程池来实现
import asyncio
import time
from threading import Thread
from concurrent.futures import ThreadPoolExecutorasync def async_bg_task():print('async bg task running')await asyncio.sleep(3)print('async bg task completed')def sync_bg_task():print('sync bg task running')time.sleep(3)print('sync bg task completed')async def main():print('Starting main program')# 异步函数的后台任务asyncio.create_task(async_bg_task())# 同步函数的后台任务# with ThreadPoolExecutor() as executor:# executor.submit(sync_bg_task)# Thread(target=sync_bg_task).start()loop = asyncio.get_running_loop()loop.run_in_executor(executor=ThreadPoolExecutor(), func=sync_bg_task)print('Main program continues')await asyncio.sleep(5)if __name__ == '__main__':asyncio.run(main())>>> ThreadPoolExecutor out
Starting main program
sync bg task running
sync bg task completed
Main program continues
async bg task running
async bg task completed>>> Thread out
Starting main program
sync bg task running
Main program continues
async bg task running
sync bg task completed
async bg task completed>>> run_in_executor out
Starting main program
sync bg task running
Main program continues
async bg task running
async bg task completed
sync bg task completed
看输出结果可以发现在同步函数使用直接使用线程池 ThreadPoolExecutor 执行还是堵塞了主线程,然后 Thread 没有,通过 loop.run_in_executor 也不会阻塞。后面发现 是 with 语法导致的堵塞,with 的根本原因就是它会等待线程池内的所有线程任务完成并回收,所以主线程必须等同步函数结束后才能继续。一开始我还一以为是线程池使用了主线程的线程后面打印线程名称看了下不是,然后调试下就发现了with的问题。
import asyncio
import time
import threading
from concurrent.futures import ThreadPoolExecutorasync def async_bg_task():print(f"async_bg_task In thread: {threading.current_thread().name}")print('async bg task running')await asyncio.sleep(3)print('async bg task completed')def sync_bg_task(num):print(f"sync_bg_task{num} In thread: {threading.current_thread().name}")print(f'sync bg task{num} running')time.sleep(3)print(f'sync bg task{num} completed')async def main():print('Starting main program')# 异步函数的后台任务asyncio.create_task(async_bg_task())# 同步函数的后台任务thread_pool = ThreadPoolExecutor()# with thread_pool as pool:# for i in range(5):# pool.submit(sync_bg_task, i)for i in range(5):thread_pool.submit(sync_bg_task, i)threading.Thread(target=sync_bg_task, args=["thread"]).start()loop = asyncio.get_running_loop()loop.run_in_executor(ThreadPoolExecutor(), sync_bg_task, "loop.run_in_executor")print('Main program continues')print(f"Main program In thread: {threading.current_thread().name}")await asyncio.sleep(5)if __name__ == '__main__':asyncio.run(main())
三、具体封装实现
import asyncio
from concurrent.futures import ThreadPoolExecutor, Executordef run_on_executor(executor: Executor = None, background: bool = False):"""异步装饰器- 支持同步函数使用 executor 加速- 异步函数和同步函数都可以使用 `await` 语法等待返回结果- 异步函数和同步函数都支持后台任务,无需等待Args:executor: 函数执行器, 装饰同步函数的时候使用background: 是否后台执行,默认FalseReturns:"""def _run_on_executor(func):@functools.wraps(func)async def async_wrapper(*args, **kwargs):if background:return asyncio.create_task(func(*args, **kwargs))else:return await func(*args, **kwargs)@functools.wraps(func)def sync_wrapper(*args, **kwargs):loop = asyncio.get_event_loop()task_func = functools.partial(func, *args, **kwargs) # 支持关键字参数return loop.run_in_executor(executor, task_func)# 异步函数判断wrapper_func = async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapperreturn wrapper_funcreturn _run_on_executor
封装成了带参数的装饰器
-
executor: 函数执行器, 装饰同步函数的时候使用
- 可以传递指定的线程池,默认None 根据系统cpu核心数动态创建线程的数量
-
background: 用于标识是否后台执行,默认False
- 有点诟病同步函数的后台任务没有用到这个参数而是使用 await 语法控制,但在使用装饰器时候可以起到后台任务标识作用,别人一看有这个参数就知道是后台任务就不用细看函数业务逻辑
- 后续再看看怎么优化,大家有没有比较好建议
-
loop.run_in_executor(executor, task_func) 方法不支持关键字参数的传递,故而采用 task_func = functools.partial(func, *args, **kwargs) ,来构造一个不带参数的函数就可以方便使用了
测试demo
import asyncio
import time
from concurrent.futures import ThreadPoolExecutorfrom py_tools.decorators.base import run_on_executor
from loguru import loggerthread_executor = ThreadPoolExecutor(max_workers=3)@run_on_executor(background=True)
async def async_func_bg_task():logger.debug("async_func_bg_task start")await asyncio.sleep(1)logger.debug("async_func_bg_task running")await asyncio.sleep(1)logger.debug("async_func_bg_task end")return "async_func_bg_task ret end"@run_on_executor()
async def async_func():logger.debug("async_func start")await asyncio.sleep(1)logger.debug("async_func running")await asyncio.sleep(1)return "async_func ret end"@run_on_executor(background=True, executor=thread_executor)
def sync_func_bg_task():logger.debug("sync_func_bg_task start")time.sleep(1)logger.debug("sync_func_bg_task running")time.sleep(1)logger.debug("sync_func_bg_task end")return "sync_func_bg_task end"@run_on_executor()
def sync_func():logger.debug("sync_func start")time.sleep(1)logger.debug("sync_func running")time.sleep(1)return "sync_func ret end"async def main():ret = await async_func()logger.debug(ret)async_bg_task = await async_func_bg_task()logger.debug(f"async bg task {async_bg_task}")logger.debug("async_func_bg_task 等待后台执行中")loop = asyncio.get_event_loop()for i in range(3):loop.create_task(async_func())ret = await sync_func()logger.debug(ret)sync_bg_task = sync_func_bg_task()logger.debug(f"sync bg task {sync_bg_task}")logger.debug("sync_func_bg_task 等待后台执行")await asyncio.sleep(10)if __name__ == '__main__':asyncio.run(main())
测试详细输出
async_func start
async_func running
async_func ret endasync bg task <Task pending name='Task-2' coro=<async_func_bg_task() running at ...
sync_func start
async_func_bg_task start
async_func start
async_func start
async_func startsync_func running
async_func_bg_task running
async_func running
async_func running
async_func runningasync_func_bg_task end
sync_func ret endsync_func_bg_task start
sync bg task <Future pending cb=[_chain_future.<locals>._call_check_cancel() at ...
sync_func_bg_task 等待后台执行
sync_func_bg_task running
sync_func_bg_task end
四、源代码
HuiDBK/py-tools: 打造 Python 开发常用的工具,让Coding变得更简单 (github.com)
题外话
“不是只有程序员才要学编程?!”
认真查了一下招聘网站,发现它其实早已变成一项全民的基本技能了。
连国企都纷纷要求大家学Python!

世界飞速发展,互联网、大数据冲击着一切,各行各业对数据分析能力的要求越来越高,这便是工资差距的原因,学习编程顺应了时代的潮流。
在这个大数据时代,从来没有哪一种语言可以像Python一样,在自动化办公、爬虫、数据分析等领域都有众多应用。
更没有哪一种语言,语法如此简洁易读,消除了普通人对于“编程”这一行为的恐惧,从小学生到老奶奶都可以学会。
《2020年职场学习趋势报告》显示,在2020年最受欢迎的技能排行榜,Python排在第一。

它的角色类似于现在Office,成了进入职场的第一项必备技能。
如果你也想增强自己的竞争力,分一笔时代的红利,我的建议是,少加点班,把时间腾出来,去学一学Python。
因为,被誉为“未来十年的职场红利”的Python,赚钱、省钱、找工作、升职加薪简直无所不能!
目前,Python人才需求增速高达**174%,人才缺口高达50万,**部分领域如人工智能、大数据开发, 年薪30万都招不到人!
感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。


二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典


简历模板
如有侵权,请联系删除。
相关文章:
同步、异步无障碍:Python异步装饰器指南
一、引言 Python异步开发已经非常流行了,一些主流的组件像MySQL、Redis、RabbitMQ等都提供了异步的客户端,再处理耗时的时候不会堵塞住主线程,不但可以提高并发能力,也能减少多线程带来的cpu上下文切换以及内存资源消耗。但在业务…...
CodeSite for .NET Crack
CodeSite for .NET Crack CodeSite for.NET与Visual Studio集成,通过实时查看器日志记录系统提供对代码执行的更深入了解,该系统有助于在本地或远程执行代码时快速查找问题。超越传统的断点调试,在应用程序继续运行时记录应用程序的执行&…...
基于IMX6ULLmini的linux裸机开发系列九:时钟控制模块
时钟控制模块 核心 4个层次配置芯片时钟 晶振时钟 PLL与PFD时钟 PLL选择时钟 根时钟/外设时钟 系统时钟来源 RTC时钟源:32.768KHz 系统时钟:24MHz,作为芯片的主晶振使用 PLL和PFD倍频时钟 7路锁相环电路(每个锁相环电路…...
【数据结构与算法】1. 绪论
1. 绪论 1.1 数据结构 1.1.1 数据结构的基本概念 1.1.2 数据结构的三要素 数据结构三要素: 逻辑结构 划分方法一: 线性结构:线性表、栈、队列、串非线性结构:树、图 划分方法二: 集合结构线性结构树形结构网状&…...
2023年京东儿童智能手表行业数据分析(京东销售数据分析)
儿童消费市场向来火爆,儿童智能手表作为能够实现定位导航,信息通讯,SOS求救,远程监听,智能防丢等多功能的智能可穿戴设备,能够通过较为精准的定位功能和安全防护能力保障儿童的安全,因而广受消费…...
数据结构(6)
2-3查找树 2-结点:含有一个键(及其对应的值)和两条链,左链接指向2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。 3-结点:含有两个键(及其对应的值)和三条链,左链接指向的2-3树中的键都小于该结点&a…...
C++学习|CUDA安装和配置
CUDA安装和配置 Windows下安装CUDAVS项目配置CUDA Windows下安装CUDA 第一步:先看自己NIVIDIA显卡适合什么版本的CUDA。打开电脑的“NIVIDIA控制面板”->系统信息->组件。会看到我的显卡驱动最高支持的CUDA版本是11.4.56。 第二步:去CUDA官网&…...
3.若依前后端分离版开发用户自定义配置表格功能
一、背景 在项目上线测试的时候,关于同一个界面的表格,不同的用户会出现不同的字段排列需求,有些用户希望把A字段排在最前面,有些用户则希望A字段不显示。针对这种情况,开发一个表格自定义配置的功能,每个…...
【操作系统】24王道考研笔记——第三章 内存管理
第三章 内存管理 一、内存管理概念 1.基本概念 2.覆盖与交换 覆盖技术: 交换技术: 总结: 3.连续分配管理方式 单一连续分配 固定分区分配 动态分区分配 动态分区分配算法: 总结: 4.基本分页存储管理 定义…...
Spring缓存深入解析:@Cacheable的使用详解
摘要:在本文中,我们将深入研究Spring框架中的Cacheable注解。我们会通过详细的Java示例,探讨如何使用这个功能强大的注解来提升应用程序性能。 一、什么是缓存? 在计算机科学中,缓存是一种存储技术,用于保…...
软件配置安装(破解)--- jdk下载配置
下载jdk 如果有oracle账号的话直接登录下载你想要的版本 不然可以尝试镜像站 HUAWEI镜像:https://repo.huaweicloud.com/java/jdk/ 安装 配置(细节) 这里的JAVA_HOME就是java的家,也就是解压(或安装)之后的java的目录ÿ…...
idea使用docker生成镜像(打包镜像,导入镜像,导出镜像)
1:先下载安装dockerdesktop,安装成功后 2: 在cmd执行docker -v,查看安装的docker版本 C:\Users\dell>docker -v Docker version 24.0.5, build ced09963:需要启动 dockerdesktop应用,才算启动docker&a…...
wazuh环境配置
目录 一、wazuh的安装 1.1官方仓库安装 1.2虚拟机OVA安装 1.2.1 然后执行下面命令 1.2.2 这里还要下载脚本和config.yml配置文件,用来生成证书编辑 1.2.3然后编辑config.yml文件,将下面的三个IP地址改为一样的 1.2.4运行./wazuh-certs-tool.sh以…...
【Linux】Linux下常用压缩解压缩指令及选项小结
0x00 前言 版本信息:Ubuntu 18.04.6 LTS 最后更新日期:2023.8.22 0x01 Linux下常用压缩解压缩指令小结 1.gzip指令 gzip file:压缩file文件为file.gz ,但是只能压缩文件不能压缩目录,且不保留源文件。若想打包目录…...
香蕉派社区推出带10G SFP+ 端口的Banana Pi BPI-R4 Wifi7开源路由器
香蕉派BPI-R4 根据著名Banana Pi品牌背后的公司Sinovoip提供的初步信息,他们即将推出的Banana Pi BPI-R4路由器板目前正在开发中。与之前的 Banana Pi R3 板相比,这在规格上将有显着提升。这就是我们目前所知道的。 您可以选择 R4 板的两种不同配置。具…...
A 题:震源属性识别模型构建与震级预测 :代码分析:
问题 1: 针对附件 1~8 中的地震波数据,找出一系列合适的指 标与判据,构建震源属性识别模型,进行天然地震事件(附件 1~7) 与非天然地震事件(附件 8)的准确区…...
源码分析CompletableFuture使用默认线程池ForkJoinPool的弊端
先说结论: 假如有20CompletableFuture任务并发执行时,都使用默认线程池ForkJoinPool,但cpu的核心数又小于3,那么就会新建20个线程(不使用默认线程池了),这20个线程相互竞争cpu资源和内存&#x…...
连接pgsql数据库 sslmode sslrootcert sslkey sslcert 参数的作用
sslmode 参数的作用 sslmode 参数用于指定数据库连接时使用的 SSL 加密模式。SSL(Secure Sockets Layer)是一种加密协议,用于保护数据在客户端和服务器之间的传输过程,以增加数据传输的安全性。sslmode 参数可以设置不同的值&…...
从零学算法3
3.给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”&…...
宠物小程序开发
在当今社会,宠物已成为许多人生活中不可或缺的一部分。宠物市场的持续增长为创业者提供了巨大的商机。然而,作为一个创业者,要在竞争激烈的宠物市场中脱颖而出并不容易。因此,开发一个专属于自己的宠物小程序成为了解决这一难题的…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
