# open() 内置函数
# 格式 open(file_name [,access_mode [, buffering]])

"""
关于第三个参数 -- buffering
1 如果参数为 0 ,打开的文件就不是带缓冲的
2 如果参数为 1 或者 True ,打开的文件就是带缓冲的
3 如果是一个 >1 的正数 ,则用于指定缓冲区的大小
4 如果是任意负数,则代表使用默认的缓冲区大小
PS --
由于外设的读写速度远远小于内存的速度,为了更好的完成数据处理,会将数据首先
传递到缓冲区,然后再进行操作
"""
# 打开文件后,就可以调用文件对象的属性和方法
"""
1 file.closed -- 返回文件是否已经关闭
2 file.mode -- 返回被打开文件的访问模式
3 file.name -- 返回文件的名称
"""
# 注意 默认的打开模式是 r 只读

 

# 文件的打开模式
"""
1 r -- 只读模式
2 w -- 写模式
3 a -- 追加模式
4 + -- 读写模式,可以与其它模式结合使用
比如: r+ 代表读写模式 ; w+ 也代表读写模式
5 b -- 二进制模式,可以与其它模式结合使用
比如: br 代表二进制只读模式
"""
# 注意
"""
1 w / w+ 模式打开文件后,会进行清空处理
2 使用 r / r+ 模式打开文件,要求打开的文件本身存在
3 w / w+ 或者 a / a+ 模式打开文件 如果文件不存在会被自动创建
4 b模式可以被追加到其他模式上,可用于以二进制的方式来读写文件内容
"""

 

# 读取文件

# 1 按字节或字符读取 -- read (到底是字节还是字符,取决于是否使用了b模式--字节)

print("示范 字节或字符 来读取文件内容")

f = open(r"镜像源安装.txt","r",True)

while True :
# 读取字符
#ch = f.read(1) # 每次读取一个字符
ch = f.read() # 如果没有指定参数,默认读取文件下的全部内容
# 如果没有读到数据,则跳出循环
if not ch :
break
# 输出
print(ch,end=" ")

f.close() # 读取完数据后,推荐立即使用 close 关闭文件,以避免资源泄露

# 注意
"""
1 默认情况下,open函数使用当前操作系统的字符集解析打开的文件,比如 Windows 的 GBK
2 如果待读取的文件不是使用当前操作系统的字符集,则需要进行字符集处理
"""

# 2 按行读取 -- 文本文件才有行的概念
# 文件对象提供了两种方法来读取行
"""
1 读取一行内容,如果指定了参数 n ,则只读取此行内的 n 个字符 -- readline([n])
2 读取文件内所有行

"""

# 示范 readline 方法来读取文件内容 -- 逐行读取
# readline() 函数在读取文件中一行的内容时,会读取最后的换行符“\n”,再加上 print() 函数输出内容时默认会换行,
# 所以输出结果中会看到多出了一个空行

print("示范 行模式 来读取文件内容 ---- 逐行读取 ")

import codecs
# 指定 utf-8 字符集读取内容
f = codecs.open(r"镜像源安装.txt",'r',"utf-8",buffering=True)
while True:
# 以行读取
line = f.readline() # 未指定参数,代表读取完整的一行
# 如果没有读取数据,则跳出循环
if not line:
break
# 输出 line
print(line,end="")

f.close()

 

# 示范 readlines 方法来读取文件内容 -- 一次读取所有行
# readlines() 函数在读取每一行时,会连同行尾的换行符一块读取

print("示范 行模式 来读取文件内容 ---- 一次读取所有行 ")


import codecs

# 指定 utf-8 字符集读取内容
f = codecs.open(r"镜像源安装.txt",'r',"utf-8",buffering=True)

# 使用 readlines 读取所有行,返回所有行组成的列表
for i in f.readlines():
print(i,end="")
f.close()

 

# 文件指针 -- 用于标明文件的读写位置
"""
假如把文件看成一个水流,文件中的每个数据(以b模式打开,每个数据就是一个字节;以普通模式打开
,每个数据就是一个字符)就相当于水滴,而文件指针就标明了文件将要读到哪个位置
"""

# -- 操作文件指针的常用方法
"""
tell() -- 判断文件指针的位置
seek(offset [,whence]) --该方法把文件指针移动到指定位置。
当 whence 为 0 时(默认值) ,表明文件从开头开始计算 ;当 whence 为 1 时(默认值) ,表明文件
从当前位置开始计算 ;当 whence 为 2 时(默认值) ,表明文件从结尾处开始计算
offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。

例如,当whence == 0 &&offset == 3(即 seek(3,0) ),表示文件指针移动至距离文件开头处 3 个字符的位置;
当whence == 1 &&offset == 5(即 seek(5,1) ),表示文件指针向后移动,移动至距离当前位置 5 个字符处。

注意,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开,否则会抛出 io.UnsupportedOperation 错误。
"""

