# 1. 文件与文件系统

## 打开文件

- `open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)` Open file and return a stream.  Raise OSError upon failure.
    - `file`: 必需，文件路径（相对或者绝对路径）。
    - `mode`: 可选，文件打开模式
    - `buffering`: 设置缓冲
    - `encoding`: 一般使用utf8
    - `errors`: 报错级别
    - `newline`: 区分换行符

常见的`mode`如下表所示：

打开模式 | 执行操作
:---:|---
'r' | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
'w' | 打开一个文件只用于写入。<br>如果该文件已存在则打开文件，并从开头开始编辑。<br>即原有内容会被删除。<br>如果该文件不存在，创建新文件。
'x' | 写模式，新建一个文件，如果该文件已存在则会报错。
'a' | 追加模式，打开一个文件用于追加。<br>如果该文件已存在，文件指针将会放在文件的结尾。<br>也就是说，新的内容将会被写入到已有内容之后。<br>如果该文件不存在，创建新文件进行写入。
'b' | 以二进制模式打开文件。一般用于非文本文件，如：图片。
't' | 以文本模式打开（默认）。一般用于文本文件，如：txt。
'+' | 可读写模式（可添加到其它模式中使用）



【例】打开一个文件，并返回文件对象，如果该文件无法被打开，会抛出`OSError`。

```python
f = open('将进酒.txt')
print(f)
# <_io.TextIOWrapper name='将进酒.txt' mode='r' encoding='cp936'>

for each in f:
    print(each)

# 君不见，黄河之水天上来，奔流到海不复回。
# 君不见，高堂明镜悲白发，朝如青丝暮成雪。
# 人生得意须尽欢，莫使金樽空对月。
# 天生我材必有用，千金散尽还复来。
# 烹羊宰牛且为乐，会须一饮三百杯。
# 岑夫子，丹丘生，将进酒，杯莫停。
# 与君歌一曲，请君为我倾耳听。
# 钟鼓馔玉不足贵，但愿长醉不复醒。
# 古来圣贤皆寂寞，惟有饮者留其名。
# 陈王昔时宴平乐，斗酒十千恣欢谑。
# 主人何为言少钱，径须沽取对君酌。
# 五花马，千金裘，呼儿将出换美酒，与尔同销万古愁。
```







---
## 文件对象方法

- `fileObject.close()` 用于关闭一个已打开的文件。关闭后的文件不能再进行读写操作， 否则会触发`ValueError`错误。

【例】
```python
f = open("将进酒.txt")
print('FileName:', f.name)  # FileName: 将进酒.txt
f.close()
```

- `fileObject.read([size])` 用于从文件读取指定的<u>字符数</u>，如果未给定或为负则读取所有。

【例】
```python
f = open('将进酒.txt', 'r')
line = f.read(20)
print("读取的字符串: %s" % line)
# 读取的字符串: 君不见，黄河之水天上来，奔流到海不复回。

f.close()
```

- `fileObject.readline()`读取整行，包括 "\n" 字符。

【例】
```python
f = open('将进酒.txt', 'r')
line = f.readline()
print("读取的字符串: %s" % line)
# 读取的字符串: 君不见，黄河之水天上来，奔流到海不复回。
f.close()
```

- `fileObject.readlines()`用于读取所有行(直到结束符 EOF)并返回列表，该列表可以由 Python 的 `for... in ...` 结构进行处理。

【例】
```python
f = open('将进酒.txt', 'r')
lines = f.readlines()
print(lines)

for each in lines:
    each.strip()
    print(each)

# 君不见，黄河之水天上来，奔流到海不复回。
# 君不见，高堂明镜悲白发，朝如青丝暮成雪。
# 人生得意须尽欢，莫使金樽空对月。
# 天生我材必有用，千金散尽还复来。
# 烹羊宰牛且为乐，会须一饮三百杯。
# 岑夫子，丹丘生，将进酒，杯莫停。
# 与君歌一曲，请君为我倾耳听。
# 钟鼓馔玉不足贵，但愿长醉不复醒。
# 古来圣贤皆寂寞，惟有饮者留其名。
# 陈王昔时宴平乐，斗酒十千恣欢谑。
# 主人何为言少钱，径须沽取对君酌。
# 五花马，千金裘，呼儿将出换美酒，与尔同销万古愁。

f.close()
```

- `fileObject.tell()`返回文件的当前位置，即文件指针当前位置。

【例】
```python
f = open('将进酒.txt', 'r')
line = f.readline()
print(line)
# 君不见，黄河之水天上来，奔流到海不复回。
pos = f.tell()
print(pos)  # 42
f.close()
```

