练习 16. 读写文件
如果你做了上一节的附加练习,你应该看到了所有的命令(commands,modules,functions),你可以把这些命令施加给文件。以下是一些我想让你记住的命令:
- close - 关闭文件,就像编辑器中的 “文件->另存为”一样。
- read - 读取文件内容。你可以把读取结果赋给一个变量。
- readline - 只读取文本文件的一行内容。
- truncate - 清空文件。清空的时候要当心。
- write('stuff') - 给文件写入一些“东西”。
- seek(0) - 把读/写的位置移到文件最开头。
这些都是你需要知道的一些非常重要的命令。其中一些要用到参数,但是我们暂且不去重点关注。你只需要记住 write
命令需要你提供一个你要写入的文件的字符串参数。
让我们用这些命令做一个小小的编辑器:
ex16.py
1 from sys import argv
2
3 script, filename = argv
4
5 print(f"We're going to erase {filename}.")
6 print("If you don't want that, hit CTRL-C (^C).")
7 print("If you do want that, hit RETURN.")
8
9 input("?")
10
11 print("Opening the file...")
12 target = open(filename, 'w')
13
14 print("Truncating the file. Goodbye!")
15 target.truncate()
16
17 print("Now I'm going to ask you for three lines.")
18
19 line1 = input("line 1: ")
20 line2 = input("line 2: ")
21 line3 = input("line 3: ")
22
23 print("I'm going to write these to the file.")
24
25 target.write(line1)
26 target.write("\n")
27 target.write(line2)
28 target.write("\n")
29 target.write(line3)
30 target.write("\n")
31
32 print("And finally, we close it.")
33 target.close()
这真是一个很大的文件,可能是你输入过的最大的文件了。所以慢一点,写完检查一下,然后再运行。你也可以写一点运行一点,比如先运行 1-8 行,然后再多运行 5 行,然后再多几行,直到所有的都完成和运行了。
你应该看到
事实上你应该看到两样东西,首先是你新脚本的输出结果:
练习 16 会话
$ python3.6 ex16.py test.txt We're going to erase test.txt.
If you don't want that, hit CTRL-C (^C). If you do want that, hit RETURN.
?
Opening the file...
Truncating the file. Goodbye!
Now I'm going to ask you for three lines.
line 1: Mary had a little lamb
line 2: Its fleece was white as snow
line 3: It was also tasty
I'm going to write these to the file.
And finally, we close it.
现在,用编辑器打开你创建的文件(比如我的是 test.txt),检查一下是不是对的。
附加练习
- 如果你理解不了这个练习,回过头去按照给每行加注释的方法再过一遍,注释能帮助你理解每一行的意思,至少让你知道你不理解的地方在哪里,然后动手去查找答案。
- 写一个类似于上个练习的脚本,使用
read
和argv
来读取你刚刚创建的文件。- 这个练习中有太多的重复,试着用一个
target.write()
命令来打印 line1、line2、line3,你可以使用字符串、格式字符串和转义字符。- 弄明白为什么我们要用一个
'w'
作为一个额外的参数来打开。提示:通过明确说明你想要写入一个文件,来安全地打开它。- 如果你用
w
模式打开文件,那你还需要target.truncate()
吗? 读一读 Python 的 open 函数文件,来搞明白这个问题。
常见问题
truncate()
对于 'w'
参数来说是必须的吗? 详见附加练习 5。
'w'
到底是什么意思? 它真的只是一个有字符的字符串,来表示文件的一种模式。如果你用了 'w'
,就代表你说“用 ‘write’ 模式打开这个文件。此外还有 'r'
表示 read 模式,'a'
表示增补模式,后面还可能加一些修饰符(modifiers)。
我能对文件使用哪些修饰符? 目前最重要的一个就是 +
,你可以用 'w+'
, 'r+'
以及 'a+'
。这样会让文件以读和写的模式打开,取决于你用的是那个符号以及文件所在的位置等。
如果只输入 open(filename)
是不是就用 'r'
(读)模式打开? 是的,那是 open()
函数的默认值。
练习 17. 更多文件
现在让我们对文件做更多新的操作。我们会写一个 Python 脚本来把一个文件复制成另一个。代码会非常短,但是能让你学会对文件做更多的操作。
ex17.py
1 from sys import argv
2 from os.path import exists
3
4 script, from_file, to_file = argv
5
6 print(f"Copying from {from_file} to {to_file}")
7
8 # we could do these two on one line, how?
9 in_file = open(from_file)
10 indata = in_file.read()
11
12 print(f"The input file is {len(indata)} bytes long")
13
14 print(f"Does the output file exist? {exists(to_file)}")
15 print("Ready, hit RETURN to continue, CTRL-C to abort.")
16 input()
17
18 out_file = open(to_file, 'w')
19 out_file.write(indata)
20
21 print("Alright, all done.")
22
23 out_file.close()
24 in_file.close()
你应该会很快注意到我们输出了另一个常用命令 exists
。它会基于一个字符串里面的变量文件名来判断,如果一个文件存在,它就会返回 True
,不存在就会返回 False
。我们会在这本书的下半部分经常使用这个函数,现在你只用知道你是如何输出它的。
使用 import
可以调出海量的免费代码,这些是程序员已经写过的代码,你就不用重复造轮子了。
你会看到
像你其他的脚本文件一样运行 ex17.py ,再加上两个变量:一个是要复制的源文件,一个是要复制到的目标文件。我会使用一个叫 test.txt 的示例文件:
练习 17 会话
$ # first make a sample file $
echo "This is a test file." > test.txt
$ # then look at it
$ cat test.txt
This is a test file.
$ # now run our script on it
$ python3.6 ex17.py test.txt new_file.txt
Copying from test.txt to new_file.txt
The input file is 21 bytes long
Does the output file exist? False
Ready, hit RETURN to continue, CTRL-C to abort.
Alright, all done.
它应该对每一个文件都适用,多试一些看看会发生什么。注意不要动重要的文件。
警告! |
---|
你应该注意到我使用了 cat 命令来显示文件内容。如果你不懂,可以在附录 A 的命令行速成教程中学到这个命令。 |
附加练习
- 这个脚本真的很烦人。在复制之前其实没必要问你,而且它打印了太多内容,试着通过删掉一些特征让这个脚本更简洁一些。
- 看看你把这个脚本缩到多短,我可以把它变成一行。
- 注意“你会看到”部分,我用了
cat
,这是一种把文件打印到屏幕的简单办法,你可以输入man cat
来看看关于这个命令的作用。- 弄明白你为什么得在代码里写
out_file.close()
。- 去读读 Python 的 import statement,然后自己试试 import 一些东西,看能不能成功运行。不成功也没关系。
常见问题
为什么 'w'
要用引号? 因为这是一个字符串。你已经用了一段时间字符串了,确保你知道它的含义。
你不可能把那些代码变成一行! 那;取决于;你;如何;定义;一行;代码。
我觉得这个练习很难,这正常吗? 是的,很正常。甚至到练习 36 的时候,或者学完这本书的时候,你可能还是会觉得很难。每个人的情况都不一样,所以坚持学下去,坚持做练习,要有耐心。
len()
函数是什么作用? 它能够取字符串的长度,然后返回一个数字。你可以试着玩玩。
但我试着把这些代码缩短的时候,我在关闭文件时遇到了错误。 你可能用了 indata = open(from_file).read()
,这意味着你不需要在之后再输入 in_file.close()
,因为你已经到了脚本的最后。一旦那一行运行过之后,它就已经被 Python 关掉了。
**我收到了一个这样的错误:Syntax:EOL while scanning string literal
。你忘了在字符串后面加引号了,再检查一遍的代码。
练习 18 名称,变量,代码,函数
这是一个很大的标题。接下来我要给你介绍一下函数。每一个程序员都要一遍一遍地用到函数,思考它们的作用以及如何使用它们,但是我会给你一些最简单的解释,让你能够快速上手。
函数一般就是做以下这些事情:
- 它们为一些代码起名字,就像变量为字符串和数字起名字一样。
- 它们像脚本获取
argv
一样获取参数(arguments)。- 通过 1 和 2 的操作,让你做一些你自己的“小脚本”或者“微命令”。
你可以通过在 Python 中使用 def
来创建一个函数。我会让你创建 4 个不同的函数,它们就像你的脚本一样运行,之后我还会想你展示每一个之间是如何关联的。
ex18.py
1 # this one is like your scripts with argv
2 def print_two(*args):
3 arg1, arg2 = args
4 print(f"arg1: {arg1}, arg2: {arg2}")
5
6 # ok, that *args is actually pointless, we can just do this
7 def print_two_again(arg1, arg2):
8 print(f"arg1: {arg1}, arg2: {arg2}")
9
10 # this just takes one argument
11 def print_one(arg1):
12 print(f"arg1: {arg1}")
13
14 # this one takes no arguments
15 def print_none():
16 print("I got nothin'.")
17
18
19 print_two("Zed","Shaw")
20 print_two_again("Zed","Shaw")
21 print_one("First!")
22 print_none()
让我们把第一个函数拆解一下,print_two
这是你从创建脚本中已经学到的最熟悉的东西:
- 首先,我们告诉 Python 我们想要用
def
(即 define)来创建一个函数。- 在
def
的同一行我们给了函数一个名字,本例中是print_two
,但是你也可以起名叫“peanuts”(花生),名字没关系,不过最好简短一些,并且能够说明这个函数的作用。- 然后我们告诉它我们想要
*args
,它很像参数args
,只不过是为函数设的,必须放在括号里面才能工作。- 然后我们以
:
结束这一行,另起一行开始缩进。- 在
:
之后缩进四个空格的所有行都是关于print_two
这个函数名的。我们第一个缩进的行就是用来解包这个参数(argument),跟之前的脚本一样。- 要表明它是如何工作的,我们把这些参数打印了出来,就像我们在脚本中所做的一样。
print_two
的问题是它不是创建一个函数最简单的方法。在 python 里面,我们可以跳过整个解包参数的过程,只用我们需要的 ()
里面的名字即可,这也正是 print_two_again
所做的事情。
之后我们用一个参数创建了 print_one
这个函数。
最后我们创建了一个没有参数的函数 print_none
。
警告! |
---|
这很重要。如果你现在不太明白,别急着灰心,我们会再做几个跟函数相关的练习来进一步学习。现在当我说“函数”的时候,你就把它想象成一个“迷你脚本”,跟着做就行了。 |
你会看到
如果你运行了 ex18.py
,你会看到:
练习 18 会话
$ python3.6 ex18.py
arg1: Zed, arg2: Shaw
arg1: Zed, arg2: Shaw
arg1: First!
I got nothin'.
现在你已经看到了函数是如何工作的。注意你使用函数的方式就像你使用 exists、open 等其他一些“命令”一样。其实我一直在跟你卖关子,因为在 python 里面,这些“命令”就是“函数”(学习癌注:哈哈哈哈哈老肖太坏了)。这意味着你可以创建你自己的命令然后在你的脚本中使用。
附加练习
创建一个如下的函数 checklist (核查表)用于后面的练习。把这些内容写在索引卡上,一直保留到你完成所有剩余练习的时候或者当你感觉你不再需要这些索引卡的时候:
- 你是否用
def
来创建函数了?- 你的函数名是只包含字符和
_
(下划线)吗?- 你在函数名后面放
(
(左圆括号)了吗?- 你在左圆括号后面放参数(argument)了吗?参数之间是以逗号隔开的吗?)
- 你的每个参数都是唯一的吗(即没有重名)?
- 你在参数后面放
)
(右圆括号)和:
(冒号)了吗?- 你在与这个函数相关的代码行前面加上四个空格的缩进了吗?(不能多,也不能少)
- 你是通过另起一行不缩进来结束你的函数的吗?
当你运行(使用或者调用)一个函数时,检查以下事项:
- 你是通过输入函数名称来运行/调用/使用一个函数的吗?
- 你运行的时候有在名称后面加
(
吗?- 你有把你想要的值放在圆括号里并用逗号隔开了吗?
- 你是以
)
来结束调用这个函数的吗?
在接下来的课程中用这两个 checklist ,直到你不再需要它们为止。
最后,再强调以下,我说的“运行”(run)、“调用”(call)、“使用”(use)都是一个意思。
常见问题
函数名称有哪些要求?跟变量名一样,任何不以数字开头的字母、数字、下划线组合都可以。
*args
中的 *
是什么作用?这是告诉 Python 取所有的参数给函数,然后把它们放在 args
里放成一列,很像你之前学的 argv
,只不过这个是为函数设置的。这种不常用,除非有特殊需要。
这部分好无聊好烦人啊。这就对了,这说明你已经开始一边输入代码一边思考它的作用了。如果想让它不这么无聊,按照我的要求一字不差地输入进去,然后再故意打乱它们,看看你能不能修复好。