# 示范 -- 文件指针

import codecs
# 指定使用 UTF-8 字符集读取文件内容
f = codecs.open(r"镜像源安装.txt",'rb','utf-8',buffering=True)

# 判断文件指针的位置
print(f.tell())

# 将文件指针移动到第3处
f.seek(3)
print(f.tell())

# 读取一个字节,文件指针自动后移1个数据
print(f.read(1))
print(f.tell())

# 将文件指针移动到第5处
f.seek(5)
print(f.tell())

#将文件指针向后移动5个数据
f.seek(5,1)
print(f.tell())

# 将文件指针移动到倒数第10处
f.seek(-10,2)
print(f.tell())

 

# 输出内容
# 文件对象提供的写文件的方法主要有两种
"""
write(str或bytes) -- 输出字符串或字节串 ,只有以二进制的模式打开的文件才能写入字节串
writelines(可迭代对象) -- 输出多个字符串或字节


在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。
这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。
除此之外,如果向文件写入数据后,不想马上关闭文件,也可以调用文件对象提供的 flush() 函数,它可以实现将缓冲区的数据写入文件中

需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符,在实际操作过程中如果想添加换行可是使用 os.linesep 实现


"""

import os
f = open("demo.txt",'w+')

# os.linesep 代表当前操作系统的换行符
f.write('我爱Python'+os.linesep)
f.writelines(('窗前明月光'+os.linesep,
'疑是地上霜'+os.linesep,
'举头望明月'+os.linesep,
'低头思故乡'+os.linesep))

f.close()

ff = open(r"demo.txt",'r')
print(ff.read())

ff.close()

# 注意
"""
采用上面的方法输入文件时,程序会使用当前操作系统默认的字符集
如果需要指定的字符集来输出文件,则可以采用二进制形式--程序先将
所输出的字符串转换为指定的字符集对应的二进制数据(字节串),然后输出二进制数据
"""

print("-----------------------------------------")

import os
f = open("test2.txt",'wb+')

# os.linesep 代表当前操作系统的换行符
f.write(('我爱Python'+os.linesep).encode('utf-8'))
f.writelines((('窗前明月光'+os.linesep).encode('utf-8'),
('疑是地上霜'+os.linesep).encode('utf-8'),
('举头望明月'+os.linesep).encode('utf-8'),
('低头思故乡'+os.linesep).encode('utf-8')))

f.close()

ff = open(r"test1.txt",'rb')
print(ff.read())

ff.close()

 

# 解决字符集不匹配的方式
"""
1 使用二进制模式读取,然后用 bytes 的 decode()方法恢复字符集
2 利用 codecs 模块的 open()函数来打开文件,该函数允许在打开文件时进行字符集指定
"""

print("-------------------指定二进制模式读取文件内容--------------------")

# 指定二进制模式读取文件内容

f = open(r"镜像源安装.txt",'rb',True)

# 直接读取全部文件内容,并调用 bytes 的 decode() 方法将字节回复成字符串
print(f.read().decode('utf-8'))

f.close()


print("--------------------使用 codecs 模块的 open() 函数来打开文件-------------------")

# 使用 codecs 模块的 open() 函数来打开文件 , 以显示模式指定字符集

import codecs

# 指定使用 utf-8 字符集读取文件

f = codecs.open(r"镜像源安装.txt",'r','utf-8',buffering=True)

while True :
# 每次读取一个字符
ch = f.read(1)
# 如果没有读到数据,则跳出循环
if not ch :
break
# 输出
print(ch,end='')

f.close()

 

# 使用 with 语句
"""
前面的程序中,我们都采用了程序主动关闭文件。实际上,Python提供了With语句来管理资源。比如
可以把打开的文件放在with语句中,这样wIth就会帮我们主动关闭文件。
with 的语法格式如下:
with content_expression [as targer(s)]:
with 代码块
-- content_expression 用于创建可自动关闭的资源
"""

 


import codecs
# 使用 with 语句打开文件,该语句会负责关闭文件
with codecs.open(r"镜像源安装.txt",'r','utf-8',buffering=True) as f :
for line in f :
print(line,end="")

print("---------------------------------------------")