- `fileObject.seek(offset[, whence])`用于移动文件读取指针到指定位置。
    - `offset`：开始的偏移量，也就是代表需要移动偏移的字节数，如果是负数表示从倒数第几位开始。
    - `whence`：可选，默认值为 0。给 `offset` 定义一个参数，表示要从哪个位置开始偏移；0 代表从文件开头开始算起，1 代表从当前位置开始算起，2 代表从文件末尾算起。

【例】
```python
f = open('将进酒.txt', 'r')
line = f.readline()
print(line)
# 君不见，黄河之水天上来，奔流到海不复回。
line = f.readline()
print(line)
# 君不见，高堂明镜悲白发，朝如青丝暮成雪。
f.seek(0, 0)
line = f.readline()
print(line)
# 君不见，黄河之水天上来，奔流到海不复回。
f.close()
```

- `fileObject.write(str)`用于向文件中写入指定字符串，返回的是写入的字符长度。

【例】
```python
f = open('workfile.txt', 'wb+')
print(f.write(b'0123456789abcdef'))  # 16
print(f.seek(5))  # 5
print(f.read(1))  # b'5'
print(f.seek(-3, 2))  # 13
print(f.read(1))  # b'd'
```

在文件关闭前或缓冲区刷新前，字符串内容存储在缓冲区中，这时你在文件中是看不到写入的内容的。

如果文件打开模式带`b`，那写入文件内容时，`str`（参数）要用`encode`方法转为`bytes`形式，否则报错：`TypeError: a bytes-like object is required, not 'str'`。

【例】
```python
str = '...'
# 文本 = Unicode字符序列
# 相当于 string 类型

str = b'...'
# 文本 = 八位序列(0到255之间的整数)
# 字节文字总是以‘b’或‘B’作为前缀；它们产生一个字节类型的实例，而不是str类型。
# 相当于 byte[]
```

【例】

```python
f = open('将进酒.txt', 'r+')
str = '\n作者：李白'
f.seek(0, 2)
line = f.write(str)
f.seek(0, 0)
for each in f:
    print(each)

# 君不见，黄河之水天上来，奔流到海不复回。
# 君不见，高堂明镜悲白发，朝如青丝暮成雪。
# 人生得意须尽欢，莫使金樽空对月。
# 天生我材必有用，千金散尽还复来。
# 烹羊宰牛且为乐，会须一饮三百杯。
# 岑夫子，丹丘生，将进酒，杯莫停。
# 与君歌一曲，请君为我倾耳听。
# 钟鼓馔玉不足贵，但愿长醉不复醒。
# 古来圣贤皆寂寞，惟有饮者留其名。
# 陈王昔时宴平乐，斗酒十千恣欢谑。
# 主人何为言少钱，径须沽取对君酌。
# 五花马，千金裘，呼儿将出换美酒，与尔同销万古愁。
# 作者：李白

f.close()
```

- `fileObject.writelines(sequence)`向文件写入一个序列字符串列表，如果需要换行则要自己加入每行的换行符`\n`。

【例】
```python
f = open('test.txt', 'w+')
seq = ['小马的程序人生\n', '老马的程序人生']
f.writelines(seq)
f.seek(0, 0)
for each in f:
    print(each)
    
# 小马的程序人生
# 老马的程序人生
f.close()
```

---
## 简洁的 with 语句

一些对象定义了标准的清理行为，无论系统是否成功的使用了它，一旦不需要它了，那么这个标准的清理行为就会执行。

关键词 with 语句就可以保证诸如文件之类的对象在使用完之后一定会正确的执行它的清理方法。

【例】
```python
try:
    f = open('myfile.txt', 'w')
    for line in f:
        print(line)
except OSError as error:
    print('出错啦!%s' % str(error))
finally:
    f.close()

# 出错啦!not readable
```

这段代码执行完毕后，就算在处理过程中出问题了，文件 f 总是会关闭。

【例】
```python
try:
    with open('myfile.txt', 'w') as f:
        for line in f:
            print(line)
except OSError as error:
    print('出错啦!%s' % str(error))

# 出错啦!not readable    
```

---
# 2. OS 模块中关于文件/目录常用的函数

我们所知道常用的操作系统就有：Windows，Mac OS，Linu，Unix等，这些操作系统底层对于文件系统的访问工作原理是不一样的，因此你可能就要针对不同的系统来考虑使用哪些文件系统模块……，这样的做法是非常不友好且麻烦的，因为这样就意味着当你的程序运行环境一改变，你就要相应的去修改大量的代码来应对。

