Python “黑箱”:輸入與輸出

世紀(jì)之交的論壇上曾有一句流行語:在互聯(lián)網(wǎng)上村怪,沒人知道你是一條狗法瑟〖较ィ互聯(lián)網(wǎng)剛剛興起時,一根網(wǎng)線鏈接到你家霎挟,信息通過這條高速線纜直達(dá)你的屏幕窝剖,你通過鍵盤飛速回應(yīng)朋友的消息,信息再次通過網(wǎng)線飛入錯綜復(fù)雜的虛擬世界酥夭,再進(jìn)入朋友家赐纱。抽象來看,一臺臺的電腦就是一個個黑箱熬北,黑箱有了輸入和輸出疙描,就擁有了圖靈機(jī)運作的必要條件。

Python 程序也是一個黑箱:通過輸入流將數(shù)據(jù)送達(dá)讶隐,通過輸出流將處理后的數(shù)據(jù)送出起胰,可能 Python 解釋器后面藏了一個人,還是一個史萊哲林巫延?No one cares效五。

好了廢話不多說,今天我們就由淺及深講講 Python 的輸入和輸出炉峰。

輸入輸出基礎(chǔ)

最簡單直接的輸入來自鍵盤操作畏妖,比如下面這個例子。


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

###### 輸入 ######
your name:codewyf
you are a boy?

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. Jack.

input() 函數(shù)暫停程序運行疼阔,同時等待鍵盤輸入瓜客;直到回車被按下,函數(shù)的參數(shù)即為提示語竿开,輸入的類型永遠(yuǎn)是字符串型(str)谱仪。注意,初學(xué)者在這里很容易犯錯否彩,下面的例子我會講到疯攒。print() 函數(shù)則接受字符串、數(shù)字列荔、字典敬尺、列表甚至一些自定義類的輸出枚尼。我們再來看下面這個例子。


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 強(qiáng)制轉(zhuǎn)換為 int 請用 int()署恍,轉(zhuǎn)為浮點數(shù)請用 float()。而在生產(chǎn)環(huán)境中使用強(qiáng)制轉(zhuǎn)換時蜻直,請記得加上 try except(即錯誤和異常處理盯质,專欄后面文章會講到)。

Python 對 int 類型沒有最大限制(相比之下概而, C++ 的 int 最大為 2147483647呼巷,超過這個數(shù)字會產(chǎn)生溢出),但是對 float 類型依然有精度限制赎瑰。這些特點王悍,除了在一些算法競賽中要注意,在生產(chǎn)環(huán)境中也要時刻提防餐曼,避免因為對邊界條件判斷不清而造成 bug 甚至 0day(危重安全漏洞)压储。

我們回望一下幣圈。2018 年 4 月 23 日中午 11 點 30 分左右源譬,BEC 代幣智能合約被黑客攻擊集惋。黑客利用數(shù)據(jù)溢出的漏洞,攻擊與美圖合作的公司美鏈 BEC 的智能合約瓶佳,成功地向兩個地址轉(zhuǎn)出了天量級別的 BEC 代幣芋膘,導(dǎo)致市場上的海量 BEC 被拋售鳞青,該數(shù)字貨幣的價值也幾近歸零霸饲,給 BEC 市場交易帶來了毀滅性的打擊。

由此可見臂拓,雖然輸入輸出和類型處理事情簡單厚脉,但我們一定要慎之又慎。畢竟相當(dāng)比例的安全漏洞胶惰,都來自隨意的 I/O 處理傻工。

文件輸入輸出

命令行的輸入輸出,只是 Python 交互的最基本方式孵滞,適用一些簡單小程序的交互中捆。而生產(chǎn)級別的 Python 代碼,大部分 I/O 則來自于文件坊饶、網(wǎng)絡(luò)泄伪、其他進(jìn)程的消息等等。

接下來匿级,我們來詳細(xì)分析一個文本文件讀寫蟋滴。假設(shè)我們有一個文本文件 in.txt染厅,內(nèi)容如下:


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(自然語言處理)任務(wù)津函。如果你對此不太了解也沒有影響肖粮,我會帶你一步步完成這個任務(wù)。

首先尔苦,我們要清楚 NLP 任務(wù)的基本步驟涩馆,也就是下面的四步:

  1. 讀取文件;
  2. 去除所有標(biāo)點符號和換行符蕉堰,并把所有大寫變成小寫凌净;
  3. 合并相同的詞,統(tǒng)計每個詞出現(xiàn)的頻率屋讶,并按照詞頻從大到小排序冰寻;
  4. 將結(jié)果按行輸出到文件 out.txt。

你可以自己先思考一下皿渗,用 Python 如何解決這個問題斩芭。這里,我也給出了我的代碼乐疆,并附有詳細(xì)的注釋划乖。我們一起來看下這段代碼。


import re

# 你不用太關(guān)心這個函數(shù)
def parse(text):
    # 使用正則表達(dá)式去除標(biāo)點符號和換行符
    text = re.sub(r'[^\w ]', ' ', text)

    # 轉(zhuǎn)為小寫
    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))