# 程序也可以使用 with 语句来处理通过 fileput.input 合并的多个文件
import fileinput
# 使用with语句打开文件,该语句会负责关闭文件
with fileinput.input(files=(r"镜像源安装.txt",r'‪C:\Users\duty1\Desktop\Python--教学\Python疯狂讲义\Python_Code\03\3.3\append_test.py'),openhook=fileinput.hook_encoded('utf-8')) as ff :
for line in ff :
print(line,end="")

 

# 补充 ---
# 关于 fileinput 读取多个输入流
# fileinput 模块提供了如下函数可以把多个输入流合并在一起
"""
1 fileinput.input(files=None,inplace=False,backup='',bufsize=0,mode='r',openbook=None)
-- 该函数中的 files 参数用于指定多个文件输入流。该函数返回一个 FileInput 对象
-- 当程序使用该函数创建 FileInput 对象之后,便可以通过 for 循环进行行遍历
files: # 文件的路径列表,默认是stdin方式,多文件['1.txt','2.txt',...]
inplace: # 是否将标准输出的结果写回文件,默认不取代
backup: # 备份文件的扩展名,只指定扩展名,如.bak。如果该文件的备份文件已存在,则会自动覆盖。
bufsize: # 缓冲区大小,默认为0,如果文件很大,可以修改此参数,一般默认即可
mode:      # 读写模式,默认为只读
openhook:    # 该钩子用于控制打开的所有文件,比如说编码方式等;
2 fileinput.filename() -- 返回正在读取的文件的文件名
3 fileinput.fileno() -- 返回当前文件的文件描述符--实质就是一个文件代号,值为一个整数
4 fileinput.lineno() -- 返回当前读取的行号
5 fileinput.filelineno() -- 返回当前读取的行在其文件中的行号
6 fileinput.isfirstline() -- 返回当前读取的行在其文件中是否为第一行
7 fileinput.isstdin() -- 返回最后一行是否从 sys.stdin 读取
8 fileinput.nextfile() -- 关闭当前文件,开始读取下一个文件
9 fileinput.close() -- 关闭 FileInput 对象
"""

# 示例程序

import fileinput
# 一次读取多个文件
for line in fileinput.input(files=(r'镜像源安装.txt',r"C:\Users\duty1\Desktop\Python--教学\教学课件\unit01-认识Python和程序的编写方法\StarDraw.txt")):
# 输出文件名,以及当前行在当前文件中的行号
print(fileinput.filename(),fileinput.filelineno(),line,end=" ")

# 关闭文件流
fileinput.close()

 

# 文件迭代器
# -- 文件对象本身就是可遍历的(就像序列一样),因此,程序完全可以使用 for 循环来遍历文件内容

# 示范 -- for 循环读取文件内容
import codecs
# 指定使用 UTF-8 字符集读取文件内容
f = codecs.open(r"镜像源安装.txt",'r','utf-8',buffering=True)
# 使用 for 循环遍历文件对象
for line in f :
print(line,end="")
f.close()

print("----------------------------------------------------------")
# 如果有需要,程序也可以使用 list()函数将文件转换为 list 列表 ,就像文件对象的 readlines()方法的返回值

# 将文件对象转换为list列表
f = codecs.open(r"镜像源安装.txt",'r','utf-8',buffering=True)
print(list(f))
f.close()


print("----------------------------------------------------------")
# sys.stdin 也是一个类文件对象,因此可以通过 for 循环遍历 sys.stdin

# 这就意味着 程序可以通过 for 循环来获取用户的键盘输入

import sys
# 使用 for 循环遍历标准输入
for line in sys.stdin: # 使用 for 循环遍历 sys.stdin
print('用户输入: ',line,end="")

print("*************")

# 程序通过 for 循环来读取用户的键盘输入 -- 用户每输入一行,程序就会输出用户输入的这行

 

# 管道输入 -- 将前一个命令的输出,当作下一个命令的输入
# 语法格式 : cmd1 : cmd2 : cmd3
# 作用 -- cmd1命令的输出,将会作为cmd2命令的输入;cmd2命令的输入,又将作为cmd3的输入

# 管道输入 示范
type ad.txt | python pipein_test.py

"""
type ad.txt 该命令使用 type 读取文件 ad.txt 的内容,并将内容输出到控制台;但由于使用了管道
因此该命令的输出会传递到下一个命令
python pipein_test.py 该命令使用 python 执行 pipein_test.py 程序 。由于该命令前面有管道,
因此他会把前一个命令的输出当作输入
"""