有了OS（Operation System）模块，我们不需要关心什么操作系统下使用什么模块，OS模块会帮你选择正确的模块并调用。



- `os.getcwd()`用于返回当前工作目录。
- `os.chdir(path)`用于改变当前工作目录到指定的路径。

【例】
```python
import os

path = 'C:\\'
print("当前工作目录 : %s" % os.getcwd())
# 当前工作目录 : C:\Users\Administrator\PycharmProjects\untitled1
os.chdir(path)
print("目录修改成功 : %s" % os.getcwd())
# 目录修改成功 : C:\
```
- `listdir (path='.')`返回`path`指定的文件夹包含的文件或文件夹的名字的列表。

【例】
```python
import os

dirs = os.listdir()
for item in dirs:
    print(item)
```

- `os.mkdir(path)`创建单层目录，如果该目录已存在抛出异常。


【例】
```python
import os

if os.path.isdir(r'.\b') is False:
    os.mkdir(r'.\B')
    os.mkdir(r'.\B\A')

os.mkdir(r'.\C\A') # FileNotFoundError
```

- `os.makedirs(path)`用于递归创建多层目录，如果该目录已存在抛出异常。

【例】
```python
import os
os.makedirs(r'.\E\A')
```

- `os.remove(path)`用于删除指定路径的文件。如果指定的路径是一个目录，将抛出 `OSError`。

【例】首先创建`.\E\A\text.txt`文件，然后进行删除。
```python
import os

print("目录为: %s" % os.listdir(r'.\E\A'))
os.remove(r'.\E\A\test.txt')
print("目录为: %s" % os.listdir(r'.\E\A'))
```

- `os.rmdir(path)`用于删除单层目录。仅当这文件夹是空的才可以, 否则, 抛出 `OSError`。

【例】首先创建`.\E\A`目录，然后进行删除。
```python
import os

print("目录为: %s" % os.listdir(r'.\E'))
os.rmdir(r'.\E\A')
print("目录为: %s" % os.listdir(r'.\E'))
```

- `os.removedirs(path)`递归删除目录，从子目录到父目录逐层尝试删除，遇到目录非空则抛出异常。

【例】首先创建`.\E\A`目录，然后进行删除。
```python
import os

print("目录为: %s" % os.listdir(os.getcwd()))
os.removedirs(r'.\E\A')  # 先删除A 然后删除E
print("目录为: %s" % os.listdir(os.getcwd()))
```

- `os.rename(src, dst)`方法用于命名文件或目录，从 `src` 到 `dst`，如果 `dst` 是一个存在的目录, 将抛出 `OSError`。

【例】把test.txt文件重命名为test2.txt。
```python
import os

print("目录为: %s" % os.listdir(os.getcwd()))
os.rename("test.txt", "test2.txt")
print("重命名成功。")
print("目录为: %s" % os.listdir(os.getcwd()))
```

- `os.system(command)`运行系统的shell命令（将字符串转化成命令）

【例】先自行创建一个a.py的文件，然后由shell命令打开。
```python
import os

path = os.getcwd() + '\\a.py'
a = os.system(r'python %s' % path)

os.system('calc')  # 打开计算器
```

- `os.curdir`指代当前目录（`.`）
- `os.pardir`指代上一级目录（`..`）
- `os.sep`输出操作系统特定的路径分隔符（win下为`\\`，Linux下为`/`）
- `os.linesep`当前平台使用的行终止符（win下为`\r\n`，Linux下为`\n`）
- `os.name`指代当前使用的操作系统（包括：'mac'，'nt'）

【例】
```python
import os

print(os.curdir)  # .
print(os.pardir)  # ..
print(os.sep)  # \
print(os.linesep)
print(os.name)  # nt
```

- `os.path.basename(path)`去掉目录路径，单独返回文件名
- `os.path.dirname(path)`去掉文件名，单独返回目录路径
- `os.path.join(path1[, path2[, ...]])`将 `path1`，`path2` 各部分组合成一个路径名
- `os.path.split(path)`分割文件名与路径，返回`(f_path,f_name)`元组。如果完全使用目录，它会将最后一个目录作为文件名分离，且不会判断文件或者目录是否存在。
- `os.path.splitext(path)`分离文件名与扩展名，返回`(f_path,f_name)`元组。

