在 Linux 系统中万物皆文件,所以我们不可避免的要和文件打交道,我们会常常对文件进行读和写的操作。例如:
cat /etc/password # 读文件
vim /etc/password # 读写文件
echo test > /tmp/abc.txt # 覆盖写文件
echo text >> /tmp/abc.txt # 追加写文件
而以上内容我们都是对文本文件进行读写,计算机中也存在对二进制文件的读写操作,那用 Python 如何实现呢?
不管是读文件还是写文件,我们第一步都是要将文件打开。
作为打开文件之门的“钥匙”,内建函数 open() 提供了初始化输入/输出(I/O)操作的通用接口,成功打开文件后时候会返回一个文件对象,否则引发一个错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhPn4usm-1653320533809)(day03.assets/image-20210810162704041.png)]
要以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开打文件,这样才能访问它。
**file_name:**表示我们要打开文件的路径
**mode:**以怎样的方式打开文件
文件模式 | 操 作 |
---|---|
r | 以读方式打开(文件不存在则报错) |
w | 以写方式打开(文件存在则清空,不存在则创建) |
a | 以追加模式打开 |
b | 以二进制模式打开 |
**file_object:**文件操作对象,我们后续对文件的所有读写操作都需要通过这个对象,而不是直接操作文件中的数据。
要使用文本文件中的信息,首先需要将信息读取到内存中。为此,我们可以一次性读取文件的全部内容,也可以以每次一行的方式逐步读取。
open
函数的第一个参数是要打开的文件名(文件名区分大小写)
read
方法可以一次性 读入 并 返回 文件的 所有内容close
方法负责 关闭文件
read
方法执行后,会把 文件指针 移动到 文件的末尾# 1. 打开 - 文件名需要注意大小写
file = open("文件路径", "r")
# 2. 读取
text = file.read() # 一次性将文件中的内容全部读取出来
print(text) # 将读取到的内容打印再控制台上
# 3. 关闭
file.close()
read
方法后,文件指针 会移动到 读取内容的末尾
read
方法,读取了所有内容,那么再次调用 read
方法,还能够获得到内容吗?答案
read
方法默认会把文件的 所有内容 一次性读取到内存
如果文件太大,对内存的占用会非常严重
readline
方法可以一次读取一行内容
方法执行后,会把 文件指针 移动到下一行,准备再次读取
# 打开文件
file = open("文件路径", mode="r")
while True:
# 读取一行内容
text = file.readline()
# 判断是否读到内容
if len(text) == 0:
break
# 每读取一行的末尾已经有了一个 \n
print(text, end="")
# 关闭文件
file.close()
readlines()
方法读取所有(剩余的)行然后把它们作为一个 字符串列表 返回
f = open("/tmp/passwd", mode="r")
for line in f: # 相当于 for line in f.readlines():
print(line, end=" ")
read()
和 readline()
相反
readlines()
一样,writelines()
方法是针对 列表 的操作f = open("./d.txt", mode="w")
f.writelines(['1st line.\n', '2nd line.\n','3rd line.\n'])
f.close()
图例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQFzXxVJ-1653320533818)(day03.assets/image-20210820103515549.png)]
with语句 是用来简化代码的
在将打开文件的操作放在 with 语句中,代码块结束后,文件将自动关闭
读写文件的逻辑没有变化,变得只是 写法
with open('/tmp/passwd', mode="r") as f:
f.readline()
# 模拟 cp 操作
# 1. 创建 cp.py 文件
# 2. 将 /usr/bin/ls "拷贝" 到/tmp 目录下
# 3. 不要修改原始文件
# 创建两个对象变量,f1为原文件;f2 为要写入数据的目标文件
# 因为是二进制文件,以字节的方式进行读写
f1 = open('/usr/bin/ls', mode='rb')
f2 = open('/tmp/list', mode='wb')
# 从用f1从原文件 /usr/bin/ls 中读取数据,并将数据存储在变量data中
# f2将变量data中的内容,写入到目标文件/tmp/list中
data = f1.read()
f2.write(data)
#关闭文件f1和文件f2
f1.close()
f2.close()
查看原文件和目标文件的md5值,是否相等
[root@localhost xxx]# md5sum /tmp/ls /tmp/list
# 创建两个变量,src_fname 存储源文件路径;dst_fname 存储目标文件路径
src_fname = '/usr/bin/ls'
dst_fname = '/tmp/list2'
# 创建两个对象变量,src_fobj为打开原文件;src_fobj为打开目标文件
# 因为是二进制文件,以字节的方式进行读写
src_fobj = open(src_fname, mode='rb')
dst_fobj = open(dst_fname, mode='wb')
while 1: # 不确定读取次数,采用while循环
data = src_fobj.read(4096) # 每次从元文件中读取4k
if len(data) == 0: # data为0,代表指针指向末尾,数据读完
break # 退出整个while循环
else:
dst_fobj.write(data) # 将data数据写入list2文件中
src_fobj.close() # 关闭原文件/usr/bin/ls
dst_fobj.close() # 关闭目标文件 /tmp/lists
# 早上洗衣服
print("打水")
print("洗衣服")
print("甩干")
# 中午洗衣服
print("打水")
print("洗衣服")
print("甩干")
# 晚上洗衣服
print("打水")
print("洗衣服")
print("甩干")
发现了问题:我们将有独立功能的代码封装成一个函数
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("洗衣服")
print("甩干")
# 优化后代码
# 早上洗衣服
washing_machine() # 打开洗衣机开关
# 中午洗衣服
washing_machine() # 打开洗衣机开关
# 晚上洗衣服
washing_machine() # 打开洗衣机开关
函数用 def
语句创建,语法如下:
def 函数名(参数列表): # 具体情况具体对待,参数可有可无
"""函数说明文档字符串"""
函数封装的代码
……
标题行由 def
关键字,函数的名字,以及参数的集合(如果有的话)组成
def
子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必需的函数体
函数名称 的命名应该 符合 标识符的命名规则
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("洗衣服")
print("甩干")
使用一对圆括号 () 调用函数,如果没有圆括号,只是对函数的引用
任何输入的参数都必须放置在括号中
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("加洗衣粉!!!")
print("洗衣服")
print("甩干")
# 早上洗衣服
washing_machine()
# 中午洗衣服
washing_machine()
# 晚上洗衣服
washing_machine()
能否将 函数调用 放在 函数定义 的上方?
Python
已经知道函数的存在NameError: name 'menu' is not defined
(名称错误:menu 这个名字没有被定义)当我们想洗其他的东西,要手动改方法内部的代码:
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("加洗衣粉!!!")
print("洗床单") # 洗被套
print("甩干")
在函数内部有一定的变化的值:
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("加洗衣粉!!!")
print("洗衣服")
print("甩干")
washing_machine()
def washing_machine(): # 洗衣机可以帮我们完成
print("打水")
print("加洗衣粉!!!")
print("洗床单")
print("甩干")
washing_machine()
......
思考一下存在什么问题
函数只能处理 固定 的数据
如何解决?
,
分隔def washing_machine(something): # 洗衣机可以帮我们完成
print("打水")
print("加洗衣粉!!!")
print("洗" + something)
print("甩干")
# 洗衣服
washing_machine("衣服")
# 洗床单
washing_machine("床单")
与 shell
脚本类似,程序名以及参数都以位置参数的方式传递给 python 程序,使用 sys
模块的 argv
列表接收
import sys
print(sys.argv[1], sys.argv[2]) #类比shell: $1 $2
def get_sum(a,b):
print(a+b)
#位置参数
#a=sys.argv[1],b=sys.argv[2]
#通过终端传来的参数都是字符串类型----这里需要类型转换
get_sum(int(sys.argv[1]),int(sys.argv[2]))
默认参数就是声明了 默认值 的参数,因为给参数赋予了默认值,所以在函数调用时,不向该参数传入值也是允许的
def get_sum(num1, num2, num3=10):
print(num1 + num2 + num3)
# 默认参数
# 如果没有手动给形参赋值,那么就用默认值
# 定义函数时,有默认值的参数一定放在没有默认值参数的后面
get_sum(1,2,3) # num01=1, num02=2, num03=3 6
get_sum(1,2) # num01=1, num02=2, num03=10 13
def get_sum1(num01=5, num02): 报错
print(num01 + num02)
def get_sum2(num1=10, num2=20, num3=30):
print(num1 + num2 + num3)
get_sum2() # 60
return
关键字可以返回结果注意:
return
表示返回,表示方法执行结束,后续的代码都不会被执行
洗完衣服后我们需要把衣服需要使用 return
关键字将每一单的价格返回,告诉帐房先生:
def get_sum(num01, num02):
return num01 + num02
如果方法内部没有 return
语句,那么会默认返回 None,即 return None
斐波那契数列函数
版本一:方法内部直接打印
def gen_fib():
fib = [0, 1] # 定义列表,指定斐波那契数列的初始两个值
n = int(input('长度: ')) # 定义变量n, 此变量为用户要看到的列表fib中的元素个数
for i in range(n - 2):
fib.append(fib[-1] + fib[-2])
print(fib) # 打印列表fib
# 调用两次函数gen_fib()
gen_fib()
gen_fib()
版本二:带返回值
def gen_fib():
fib = [0, 1]
n = int(input('长度: '))
for i in range(n - 2):
fib.append(fib[-1] + fib[-2])
return fib # 返回最后生成的列表fib
# 调用函数gen_fib()
print(gen_fib())
版本三:带参数
def gen_fib(n):
fib = [0, 1]
for i in range(n - 2):
fib.append(fib[-1] + fib[-2])
return fib # 返回最后生成的列表fib
# 定义列表nlist, 将要产生的斐波那契数列的长度,作为列表nlist的元素
nlist = [10, 8, 6]
# 使用for循环,传递实参(nlist中的元素)给函数gen_fib(n),得到三组斐波那契数列
for i in nlist:
print(gen_fib(i))
版本1:
# 定义函数copy(),实现指定单个文件拷贝的功能
def copy():
src_name = '/usr/bin/ls'
dst_name = '/tmp/list3'
# 以只读字节的方式打开源文件,赋值给变量src_fobj
# 以写入字节的方式打开源文件,赋值给变量dst_fobj
src_fobj = open(src_name, 'rb')
dst_fobj = open(dst_name, 'wb')
while 1:
data = src_fobj.read(4096)
if len(data) == 0:
break
dst_fobj.write(data)
src_fobj.close()
dst_fobj.close()
copy() # 调用拷贝文件的函数copy()
版本2:
def copy(src_name, dst_name): # 定义函数copy(),实现任意文件的拷贝操作
# 以只读字节的方式打开源文件,赋值给变量src_fobj
# 以写入字节的方式打开源文件,赋值给变量dst_fobj
src_fobj = open(src_name, 'rb')
dst_fobj = open(dst_name, 'wb')
while 1:
data = src_fobj.read(4096)
if len(data) == 0:
break
dst_fobj.write(data)
src_fobj.close()
dst_fobj.close()
copy(源文件路径, 目标文件路径) # 使用sys模块的,argv列表获取位置参数
可以提升开发效率,简化代码
正确使用
# test.py,将 file_copy.py 放在同级目录下
# 需求:要将/etc/passwd复制到/tmp/passwd
src_path = "/etc/passwd"
dst_path = "/tmp/passwd"
# 如何复制?
# 调用已有模块中的方法
# - 很推荐,简单粗暴不动脑
# - 直接使用 file_copy.py的方法即可
# 导入方法一:直接导入模块
import file_copy # 要注意路径问题
file_copy.copy(src_path, dst_path)
# 导入方法二:只导入 file_copy 模块的 copy 方法
from file_copy import copy # 如果相同时导入多个模块 from file_copy import *
copy(src_path, dst_path)
# 导入方法四:导入模块起别名 as
import file_copy as fc
fc.copy(src_path, dst_path)
模块在被导入时,会先完整的执行一次模块中的 所有程序
# foo.py
print(__name__)
# bar.py
import foo # 导入foo.py,会将 foo.py 中的代码完成的执行一次,所以会执行 foo 中的 print(__name__)
结果:
# foo.py -> __main__
当模块文件直接执行时,__name__的值为‘__main__’
# bar.py -> foo
当模块被另一个文件导入时,__name__的值就是该模块的名字
所以我们以后在 Python 模块中执行代码的标准格式:
def test():
......
if __name__ == "__main__":
test()
创建 randpass.py 脚本,要求如下:
编写一个能生成 8 位随机密码的程序
使用 random 的 choice 函数随机取出字符
改进程序,用户可以自己决定生成多少位的密码
版本一:
import random # 调用随机数模块random
# 定义变量all_chs,存储密码的所有选择;
# 定义变量result,存储8位随机数,初值为''
all_chs = '1234567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQLMNUVWXYZ'
result = ''
#使用for循环,循环8次,每次从all_chs中随机产生一个字符,拼接到result中
for i in range(8):
ch = random.choice(all_chs)
result += ch
print(result) # 输出结果,右键执行【Run 'randpass'】,查看结果
版本二(优化):函数化程序,并可以指定密码长度,在randpass.py文件中操作
import random # 调用随机数模块random
# 定义变量all_chs,存储密码的所有选择;
all_chs = '1234567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQLMNUVWXYZ'
def randpass(n=8): # 使用def定义函数randpass(), 生成随机8位密码
result = ''
for i in range(n):
ch = random.choice(all_chs)
result += ch
return result # return给函数返回密码
if __name__ == '__main__': # 测试代码块,__name__作为python文件调用时,执行代码块
print(randpass(8))
print(randpass(4))
版本三:随机密码的字符选择可以调用模块
# 调用随机数模块random
# string模块中的变量ascii_letters和digits中,定义了大小写字母和所有数字
# 【Ctrl + 鼠标左键】可以看到 ascii_letters 的模块文件内容
import random
from string import ascii_letters, digits
# 定义变量all_chs,存储密码的所有选择;
all_chs = ascii_letters + digits
# 使用def定义函数randpass(), 生成随机8位密码
def randpass(n=8):
result = ''
for i in range(n):
ch = random.choice(all_chs)
result += ch
return result # return给函数返回密码
# 测试代码块,__name__作为python文件调用时,执行代码块
if __name__ == '__main__':
print(randpass(8))
print(randpass(4))