本博客记录的是对Python输入输出的一个简单学习。

一、输入输出基础

最简单直接的输入来自键盘操作,如下:

name = input('your name:')
gender = input('you are a boy?(y/n)')
 
###### 输入 ######
your name:zhu
you are a boy?y

welcome_str = 'Welcome to the matrix {prefix} {name}.'
welcome_dic = {
    'prefix': 'Mr.' if gender == 'y' else 'Mrs',
    'name': name
}

print('authorizing...')
print(welcome_str.format(**welcome_dic))

########## 输出 ##########
authorizing...
Welcome to the matrix Mr. zhu.

input() 函数暂停程序运行,同时等待键盘输入;直到回车被按下,函数的参数即为提示语,输入的类型永远是字符串型(str)。注意,初学者在这里很容易犯错,下面的例子会讲到。print() 函数则接受字符串、数字、字典、列表甚至一些自定义类的输出。

a = input()
1
b = input()
2
print('a + b = {}'.format(a + b))

########## 输出 ##############
a + b = 12

print('type of a is {}, type of b is {}'.format(type(a), type(b)))

########## 输出 ##############
type of a is <class 'str'>, type of b is <class 'str'>
print('a + b = {}'.format(int(a) + int(b)))

########## 输出 ##############
a + b = 3

注意,把 str 强制转换为 int 请用 int(),转为浮点数请用 float()。而在生产环境中使用强制转换时,请记得加上 try except(即错误和异常处理)

Python 对 int 类型没有最大限制(相比之下, C++ 的 int 最大为 2147483647,超过这个数字会产生溢出),但是对 float 类型依然有精度限制。这些特点,除了在一些算法竞赛中要注意,在生产环境中也要时刻提防,避免因为对边界条件判断不清而造成 bug 甚至 0day(危重安全漏洞)。

二、文件输入输出

命令行的输入输出,只是 Python 交互的最基本方式,适用一些简单小程序的交互。而生产级别的 Python 代码,大部分 I/O 则来自于文件、网络、其他进程的消息等等。

接下来,详细分析一个文本文件读写。假设有一个文本文件 in.txt,内容如下:

I have a dream that my four little children will one day live in a nation where they will not be judged by the color of their skin but by the content of their character. I have a dream today. 

I have a dream that one day down in Alabama, with its vicious racists, . . . one day right there in Alabama little black boys and black girls will be able to join hands with little white boys and white girls as sisters and brothers. I have a dream today. 

I have a dream that one day every valley shall be exalted, every hill and mountain shall be made low, the rough places will be made plain, and the crooked places will be made straight, and the glory of the Lord shall be revealed, and all flesh shall see it together.

This is our hope. . . With this faith we will be able to hew out of the mountain of despair a stone of hope. With this faith we will be able to transform the jangling discords of our nation into a beautiful symphony of brotherhood. With this faith we will be able to work together, to pray together, to struggle together, to go to jail together, to stand up for freedom together, knowing that we will be free one day. . . .

And when this happens, and when we allow freedom ring, when we let it ring from every village and every hamlet, from every state and every city, we will be able to speed up that day when all of God's children, black men and white men, Jews and Gentiles, Protestants and Catholics, will be able to join hands and sing in the words of the old Negro spiritual: "Free at last! Free at last! Thank God Almighty, we are free at last!"

首先,我们要清楚 NLP (自然语言处理)任务的基本步骤,也就是下面的四步:

  1. 读取文件;
  2. 去除所有标点符号和换行符,并把所有大写变成小写;
  3. 合并相同的词,统计每个词出现的频率,并按照词频从大到小排序;
  4. 将结果按行输出到文件 out.txt。
import re

# 不用太关心这个函数
def parse(text):
    # 使用正则表达式去除标点符号和换行符
    text = re.sub(r'[^\w ]', ' ', text)
    # 转为小写
    text = text.lower()    
    # 生成所有单词的列表
    word_list = text.split(' ')    
    # 去除空白单词
    word_list = filter(None, word_list)
    # 生成单词和词频的字典
    word_cnt = {}
    for word in word_list:
        if word not in word_cnt:
            word_cnt[word] = 0
        word_cnt[word] += 1
    # 按照词频排序
    sorted_word_cnt = sorted(word_cnt.items(), key=lambda kv: kv[1], reverse=True)    
    return sorted_word_cnt
 
with open('in.txt', 'r') as fin:
    text = fin.read()
    
word_and_freq = parse(text)

with open('out.txt', 'w') as fout:
    for word, freq in word_and_freq:
        fout.write('{} {}\n'.format(word, freq))

########## 输出 (省略较长的中间结果) ########## 
and 15
be 1
will 11
to 11
the 10
of 10
a 8
we 8
day 6 
... 
old 1
negro 1
spiritual 1
thank 1
god 1
almighty 1
are 1

