【Python入门】20.IO编程 & 文件搜索功能的函数实现

摘要:IO编程的基本介绍;文件读写的函数;StringIO;BytestIO;通过Python操作文件和目录;os模块介绍;序列化picking;JSON标准格式的转化


写在前面:为了更好的学习python,博主记录下自己的学习路程。本学习笔记基于廖雪峰的Python教程,如有侵权,请告知删除。欢迎与博主一起学习Pythonヽ( ̄▽ ̄)ノ


目录

  • IO编程
    • 文件读写
      • • 读文件
      • • 写文件
      • • 二进制文件
      • • 字符编码
    • StringIO
    • BytesIO
    • 操作文件和目录
      • • 环境变量
      • • 操作文件和目录
      • • 【练习】搜素指定文件的函数实现
    • 序列化 picking
      • • JSON
      • • JSON进阶

IO编程

IO即Input/Output的简称,也就是输入与输出。程序的运行往往涉及到数据的交换,通常在磁盘、网络等地方需要IO接口。

如我们把数据输出到磁盘文件保存,这个过程叫输出Output,反过来从磁盘里读取数据到内存运行,叫输入Input。

如浏览网页时,浏览器会先把数据发给服务器,告诉服务器用户想浏览的页面,这个过程就是输出数据Output,随后服务器把网页发到浏览器,这个过程就是接受数据Input。

在IO编程中,有个重要的概念叫Stream,流。Input Stream就是数据从外(磁盘、网络)往内(内存)流,Output Stream就是数据从内往外流。需要注意的是,Stream只能单向流动。所以对于浏览网页来说,需要建立两个Stream来完成Input与Output。

由于在IO编程中存在速度不匹配的情况,所以会有同步IO与异步IO。

同步IO是指在执行IO时,暂停后续的代码,等待IO执行完毕后再继续执行;

异步IO是指在执行IO时,不去等待IO的执行结果,继续执行执行后续的代码,IO执行完毕后再去执行相关代码。

异步IO复杂程度很高,在后面会介绍,本节IO编程内容都是同步模式。

文件读写

读写文件需要向操作系统请求打开一个文件对象(文件描述符),然后通过操作系统提供的接口从这个文件对象中读取数据或写入数据。

• 读文件

open( )函数可以打开一个文件。open( )需要传入文件名和指示符两个参数。指示符“r”,即read表示“读”

>>>f = open('c:\Users\Administrator\test.txt','r')

如果文件不存在,则会抛出IOError。

成功打开文件后,就可以调用read( )来读取数据了,read( )一次性把所有内容读取,并以str类型显示。

>>> f.read()
'Hello, world!'

最后调用close( )来关闭文件。每次打开文件后必须关闭文件,否则占用内容。

>>>f.close()

为了确保close( )的运行,我们可以用try…finally来优化代码,避免在读写过程中出现IOError时中断执行代码而无法关闭文件。

try:
    f = open('c:\Users\Administrator\test.txt', 'r')
    print(f.read())
finally:
    if f:
        f.close()

上面的代码等同于下面:

with open('c:\Users\Administrator\test.txt', 'r') as f:
    print(f.read())

Python引入了with语句来自动调用close()方法,这样写起来就方便很多。

读取文件的方法还有很多,除了read( )一次性读取全部内容外,还有:

read(size),每次读取size个字节的内容,适合于未知文件大小的读取;
readline( ),每次读取一行内容;
readlines( ),一次性读取所有内容,并按行返回list,适用于配置文件的读取。

file-like Object:像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。
StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

• 写文件

调用open( )函数时把指示符改为“w”即write,就可以进行写文件。成功打开文件后用write( )方法来进行写入。

