前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python高级进阶技术——IO操作、进程和线程操作【建议收藏】

Python高级进阶技术——IO操作、进程和线程操作【建议收藏】

作者头像
灰小猿
发布2022-05-05 20:40:45
7250
发布2022-05-05 20:40:45
举报

本篇文章和大家分享Python高阶开发中详细的IO操作、线程和进程操作!,建议先收藏之后慢慢学习!

目录

写在前面

一、Python输入输出——IO操作

1、文件读写

(1)打开文件

(2)写入文件

(3)读取文件

(4)关闭文件

2、文件系统操作

3、序列化和反序列化

(1)pickle

(2)JSON

二、让你的代码更加灵活——进程和线程操作

1、进程操作

2、线程操作

(1)线程锁


写在前面

Hello,你好呀!我是灰小猿,一个超会写bug的程序猿!本想彪上一手好bug,奈何技术太差,只能苟且搞输出!

近期和大家分享了很多关于Python入门进阶相关的文章,帮助了很多小伙伴了解并深入的学习到了Python开发,在这里为大家安利上文章链接,有想学习的小伙伴可以收藏阅读

常见报错及解决方案:

全网最值得收藏的Python常见报错及其解决方案,再也不用担心遇到BUG了!

今天继续和大家分享Python高阶开发中详细的IO操作、线程和进程操作!帮助你掌握在基础进阶之后又一高阶技术!小伙伴们可以关注我一起学习呀!

一、Python输入输出——IO操作

1、文件读写

(1)打开文件

在进行文件读写之前,有个重要的步骤——将文件打开,同时指定针对文件的读写模式,比如只读、只写、可读可写等等。只有先打开文件才能对文件进行读写操作。

打开文件使用内置函数 open()

代码语言:javascript
复制
f = open('文件路径', 读写模式)

如:

代码语言:javascript
复制
f = open('/Users/obsession/text', 'w')

其中,读写模式 有以下常用选项:

  • 'r':只读,若文件不存在则抛出 FileNotFoundError 异常
  • 'w':只写,将覆盖所有原有内容,若文件不存在则创建文件
  • 'a':只写,以追加的形式写入内容,若文件不存在则创建文件
  • 'r+':可读可写,若文件不存在则抛出 FileNotFoundError 异常
  • 'w+':可读可写,若文件不存在则创建文件
  • 'a+':可读可写,写入时使用追加模式,若文件不存在则创建文件

以上所有读写模式都是基于文本内容的,如果想要读写二进制内容,可在上面的基础上添加 'b' 模式,如 rb'wb+'

open() 的返回值为 file 对象,也就是这里的变量 f。利用这个对象,我们可以进行文件读写。

上述打开方式默认使用 UTF-8 编码,如果文件内容并非 UTF-8 编码,可以使用 encoding 参数指定编码格式,如 f = open('/Users/obsession/text', 'w', encoding='gbk')

(2)写入文件

写入文件使用:

代码语言:javascript
复制
length = f.write('内容')
代码语言:javascript
复制
 >>> f = open(’/Users/obsession/text’, ‘w’)
 >>> f.write(‘The quick brown fox jumps over the lazy dog’)
 43
 

调用 f.write() 后将返回写入字符的长度。

(3)读取文件

读取文件使用:

代码语言:javascript
复制
content = f.read()
代码语言:javascript
复制
 >>> f = open(’/Users/obsession/text’, ‘r’)
 >>> f.read()
 ’The quick brown fox jumps over the lazy dog’
 

上例中将读取文件的所有内容。也可以指定要读取内容的字符长度:

代码语言:javascript
复制
 >>> f = open(’/Users/obsession/text’, ‘r’)
 >>> f.read(30)
 ’The quick brown fox jumps over’
 >>> f.read(30)
 ’ the lazy dog’
 >>> f.read(30)
 ’’
 

此时将根据所指定的长度来读取内容。注意观察示例,每次调用 f.read(30) 时都是从上一次读取的结束位置开始,来读取新的内容,直至所有的内容被获取完,之后再调用 f.read(30) 只会得到空字符串 ''

还可以按行来读取文件,使用:

代码语言:javascript
复制
line = f.readline()

例如某文件内容为

代码语言:javascript
复制
 The quick brown fox
 jumps over
 the lazy dog
 

按行读取文件如下:

