【Python基础】文件 I/O,JSON 序列化

Python I/O 基础

最简单直接的输入来自键盘操作,input() 函数暂停程序运行,同时等待键盘输入;直到回车被按下,函数的参数即为提示语。

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

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)) # 调用函数时,在 dict前加两个星号会把字典的键值对变成关键字参数。

###### 输入 ######
your name:Jack
you are a boy? y

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

要注意的是,input() 输入的类型永远是字符串型(str),当需要把 str 型强制转换为 int 型时用 int(),转为浮点数则用 float()。而 print() 函数则接受字符串、数字、字典、列表甚至一些自定义类的输出。

a = input('a:')
b = input('b:')
print('a + b = {}'.format(a + b))
###### 输入 ######
a:1
b:2
########## 输出 ##############
a + b = 12 # input()输入的类型永远是字符串型,两个 str相加是字符串拼接操作

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))) # 使用 int()强制转换为 int型
a + b = 3

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

文件 I/O

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

接下来,我们来做一个简单的 NLP(自然语言处理)任务。假设有一个文本文件 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!"

任务的要求是:

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

参考代码如下:

import re

def parse(text): # parse()把输入的 text字符串,转化为我们需要的排序后的词频统计
    # 使用正则表达式去除标点符号和换行符
    '''
	r'...': 正则命令字符串
    [^x]: 对x取反
    \w : 字母、数字、下划线、汉字
    r'[^\w]': 所有不是字母、数字、下划线、汉字的字符
    re.sub(r'...',' ',text) : 将text中的满足第一参数的字符替换成第二参数指定的字符
	'''
    text = re.sub(r'[^\w ]', ' ', text) # 将所有不是字母、数字、下划线、汉字的字符替换成空格
    # 转为小写
    text = text.lower()    
    # 生成所有单词的列表
    word_list = text.split(' ') # 按空格分隔出每个单词
    # 去除空白单词
    # filter()过滤掉不符合条件的元素,函数接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
    # 这里的 None,严格意义上等于 lambda x: x,就是把序列中的False值,如空字符串、0、False、None、[]、{}、()等等都过滤掉。
    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 # 返回的 sorted_word_cnt是一个二元组的列表(list of tuples)。

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 13
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

我们先要用 open() 函数拿到文件的指针。其中,第一个参数指定文件所在位置(相对位置或者绝对位置);第二个参数,如果是 ‘r’ 表示读取,如果是 ‘w’ 则表示写入,当然也可以用 ‘rw’ ,表示读写都要。‘a’ 则是一个不太常用(但也很有用)的参数,表示追加(append),每次打开后会从原始文件的最末尾继续写入。

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

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

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

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

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

JSON 序列化

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它的设计意图是把所有事情都用设计的字符串来表示,这样既方便在互联网上传递信息,也方便人进行阅读(相比一些 binary 的协议)。你可以把它简单地理解为两种黑箱:

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

读写 JSON 字符串的具体过程,主要是利用 json 包中的两个函数:

  • json.dumps() 这个函数,接受 Python 的基本数据类型,然后将其序列化为 string;
  • 而 json.loads() 这个函数,接受一个合法字符串,然后将其反序列化为 Python 的基本数据类型。

JSON 在当今互联网中应用非常广泛,比如,当开发一个第三方应用程序时,你就可以通过 JSON 将用户的个人配置输出到文件,方便下次程序启动时自动读取。这也是现在普遍运用的成熟做法。那么 JSON 是唯一的选择吗?显然不是,它只是轻量级应用中最方便的选择之一。据我所知,在 Google,有类似的工具叫做 Protocol Buffer,相比于 JSON,它的优点是生成优化后的二进制文件,因此性能更好。但与此同时,生成的二进制序列,是不能直接阅读的。它在 TensorFlow 等很多对性能有要求的系统中都有广泛的应用。

参考

《Python核心技术与实战》

正则表达式手册:https://tool.oschina.net/uploads/apidocs/jquery/regexp.html
Python函数中 * 和 ** 的内涵:
https://blog.csdn.net/sinat_38682860/article/details/85254619
https://www.zhihu.com/question/265519629/answer/310549070

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值