>>> f = open('c:\Users\Administrator\test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

更好的写法:

with open('c:\Users\Administrator\test.txt', 'w') as f:
    f.write('Hello, world!')

需要注意的是一定要保证close( )的运行,因为操作系统只有在调用close( )方法时,才能保证把所有内容全部写入磁盘。

如果想要在一个文件后继续添加内容,只要在调用open( )函数时,把指示符改为“a”即append,即可。

• 二进制文件

上面的都是默认读写UTF-8编码的文本文件,若要读写二进制文件,把指示符改为“rb”或“rw”就可以读取或写入。

• 字符编码

如果要读写非UTF-8编码的文本文件,在调用open( )函数时还需要传入另一个参数,encoding。比如读取GBK文件:

>> f = open('c:\Users\Administrator\gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'

StringIO

StringIO就是在内存中读写str。

先创建一个StringIO,然后像写文件一样操作即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

其中getvalue( )方法可以获得写入的str。

读取StringIO,先用一个str初始化StringIO,然后像读文件一样操作即可:

>>>from io import StringIO
>>>f = StringIO('hello\nworld')
>>>while True:
...    s = f.read()
...    if s == '':
...        break
...    print(s)
...
hello 
world 

BytesIO

要操作二进制数据,就要用到BytesIO。操作与读写String类似。

写入bytes:

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

读取bytes:

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件和目录

我们都知道可以在命令行模式下操作文件和目录,比如输入dir命令等。那如果想要在Python中操作文件和目录呢?

Python内置的os模块可以直接调用操作系统提供的接口函数。

在Python的交互式模型下,引入os模块,输入os.name来查看操作系统类型:

>>> import os
>>> os.name # 操作系统类型
'nt'

如果显示’nt’,表示是window系统,如果显示’posix’,则表示系统是Linux、Unix或Mac OS X。

• 环境变量

在操作系统中定义的环境变量存在os.environ变量中,可直接调用查看。

想要直接获取某个环境变量的值,用语句os.environ.get(‘key’)即可。

>>>os.environ.get('path')
...;D:\\program files (x86)\\Python37\\Scripts\\;...

• 操作文件和目录

接下来介绍一些操作文化和目录的函数。

os.path.abspath( )函数可以获取绝对路径

os.path.join( )函数可以拼接两个路径

os.mkdir( )函数用来创建目录

os.rmdir( )函数用来删除目录

例:

>>> os.path.abspath('.')                                 # 查看当前目录的绝对路径
'C:\\Users\\Administrator'
>>> os.path.join('C:\\Users\\Administrator', 'testdir')  # 表示出新目录的完整路径
'C:\\Users\\Administrator\\testdir'
>>> os.mkdir('C:\\Users\\Administrator\\testdir')        # 创建一个目录
>>> os.rmdir('C:\\Users\\Administrator\\testdir')        # 删除一个目录
>>>

还有os.path.split( )函数可以拆分路径,像这样:

>>> os.path.split('C:\\Users\\Administrator\\testdir')
('C:\\Users\\Administrator', 'testdir')

os.path.splitext( )可以获得文件的扩展名:

>>> os.path.splitext('C:\\Users\\Administrator\\test.py')
'C:\\Users\\Administrator\\test','.py'

需要注意的是,这些拼接、拆分路径的函数只对字符串进行操作,并没有要求要存在真正的文件或目录。

os.rename( )函数可以重命名文件

os.remove( )函数可以删除文件

>>> os.rename('test.txt', 'test.py')       # 重命名文件
>>> os.remove('test.py')                   # 删除文件

如果想要复制文件,要用到shutil模块里面的copyfile( )函数。os模块中没有是因为复制文件并非由操作系统提供的系统调用。

灵活运用这些函数能起到很多作用,比如我们想列出所有.py的文件,可以这样写:

>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['err.py', 'error.py', 'learning.py', 'mydict.py', 'mydict_test.py', 'new1.py',
'text.py', 'text2.py']

其中os.listdir( )返回指定目录下的所有文件,os.path.isfile( )判断对象是否为文件。

• 【练习】搜素指定文件的函数实现

编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。

import os

def search(name, path='.'):
    for x in os.listdir(path):                       # 读取当前目录下的所有文件和文件夹
        if os.path.isdir(x):                         # 如果x是文件夹
            search(name, os.path.join(path,x))       # 搜素该文件夹的文件
        else:                                        # 反之如果x是文件
            if name in x :                           # 文件名含有指定的字符串     
                print(x)                             # 打印该文件名
                print(os.path.join(path,x))          # 打印该文件的相对路径

序列化 picking

我们把变量从内存中变成可存储或传输的过程称之为序列化。

把变量内容序列化之后,就可以进行储存或传输了。反之,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

在Python中,有pick模块来实现序列化。

pickle.dumps( )方法实现序列化。

pickle.load( )方法实现反序列化。

我们试一下把一个dict序列化之后写入文件:

>>>import pickle
>>>d = dict(name = 'Ming', socre = 90)
>>>pickle.dumps(d)
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x04\x00\x00\x00Mingq\x02X\x05\x00\x00\x00socreq\x03KZu.'

会发现一个对象序列化之后会变成bytes类型,所以要用指示符“wb”来打开文件,并写入文件:

f = open('dump.txt', 'wb')
pickle.dump(d, f)
f.close()

这时候dump.txt文件内是Python保存的对象内部信息,想要读取,可以把它反序列化之后显示:


>>>f = open('dump.txt', 'rb')
>>>a = pickle.load(f)
>>>f.close()
>>>a
{'name': 'Ming', 'socre': 90} 

Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。

• JSON

想要在不同编程语言之间传递对象,就要把对象序列化成标准格式,像JSON。

JSON表示的对象就是标准的JavaScript语言的对象。

JSON类型与Python类型的对比:

JSON类型 Python类型
{} dict
[] list
“string” str
1234.56 int或float
true/false True/False
null None

python中内置的json模块可以python对象转化为JSON格式。

我们可以用json.dumps( )方法将一个对象序列化为JSON格式,用json.loads( )方法来反序列化:

>>> import json
>>> d = dict(name='Ming', score=90)
>>> json.dumps(d)                                            # 序列化
'{ "name": "Ming", "score": 90}'
>>> json_str = '{"name": "Ming", "score": 90}'
>>> json.loads(json_str)                                     # 反序列化
{'name': 'Ming', 'score': 90} 

• JSON进阶

在Python中,我们常用class来表示对象,当我们尝试把一个class序列化为JSON格式时,却出现了TypeError。

import json

class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student('Bob', 20, 88)
print(json.dumps(s))
Traceback (most recent call last):
  ...
TypeError: <__main__.Student object at 0x10603cc50> is not JSON serializable

这是因为在默认情况下,dumps()方法不知道如何将Student实例变为一个JSON的{ }对象。我们需要添加一个可选参数default,参数default是把任意一个对象转化为可序列化对象。

所以我们需要定义一个函数把Student转为dict,然后把函数传给default:

def student2dict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }
>>> print(json.dumps(s, default=student2dict))
{"age": 20, "name": "Bob", "score": 88}

实例s先经过参数default即函数student2dict转化为dict,然后通过dumps序列化。

还可以这样写:

print(json.dumps(s, default=lambda obj: obj.__dict__))

这是因为一般而言class的实例都会有__dict__属性,它就是一个dict,用来存储实例变量。这样写的话就不用额外地去定义一个函数了。

反序列化也是一样,当loads方法把对象转化为dict后,通过object_hook参数把dict转化为class类型:

def dict2student(d):
    return Student(d['name'], d['age'], d['score'])
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x10cd3c190>

输出结果为实例对象。


以上就是本节的全部内容,感谢你的阅读。

下一节内容:进程和线程

有任何问题与想法,欢迎评论与吐槽。

和博主一起学习Python吧( ̄▽ ̄)~*

你可能感兴趣的:(Python入门,学习笔记)