代码语言:javascript
复制
 >>> f = open(’/Users/obsession/text’, ‘r’)
 >>> f.readline()
 ’The quick brown fox\n’
 >>> f.readline()
 ’jumps over\n’
 >>> f.readline()
 ’the lazy dog’
 >>> f.readline()
 ’’
 

按行读取文件还可以一次性将所有行读出,然后放进列表里:

代码语言:javascript
复制
lines = f.readlines()
代码语言:javascript
复制
 >>> f = open(’/Users/obsession/text’, ‘r’)
 >>> f.readlines()
 [‘The quick brown fox\n’, ‘jumps over\n’, ‘the lazy dog’]
 

(4)关闭文件

每次打开文件后,无论进行了多少读写操作,最终都一定要将文件关闭,因为打开文件会消耗相关系统资源(文件描述符),不使用时应及时释放。

关闭文件使用:

代码语言:javascript
复制
f.close()

还有一种方式能自动关闭打开的文件,那就是使用 with 语句:

代码语言:javascript
复制
with open('/Users/obsession/text', 'w') as f:
    f.write('The quick brown fox jumps over the lazy dog')

open() 后的 file 对象会被 as 关键字赋予变量 f。和之前一样,我们利用 f 进行文件读写。

with 语句会在它的代码块执行完毕后,或代码块抛出异常时,自动关闭文件,为我们省却了 f.close() 步骤。

2、文件系统操作

文件系统操作需要使用内置的 os 模块。

创建目录

判断路径是否是一个目录

列举目录下的内容

删除目录

创建文件 创建文件可以直接使用之前学过的 :

判断路径是否是一个文件

删除文件

重命名文件

3、序列化和反序列化

程序运行时,产生的所有对象都位于内存之中。内存有个特点,那就是它是非持久的,如果程序运行结束或者计算机断电,占用的内存将被清空。

有时,我们需要把程序运行时内存中的对象,持久化下来保存在文件系统中,或者传输至网络。比如将这样一个类的对象保存在文件中:

代码语言:javascript
复制
class Pair:
    def __init__(self, first, second):
        self.first = first
        self.second = second

pair = Pair(10, 20)

这就涉及到序列化和反序列化了。序列化是将内存中的对象转换为可被存储或可被传输的形式的过程。反序列化是将序列化后的内容恢复回内存中对象的过程。

(1)pickle

Python 中内置的 pickle 模块用作序列化和反序列化。它的序列化结果是二进制形式。

序列化使用:

代码语言:javascript
复制
import pickle

some_bytes = pickle.dumps(对象)
代码语言:javascript
复制
 >>> pair = Pair(10, 20)
 >>> pickle.dumps(pair)
 b’\x80\x03c__main__\nPair\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00firstq\x03K\nX\x06\x00\x00\x00secondq\x04K\x14ub.’
 

上面输出的乱码便是 pair 对象被序列化后的二进制。

对于刚才序列化后的结果,可以使用 pickle.loads() 将其反序列化回对象。如:

代码语言:javascript
复制
some_bytes = b'\x80\x03c__main__\nPair\nq\x00)\x81q\x01}q\x02(X\x05\x00\x00\x00firstq\x03K\nX\x06\x00\x00\x00secondq\x04K\x14ub.'

pair = pickle.loads(some_bytes)

pair 对象可以像之前一样正常被使用:

代码语言:javascript
复制
 >>> pair.first
 10
 >>> pair.second
 20
 

也可以与 open() 相结合,将序列化的结果保存在文件中,此时使用 pickle.dump()(注意与之前的 pickle.dumps() 不同):

代码语言:javascript
复制
with open('/Users/obsession/dump', 'wb') as f:
    pickle.dump(pair, f)

从文件中反序列化出对象,使用 pickle.load()(注意与之前的 pickle.loads() 不同):

代码语言:javascript
复制
with open('/Users/obsession/dump', 'rb') as f:
    pair = pickle.load(f)

(2)JSON

pickle 使用 Python 专用的序列化格式,序列化后的结果无法做到跨语言使用。另外其序列化结果是二进制,不适合阅读。

JSON 相对而言更加通用和流行,并且其结果为文本格式,更具可读性。

同样是刚才的 pair 对象,可以像这样将它序列化为 JSON 字符串:

代码语言:javascript
复制
import json

