4. python黑箱:输入与输出
4.1 输入输出基础
最简单的输入输出:
- age = input(“how old are you?”) :特别需要注意input()函数的用法的是,输入的变量最终都存储为字符串类型
- print()
a = input()
1
b = input()
2
print('a + b = {}'.format(a + b))
########## 输出 ##############
a + b = 12 # 注意是字符串12而不是整数3
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
强制转换的注意事项
上面的代码中用到了强制类型转换,在Python中:
- 把 str 强制转换为 int 要用 int(),转为浮点数要用float()
- 在生产环境使用强制类型转换时,要记得使用错误和异常处理(try…except)
Python中的int类型没有最大限制(相比之下,C++的int最大为2147483647,超过这个数字就会产生溢出),但对float类型有精度限制。这些特点除了在一些算法竞赛中要注意,在生产环境中也要时刻堤防,避免因边界问题不清而引起bug甚至0day(危重安全漏洞)
有相当比例的安全漏洞,都来自于随意的I/O操作
4.2 文件输入输出
生产级别的Python代码,大部分I/O则来自文件,网络,其他进程的消息
以下是一个简单的NLP任务:读取文件中的文本,统计词频将统计结果存储在新建文件中
NLP小任务——词频统计:
import re
def parse(text, word_count_dic):
# 大写转小写
text = text.lower()
# 构建这一行字符串的单词列表
word_list = re.findall(r'\w+',text)
# 统计出现的各个单词以及的次数,存于字典结构中
for word in word_list:
word_count_dic[word] = word_count_dic.get(word, 0) + 1 # 这里还可以使用defaultdict字典
return word_count_dic
fin_path = 'G:\Python_Projects\I have a dream.txt'
fout_path = 'G:\Python_Projects\I have a dream parse.txt'
with open(fin_path, 'r') as fin:
word_count_dic = {}
# 逐行读取再进行处理而不是直接fin.read(),避免文件过大一次读入会爆内存
for line in fin:
word_count_dic = parse(line, word_count_dic)
with open(fout_path, 'w') as fout:
# 将单词按出现的词频排序,sorted()函数返回的 word_count_dic为二元组列表(list of tuples)
word_count_dic = sorted(word_count_dic.items(), key=lambda x:x[1], reverse=True)
for word, freq in word_count_dic:
fout.writelines(f"{word}:{freq}\n")
上面的代码:
word_count_dic[word] = word_count_dic.get(word, 0) + 1
完成了对词频的更新,在这里介绍另一种方法:可使用collections里的defaultdict字典,接受工厂函数作为参数,如dict =defaultdict( factory_function),作用是当key不存在时,返回的是工厂函数的默认值,int对应0,list对应[],str对应的是空字符串,set对应set( )
以下是利用defaultdict完成对文本词频的统计:
from collections import defaultdict
import re
f = open("ini.txt", mode="r", encoding="utf-8")
dic = defaultdict(int) # 若key不存在,添加该键且对应value为int的默认值0
for line in f:
for word in filter(lambda x: x, re.split(r"\s", line)):
dic[word] += 1
print(dic)
- open(path, “r”/“w”/“rw”/“a”)函数拿到文件的指针("w"模式注意覆盖问题)
- f.read(size)指定读取的字串长度大小
- 尽量用with语句打开文件,函数会自动到之后关闭文件,就不需要自己再写f.close()
需要注意的是,所有I/O操作都需要错误和异常处理,因为I/O操作可能有各种各样的情况出现,否则可能会导致程序崩溃
4.3 JSON序列化与实战
之前学习过用“泡菜”pickle存储文件数据,这里的JSON也有类似的功能和实现方法
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它的设计意图是把所有事情都用设计的字符串表示,这样便于信息的传递和阅读(相比一些binary协议)
下面以一个应用场景来运用JSON:
JSON就能解决这种场景,我们可以把JSON理解为这样的两种黑箱:
- 第一种: \quad 输入:一些杂乱的信息(如字典); \quad 输出:一个字符串
- 第二种: \quad 输入:一个字符串; \quad 输出:原始信息(字典)
具体代码如下:
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))
########## 输出 ##########
after json serialization
type of params_str = <class 'str'>, params_str = {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}
after json deserialization
type of original_params = <class 'dict'>, original_params = {'symbol': '123456', 'type': 'limit', 'price': 123.4, 'amount': 23}
其中:
- json.dumps()这个函数接收Python的基本数据类型,并将其序列化为字符串
- json.loads()这个函数接收一个合法的字符串,并将其反序列化为Python的基本数据类型
还是需要注意的是,记得加上错误和异常处理。否则,哪怕只是给json.loads()传递了一个非法字符串却没有catch到,程序就会奔溃。
若要输出字符串到文件,或者从文件中读取json字符串,也即涉及文件操作的JSON解码/编码的情况,这时要用json.dump(params, file) 和 json.load(file):
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将用户的个人配置保存输出到文件,方便下次程序启动时自动读取。这也是现在普遍使用的成熟做法。
思考题:
省略了一些异常处理:
server.py
# 我们假设 server 电脑上的所有的文件都在 BASR_DIR 中,为了简化不考虑文件夹结构,网盘的路径在 NET_DIR
import os
from shutil import copyfile
import time
BASE_DIR = 'server/'
NET_DIR = 'net/'
def main():
filenames = os.listdir(BASE_DIR)
for i, filename in enumerate(filenames):
print('copying {} into net drive... {}/{}'.format(filename, i + 1, len(filenames)))
copyfile(BASE_DIR + filename, NET_DIR + filename)
print('copied {} into net drive, waiting client complete... {}/{}'.format(filename, i + 1, len(filenames)))
while os.path.exists(NET_DIR + filename):
time.sleep(3)
print('transferred {} into client. {}/{}'.format(filename, i + 1, len(filenames)))
if __name__ == "__main__":
main()
client.py
# 我们假设 client 电脑上要输出的文件夹在 BASR_DIR ,网盘的路径在 NET_DIR
import os
from shutil import copyfile
import time
BASE_DIR = 'client/'
NET_DIR = 'net/'
def main():
while True:
filenames = os.listdir(NET_DIR)
for filename in filenames:
print('downloading {} into local disk...'.format(filename))
copyfile(NET_DIR + filename, BASE_DIR + filename)
os.remove(NET_DIR + filename) # 我们需要删除这个文件,网盘会提我们同步这个操作,从而 server 知晓已完成
print('downloaded {} into local disk.'.format(filename))
time.sleep(3)
if __name__ == "__main__":
main()