########## 輸出(省略較長的中間結(jié)果) ##########

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

你不用太關(guān)心 parse() 函數(shù)的具體實現(xiàn)挤土,你只需要知道琴庵,它做的事情是把輸入的 text 字符串,轉(zhuǎn)化為我們需要的排序后的詞頻統(tǒng)計仰美。而 sorted_word_cnt 則是一個二元組的列表(list of tuples)迷殿。

首先我們需要先了解一下,計算機(jī)中文件訪問的基礎(chǔ)知識咖杂。事實上庆寺,計算機(jī)內(nèi)核(kernel)對文件的處理相對比較復(fù)雜,涉及到內(nèi)核模式诉字、虛擬文件系統(tǒng)懦尝、鎖和指針等一系列概念,這些內(nèi)容我不會深入講解壤圃,我只說一些基礎(chǔ)但足夠使用的知識陵霉。

我們先要用 open() 函數(shù)拿到文件的指針。其中伍绳,第一個參數(shù)指定文件位置(相對位置或者絕對位置)踊挠;第二個參數(shù),如果是 'r'表示讀取墨叛,如果是'w' 則表示寫入,當(dāng)然也可以用 'rw' ,表示讀寫都要辱揭。a 則是一個不太常用(但也很有用)的參數(shù),表示追加(append)忍疾,這樣打開的文件,如果需要寫入谨朝,會從原始文件的最末尾開始寫入卤妒。

這里我插一句,在 Facebook 的工作中字币,代碼權(quán)限管理非常重要则披。如果你只需要讀取文件,就不要請求寫入權(quán)限洗出。這樣在某種程度上可以降低 bug 對整個系統(tǒng)帶來的風(fēng)險士复。

好,回到我們的話題翩活。在拿到指針后阱洪,我們可以通過 read() 函數(shù),來讀取文件的全部內(nèi)容菠镇。代碼 text = fin.read() 冗荸,即表示把文件所有內(nèi)容讀取到內(nèi)存中,并賦值給變量 text利耍。這么做自然也是有利有弊:

  • 優(yōu)點是方便蚌本,接下來我們可以很方便地調(diào)用 parse 函數(shù)進(jìn)行分析;
  • 缺點是如果文件過大隘梨,一次性讀取可能造成內(nèi)存崩潰程癌。

這時,我們可以給 read 指定參數(shù) size 出嘹,用來表示讀取的最大長度席楚。還可以通過 readline() 函數(shù)咬崔,每次讀取一行税稼,這種做法常用于數(shù)據(jù)挖掘(Data Mining)中的數(shù)據(jù)清洗,在寫一些小的程序時非常輕便垮斯。如果每行之間沒有關(guān)聯(lián)郎仆,這種做法也可以降低內(nèi)存的壓力。而 write() 函數(shù)兜蠕,可以把參數(shù)中的字符串輸出到文件中扰肌,也很容易理解。

這里我需要簡單提一下 with 語句(后文會詳細(xì)講到)熊杨。open() 函數(shù)對應(yīng)于 close() 函數(shù)曙旭,也就是說盗舰,如果你打開了文件,在完成讀取任務(wù)后桂躏,就應(yīng)該立刻關(guān)掉它钻趋。而如果你使用了 with 語句,就不需要顯式調(diào)用 close()剂习。在 with 的語境下任務(wù)執(zhí)行完畢后蛮位,close() 函數(shù)會被自動調(diào)用,代碼也簡潔很多鳞绕。

最后需要注意的是失仁,所有 I/O 都應(yīng)該進(jìn)行錯誤處理。因為 I/O 操作可能會有各種各樣的情況出現(xiàn)们何,而一個健壯(robust)的程序萄焦,需要能應(yīng)對各種情況的發(fā)生,而不應(yīng)該崩潰(故意設(shè)計的情況除外)冤竹。

JSON 序列化與實戰(zhàn)

最后楷扬,我來講一個和實際應(yīng)用很貼近的知識點。

JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式贴见,它的設(shè)計意圖是把所有事情都用設(shè)計的字符串來表示烘苹,這樣既方便在互聯(lián)網(wǎng)上傳遞信息,也方便人進(jìn)行閱讀(相比一些 binary 的協(xié)議)片部。JSON 在當(dāng)今互聯(lián)網(wǎng)中應(yīng)用非常廣泛镣衡,也是每一個用 Python 程序員應(yīng)當(dāng)熟練掌握的技能點。

設(shè)想一個情景档悠,你要向交易所購買一定數(shù)額的股票廊鸥。那么,你需要提交股票代碼辖所、方向(買入 / 賣出)惰说、訂單類型(市價 / 限價)、價格(如果是限價單)缘回、數(shù)量等一系列參數(shù)吆视,而這些數(shù)據(jù)里,有字符串酥宴,有整數(shù)啦吧,有浮點數(shù),甚至還有布爾型變量拙寡,全部混在一起并不方便交易所解包授滓。

那該怎么辦呢?