json_string = json.dumps(pair.__dict__)
代码语言:javascript
复制
 >>> json_string
 ’{“first”: 10, “second”: 20}’
 

注意上面结果为字符串类型。另外这里使用了 pair.__dict__ 来获取包含所有 pair 属性的字典,因为类对象不能直接用于 json.dumps() 序列化,而字典可以。

或者使用 default 参数,向 json.dumps() 告知如何进行从对象到字典的转换,这样便可以不使用 __dict__ 属性。如下:

代码语言:javascript
复制
def pair_to_dict(pair):
	return {
		'first': pair.first,
		'second': pair.second,
	}

json_string = json.dumps(pair, default=pair_to_dict)
代码语言:javascript
复制
 >>> json_string
 ’{“first”: 10, “second”: 20}’
 

从 JSON 反序列化为对象:

代码语言:javascript
复制
def dict_to_pair(d):
    return Pair(d['first'], d['second'])

pair = json.loads(json_string, object_hook=dict_to_pair)

上述反序列化过程中,json.loads() 首先会将 JSON 字符串反序列化为字典,然后使用 object_hook 参数进一步从字典转换出 pair 对象。

pickle 相似,json 也可以与 open() 结合使用,将序列化的结果保存在文件中:

代码语言:javascript
复制
with open('/Users/obsession/json', 'w') as f:
    json.dump(pair, f, default=pair_to_dict)

或从文件中反序列化出对象:

代码语言:javascript
复制
with open('/Users/obsession/json', 'r') as f:
    pair = json.load(f, object_hook=dict_to_pair)

二、让你的代码更加灵活——进程和线程操作

进程和线程时操作系统所提供的,能让程序在同一时间处理多个任务的方法,让程序能够做到「一心二用」。

1、进程操作

当我们运行一个程序时,这个程序的代码会被操作系统加载内存中,并创建出一个进程来承载和运行它。简单来说,每一个运行中的程序就是一个进程,这个进程被称为主进程。

在主进程中,我们可以创建子进程来协助处理其它任务,这时主进程和子进程是并行运行的。子进程也可以有它的子进程,从而形成以主进程为根的一棵进程树。

我们可以使用 multiprocessing.Process() 方法来创建进程:

代码语言:javascript
复制
import multiprocessing

p = multiprocessing.Process(target=目标函数, args=(目标函数的参数,))

start() 方法来启动一个进程:

代码语言:javascript
复制
p.start()

来看个例子:

代码语言:javascript
复制
import multiprocessing
import os

def target_func():
    print('子进程运行')
    print('子进程 pid:', os.getpid())
    print('子进程的 ppid:', os.getppid())

p = multiprocessing.Process(target=target_func)
p.start()

print('主进程运行')
print('主进程 pid:', os.getpid())

将上述代码拷贝至文件 process.py 中,执行下:

➜ ~ python3 process.py 主进程运行 主进程 pid: 13343 子进程运行 子进程 pid: 13344 子进程的 ppid: 13343

在这里例子中,

  • 使用 multiprocessing.Process() 来创建进程,并为该进程指定要执行的目标函数 target_func,进程启动后将执行该函数
  • 使用 start() 方法来启动进程
  • 使用 os.getpid() 获取进程的进程 ID,它是进程的唯一的标识,可用于区分进程
  • 使用 os.getppid() 获取进程的父进程 ID,父进程是创建子进程的进程
  • 主进程的 pid 和子进程的 ppid 相同(因为主进程是该子进程的父进程)

另外可以看到,虽然子进程被创建并启动,但子进程中的 print() 函数并未立即执行,反而是主进程中的 print() 函数先执行。这说明进程间的执行顺序是不确定的,并非同步执行。

使用 join() 方法可以控制子进程的执行顺序:

代码语言:javascript
复制
import multiprocessing
import os

def target_func():
    print('子进程运行')
    print('子进程 pid:', os.getpid())
    print('子进程的 ppid:', os.getppid())

p = multiprocessing.Process(target=target_func)
p.start()
p.join()

print('主进程运行')
print('主进程 pid:', os.getpid())

上述代码中新增了 p.join()。相应修改原先的 process.py 文件,再来执行下:

➜ ~ python3 process.py 子进程运行 子进程 pid: 13386 子进程的 ppid: 13385 主进程运行 主进程 pid: 13385

可以看到,使用 p.join() 后主进程将等待子进程执行完成,然后再向下执行代码。