【例】
```python
import os

# 返回文件名
print(os.path.basename(r'C:\test\lsgo.txt'))  # lsgo.txt
# 返回目录路径
print(os.path.dirname(r'C:\test\lsgo.txt'))  # C:\test
# 将目录和文件名合成一个路径
print(os.path.join('C:\\', 'test', 'lsgo.txt'))  # C:\test\lsgo.txt
# 分割文件名与路径
print(os.path.split(r'C:\test\lsgo.txt'))  # ('C:\\test', 'lsgo.txt')
# 分离文件名与扩展名
print(os.path.splitext(r'C:\test\lsgo.txt'))  # ('C:\\test\\lsgo', '.txt')
```



- `os.path.getsize(file)`返回指定文件大小，单位是字节。
- `os.path.getatime(file)`返回指定文件最近的访问时间
- `os.path.getctime(file)`返回指定文件的创建时间
- `os.path.getmtime(file)`返回指定文件的最新的修改时间
- 浮点型秒数，可用time模块的`gmtime()`或`localtime()`函数换算

【例】
```python
import os
import time

file = r'.\lsgo.txt'
print(os.path.getsize(file))  # 30
print(os.path.getatime(file))  # 1565593737.347196
print(os.path.getctime(file))  # 1565593737.347196
print(os.path.getmtime(file))  # 1565593797.9298275
print(time.gmtime(os.path.getctime(file)))
# time.struct_time(tm_year=2019, tm_mon=8, tm_mday=12, tm_hour=7, tm_min=8, tm_sec=57, tm_wday=0, tm_yday=224, tm_isdst=0)
print(time.localtime(os.path.getctime(file)))
# time.struct_time(tm_year=2019, tm_mon=8, tm_mday=12, tm_hour=15, tm_min=8, tm_sec=57, tm_wday=0, tm_yday=224, tm_isdst=0)
```

- `os.path.exists(path)`判断指定路径（目录或文件）是否存在
- `os.path.isabs(path)`判断指定路径是否为绝对路径
- `os.path.isdir(path)`判断指定路径是否存在且是一个目录
- `os.path.isfile(path)`判断指定路径是否存在且是一个文件
- `os.path.islink(path)`判断指定路径是否存在且是一个符号链接
- `os.path.ismount(path)`判断指定路径是否存在且是一个悬挂点
- `os.path.samefile(path1,path2)`判断path1和path2两个路径是否指向同一个文件


【例】
```python
import os

print(os.path.ismount('D:\\'))  # True
print(os.path.ismount('D:\\Test'))  # False
```

---
# 3. 序列化与反序列化

Python 的 pickle 模块实现了基本的数据序列和反序列化。
- 通过 pickle 模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去，永久存储。
- 通过 pickle 模块的反序列化操作，我们能够从文件中创建上一次程序保存的对象。

pickle模块中最常用的函数为：

`pickle.dump(obj, file, [,protocol])` 将`obj`对象序列化存入已经打开的`file`中。
- `obj`：想要序列化的`obj`对象。
- `file`:文件名称。
- `protocol`：序列化使用的协议。如果该项省略，则默认为0。如果为负值或`HIGHEST_PROTOCOL`，则使用最高的协议版本。


`pickle.load(file)` 将`file`中的对象序列化读出。
- `file`：文件名称。


【例】
```python
import pickle

dataList = [[1, 1, 'yes'],
            [1, 1, 'yes'],
            [1, 0, 'no'],
            [0, 1, 'no'],
            [0, 1, 'no']]
dataDic = {0: [1, 2, 3, 4],
           1: ('a', 'b'),
           2: {'c': 'yes', 'd': 'no'}}

# 使用dump()将数据序列化到文件中
fw = open(r'.\dataFile.pkl', 'wb')

# Pickle the list using the highest protocol available.
pickle.dump(dataList, fw, -1)

# Pickle dictionary using protocol 0.
pickle.dump(dataDic, fw)
fw.close()

# 使用load()将数据从文件中序列化读出
fr = open('dataFile.pkl', 'rb')
data1 = pickle.load(fr)
print(data1)
data2 = pickle.load(fr)
print(data2)
fr.close()

# [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
# {0: [1, 2, 3, 4], 1: ('a', 'b'), 2: {'c': 'yes', 'd': 'no'}}
```

---
**参考文献**：
- https://www.runoob.com/python3/python3-tutorial.html
- https://www.bilibili.com/video/av4050443

---
**练习题**：

1、打开中文字符的文档时，会出现乱码，Python自带的打开文件是否可以指定文字编码？还是只能用相关函数？

2、编写程序查找最长的单词

输入文档: res/test.txt

题目说明:
   
```python
"""
   
Input file
   test.txt
   
Output file
   ['general-purpose,', 'object-oriented,']
   
"""
def longest_word(filename):
    # your code here
        pass
   
```
   
   