其實,我們要講的 JSON 般堆,正能解決這個場景在孝。你可以把它簡單地理解為兩種黑箱:

  • 第一種,輸入這些雜七雜八的信息淮摔,比如 Python 字典浑玛,輸出一個字符串;
  • 第二種噩咪,輸入這個字符串顾彰,可以輸出包含原始信息的 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))

########## 輸出 ##########

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() 這個函數(shù)涨享,接受 Python 的基本數(shù)據(jù)類型,然后將其序列化為 string仆百;
  • 而 json.loads() 這個函數(shù)厕隧,接受一個合法字符串,然后將其反序列化為 Python 的基本數(shù)據(jù)類型俄周。

是不是很簡單呢吁讨?

不過還是那句話,請記得加上錯誤處理峦朗。不然建丧,哪怕只是給 json.loads() 發(fā)送了一個非法字符串,而你沒有 catch 到波势,程序就會崩潰了翎朱。

到這一步,你可能會想尺铣,如果我要輸出字符串到文件拴曲,或者從文件中讀取 JSON 字符串,又該怎么辦呢凛忿?

是的澈灼,你仍然可以使用上面提到的 open() 和 read()/write() ,先將字符串讀取 / 輸出到內(nèi)存店溢,再進(jìn)行 JSON 編碼 / 解碼叁熔,當(dāng)然這有點麻煩。


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}

這樣逞怨,我們就簡單清晰地實現(xiàn)了讀寫 JSON 字符串的過程者疤。當(dāng)開發(fā)一個第三方應(yīng)用程序時福澡,你可以通過 JSON 將用戶的個人配置輸出到文件叠赦,方便下次程序啟動時自動讀取。這也是現(xiàn)在普遍運用的成熟做法。

那么 JSON 是唯一的選擇嗎除秀?顯然不是糯累,它只是輕量級應(yīng)用中最方便的選擇之一。據(jù)我所知册踩,在 Google泳姐,有類似的工具叫做 Protocol Buffer,當(dāng)然暂吉,Google 已經(jīng)完全開源了這個工具胖秒,你可以自己了解一下使用方法。

相比于 JSON慕的,它的優(yōu)點是生成優(yōu)化后的二進(jìn)制文件阎肝,因此性能更好。但與此同時肮街,生成的二進(jìn)制序列风题,是不能直接閱讀的。它在 TensorFlow 等很多對性能有要求的系統(tǒng)中都有廣泛的應(yīng)用嫉父。

總結(jié)

這節(jié)課沛硅,我們主要學(xué)習(xí)了 Python 的普通 I/O 和文件 I/O,同時了解了 JSON 序列化的基本知識绕辖,并通過具體的例子進(jìn)一步掌握摇肌。再次強(qiáng)調(diào)一下需要注意的幾點:

  • I/O 操作需謹(jǐn)慎,一定要進(jìn)行充分的錯誤處理仪际,并細(xì)心編碼朦蕴,防止出現(xiàn)編碼漏洞;
  • 編碼時弟头,對內(nèi)存占用和磁盤占用要有充分的估計吩抓,這樣在出錯時可以更容易找到原因;
  • JSON 序列化是很方便的工具赴恨,要結(jié)合實戰(zhàn)多多練習(xí)疹娶;
  • 代碼盡量簡潔、清晰伦连,哪怕是初學(xué)階段雨饺,也要有一顆當(dāng)元帥的心。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惑淳,一起剝皮案震驚了整個濱河市额港,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歧焦,老刑警劉巖移斩,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡向瓷,警方通過查閱死者的電腦和手機(jī)肠套,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猖任,“玉大人你稚,你說我怎么就攤上這事≈焯桑” “怎么了刁赖?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長长搀。 經(jīng)常有香客問我乾闰,道長,這世上最難降的妖魔是什么盈滴? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任涯肩,我火速辦了婚禮,結(jié)果婚禮上巢钓,老公的妹妹穿的比我還像新娘病苗。我一直安慰自己,他們只是感情好症汹,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布硫朦。 她就那樣靜靜地躺著,像睡著了一般背镇。 火紅的嫁衣襯著肌膚如雪咬展。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天瞒斩,我揣著相機(jī)與錄音破婆,去河邊找鬼。 笑死胸囱,一個胖子當(dāng)著我的面吹牛祷舀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烹笔,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼裳扯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谤职?” 一聲冷哼從身側(cè)響起饰豺,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎允蜈,沒想到半個月后冤吨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒿柳,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年锅很,在試婚紗的時候發(fā)現(xiàn)自己被綠了其馏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凤跑。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡爆安,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仔引,到底是詐尸還是另有隱情扔仓,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布咖耘,位于F島的核電站翘簇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儿倒。R本人自食惡果不足惜版保,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夫否。 院中可真熱鬧彻犁,春花似錦、人聲如沸凰慈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽微谓。三九已至森篷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豺型,已是汗流浹背仲智。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留姻氨,地道東北人坎藐。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像哼绑,于是被迫代替她去往敵國和親岩馍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351