2、线程操作

每一个进程都默认有一个线程,这个线程被称为主线程。我们可以在主线程中创建其它线程来协助处理任务,这些线程也是并行运行的。

线程是进程的执行单元,CPU 调度进程时,实际上是在进程的线程间作切换。另外线程间共享它们所在进程的内存空间(栈除外)。

可以使用 threading.Thread() 方法来创建线程:

代码语言:javascript
复制
import threading

t = threading.Thread(target=目标函数, args=(目标函数的参数,))

start() 方法来启动一个线程:

代码语言:javascript
复制
t.start()

来看个例子:

代码语言:javascript
复制
import threading

def target_func(n):
    for i in range(n):
        print(i)

t = threading.Thread(target=target_func, args=(8,))
t.start()

print('主线程结束')

将上述代码拷贝至文件 thread.py 中,执行下:

➜ ~ python3 thread.py 0 1 主线程结束 2 3 4 5 6 7

上述子线程和主线程交替执行,可以使用 join() 让主线程等待子线程执行完成:

代码语言:javascript
复制
import threading

def target_func(n):
    for i in range(n):
        print(i)

t = threading.Thread(target=target_func, args=(8,))
t.start()
t.join()

print('主线程结束')

上述代码中新增了 t.join()。相应修改原先的 thread.py 文件,再来执行下:

➜ ~ python3 thread.py 0 1 2 3 4 5 6 7 主线程结束

(1)线程锁

多个线程间回共享进程的内存空间,如果多个线程同时修改和访问同一个对象,则可能会出现非预期的错误。

比如下面这个例子中,我们创建了两个线程,这两个线程分别对 number 变量做一百万次 +1 操作。

代码语言:javascript
复制
import threading

number = 0

def add():
    for i in range(1000000):
        global number
        number += 1

t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()

print(number)

number 的预期结果应该是 2000000(两百万)。

将上述代码保存至文件 thread_add.py 中,来看下实际运行结果:

代码语言:javascript
复制
 ➜ ~ python3 thread_add.py
 1584627
 ➜ ~ python3 thread_add.py
 1413399
 ➜ ~ python3 thread_add.py
 1541521

可以看到,每次运行的结果并不一致,并且均小于 2000000

这是因为,number += 1 其实是两个操作——首先获取 number,然后对获取到的值 +1。这两个操作并不是原子的(也就是说,这两个操作并不一定会被 CPU 连续执行,执行第一个操作时,CPU 有可能被中断去执行其它任务,之后又回到这里执行第二个操作)。这个例子中有一种可能情形是,执行到某一时刻时,第一个线程获取到 number 值为 100,紧接着第二次线程也获取到 number 值为 100,第一个线程在 100 的基础上 +1 并将 101 赋值给 number,第二线程也在 100 的基础上 +1 并将 101 赋值给 number,由于两个线程是并行运行的,它们彼此间并不知情,这样就浪费了一次 +1 操作,最终的 number 结果也会变小。

在这种情况下想要得到正确的结果,应该对 number += 1 操作加锁。如下:

代码语言:javascript
复制
import threading

number = 0
lock = threading.Lock()

def add():
    for i in range(1000000):
        global number
        
        lock.acquire()
        number += 1
        lock.release()

t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()

print(number)

更新 thread_add.py 文件,来看下运行结果:

代码语言:javascript
复制
 ➜ ~ python3 thread_add.py
 2000000
 ➜ ~ python3 thread_add.py
 2000000
 ➜ ~ python3 thread_add.py
 2000000

可以看到,这次结果完全正确。但同时我们也能感受到,程序的执行速度变慢了,是的,锁会带来性能上的损耗,这就需要我们在正确性和性能间做取舍了。

OK,关于常见的Python高阶IO操作及进程线程操作就和大家先分享这些,大家有疑问或者补充的话,欢迎在评论区留言!

灰小猿陪你一起进步

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 一、Python输入输出——IO操作
    • 1、文件读写
      • (1)打开文件
      • (2)写入文件
      • (3)读取文件
      • (4)关闭文件
    • 2、文件系统操作
      • 3、序列化和反序列化
        • (1)pickle
        • (2)JSON
    • 二、让你的代码更加灵活——进程和线程操作
      • 1、进程操作
        • 2、线程操作
          • (1)线程锁
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档