parse() 函数是把输入的 text 字符串,转化为需要的排序后的词频统计。而 sorted_word_cnt 则是一个二元组的列表(list of tuples)。

先要用 open() 函数拿到文件的指针。其中,第一个参数指定文件位置(相对位置或者绝对位置);第二个参数,如果是 ‘r’表示读取,如果是’w’ 则表示写入,当然也可以用 ‘rw’ ,表示读写都要。a 则是一个不太常用(但也很有用)的参数,表示追加(append),这样打开的文件,如果需要写入,会从原始文件的最末尾开始写入。
注:在工作中,代码权限管理非常重要。如果你只需要读取文件,就不要请求写入权限。这样在某种程度上可以降低 bug 对整个系统带来的风险。

在拿到指针后,我们可以通过 read() 函数,来读取文件的全部内容。代码 text = fin.read() ,即表示把文件所有内容读取到内存中,并赋值给变量 text。这么做自然也是有利有弊:

  1. 优点是方便,接下来我们可以很方便地调用 parse 函数进行分析;
  2. 缺点是如果文件过大,一次性读取可能造成内存崩溃。

可以给 read 指定参数 size ,用来表示读取的最大长度。还可以通过 readline() 函数,每次读取一行,这种做法常用于数据挖掘(Data Mining)中的数据清洗,在写一些小的程序时非常轻便。如果每行之间没有关联,这种做法也可以降低内存的压力。而 write() 函数,可以把参数中的字符串输出到文件中。

注:with 语句。open() 函数对应于 close() 函数,也就是说,如果你打开了文件,在完成读取任务后,就应该立刻关掉它。而如果你使用了 with 语句,就不需要显式调用 close()。在 with 的语境下任务执行完毕后,close() 函数会被自动调用,代码也简洁很多。

最后需要注意的是,所有 I/O 都应该进行错误处理。因为 I/O 操作可能会有各种各样的情况出现,而一个健壮(robust)的程序,需要能应对各种情况的发生,而不应该崩溃(故意设计的情况除外)。

三、JSON序列化与实战

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它的设计意图是把所有事情都用设计的字符串来表示,这样既方便在互联网上传递信息,也方便人进行阅读(相比一些 binary 的协议)。JSON 在当今互联网中应用非常广泛,也是每一个用 Python 程序员应当熟练掌握的技能点。

在一个场景下,输入的数据类型各式各样,有整型、浮点型、字符串、布尔型等,全都混在一起,应怎样处理,这时候就可以通过JSON解决,可以理解为两种黑箱。

  1. 第一种,输入这些杂七杂八的信息,比如 Python 字典,输出一个字符串;
  2. 第二种,输入这个字符串,可以输出包含原始信息的 Python 字典。
import json

params = {
    'symbol': '123456',
    'type': 'limit',
    'price': 123.4,
    'amount': 23
}

params_str = json.dumps(params)

print('after json serialization')

print('type of params_str = {}, params_str = {}'.format(type(params_str), params))

original_params = json.loads(params_str)

print('after json deserialization')

print('type of original_params = {}, original_params = {}'.format(type(original_params), original_params))
  1. json.dumps() 这个函数,接受 Python 的基本数据类型,然后将其序列化为 string;
  2. json.loads() 这个函数,接受一个合法字符串,然后将其反序列化为 Python 的基本数据类型。

注:记得加上错误处理。不然,哪怕只是给 json.loads() 发送了一个非法字符串,而你没有 catch 到,程序就会崩溃了。

如果要输出字符串到文件,或者从文件中读取 JSON 字符串,仍然可以使用上面提到的 open() 和 read()/write() ,先将字符串读取 / 输出到内存,再进行 JSON 编码 / 解码,当然这有点麻烦。

import json

params = {
    'symbol': '123456',
    'type': 'limit',
    'price': 123.4,
    'amount': 23
}

with open('params.json', 'w') as fout:
    params_str = json.dump(params, fout) 

with open('params.json', 'r') as fin:
    original_params = json.load(fin)

 
print('after json deserialization')

print('type of original_params = {}, original_params = {}'.format(type(original_params), original_params))

########## 输出 ##########

after json deserialization

type of original_params = <class 'dict'>, original_params = {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}

这样就简单清晰地实现了读写 JSON 字符串的过程。当开发一个第三方应用程序时,你可以通过 JSON 将用户的个人配置输出到文件,方便下次程序启动时自动读取。

总结

主要学习了 Python 的普通 I/O 和文件 I/O,同时了解了 JSON 序列化的基本知识,并通过具体的例子进一步掌握。

参考:
《Python核心技术与实践》