Python 黑箱 :輸入與輸出-day4

Python “黑箱” :輸入與輸出


世紀之交的論壇上曾有一句流行語:在互聯網上,沒人知道你是一條狗。互聯網剛剛興起時,一根網線鏈接到你家,信息通過這條高速線纜直達你的屏幕,你通過鍵盤飛速回應朋友的消息,信息再次通過網線飛入錯綜複雜的虛擬世界,再進入朋友家。抽象來看,一臺臺的電腦就是一個個黑箱,黑箱有了輸入和輸出,就擁有了圖靈機運作的必要條件。

Python 程序也是一個黑箱:通過輸入流將數據送達,通過輸出流將處理後的數據送出,可能 Python 解釋器後面藏了一個人,還是一個史萊哲林?No one cares。

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

輸入輸出基礎

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


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

###### 輸入 ######
your name:Jack
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() 函數暫停程序運行,同時等待鍵盤輸入;直到回車被按下,函數的參數即爲提示語,輸入的類型永遠是字符串型(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(危重安全漏洞)。

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

由此可見,雖然輸入輸出和類型處理事情簡單,但我們一定要慎之又慎。畢竟相當比例的安全漏洞,都來自隨意的 I/O 處理。

文件輸入與輸出

命令行的輸入輸出,只是 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(自然語言處理)任務。如果你對此不太瞭解也沒有影響,我會帶你一步步完成這個任務。

首先,我們要清楚 NLP 任務的基本步驟,也就是下面的四步:

  1. 讀取文件;
  2. 去除所有標點符號和換行符,並把所有大寫變成小寫;
  3. 合併相同的詞,統計每個詞出現的頻率,並按照詞頻從大到小排序;
  4. 將結果按行輸出到文件 out.txt。

以自己先思考一下,用 Python 如何解決這個問題。這裏,我也給出了我的代碼,並附有詳細的註釋。我們一起來看下這段代碼。


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

你不用太關心 parse() 函數的具體實現,你只需要知道,它做的事情是把輸入的 text 字符串,轉化爲我們需要的排序後的詞頻統計。而 sorted_word_cnt 則是一個二元組的列表(list of tuples)。

首先我們需要先了解一下,計算機中文件訪問的基礎知識。事實上,計算機內核(kernel)對文件的處理相對比較複雜,涉及到內核模式、虛擬文件系統、鎖和指針等一系列概念,這些內容我不會深入講解,我只說一些基礎但足夠使用的知識。

我們先要用 open() 函數拿到文件的指針。其中,第一個參數指定文件位置(相對位置或者絕對位置);第二個參數,如果是 ‘r’表示讀取,如果是’w’ 則表示寫入,當然也可以用 ‘rw’ ,表示讀寫都要。a 則是一個不太常用(但也很有用)的參數,表示追加(append),這樣打開的文件,如果需要寫入,會從原始文件的最末尾開始寫入。

這裏我插一句,,代碼權限管理非常重要。如果你只需要讀取文件,就不要請求寫入權限。這樣在某種程度上可以降低 bug 對整個系統帶來的風險。

好,回到我們的話題。在拿到指針後,我們可以通過 read() 函數,來讀取文件的全部內容。代碼 text = fin.read() ,即表示把文件所有內容讀取到內存中,並賦值給變量 text。這麼做自然也是有利有弊:

  • 優點是方便,接下來我們可以很方便地調用 parse 函數進行分析;
  • 缺點是如果文件過大,一次性讀取可能造成內存崩潰。這時,我們可以給 read 指定參數 size ,用來表示讀取的

這時,我們可以給 read 指定參數 size ,用來表示讀取的最大長度。還可以通過 readline() 函數,每次讀取一行,這種做法常用於數據挖掘(Data Mining)中的數據清洗,在寫一些小的程序時非常輕便。如果每行之間沒有關聯,這種做法也可以降低內存的壓力。而 write() 函數,可以把參數中的字符串輸出到文件中,也很容易理解。

這裏我需要簡單提一下 with 語句(後文會詳細講到)。open() 函數對應於 close() 函數,也就是說,如果你打開了文件,在完成讀取任務後,就應該立刻關掉它。而如果你使用了 with 語句,就不需要顯式調用 close()。在 with 的語境下任務執行完畢後,close() 函數會被自動調用,代碼也簡潔很多。

最後需要注意的是,所有 I/O 都應該進行錯誤處理。因爲 I/O 操作可能會有各種各樣的情況出現,而一個健壯(robust)的程序,需要能應對各種情況的發生,而不應該崩潰(故意設計的情況除外)。JSON 序列化與實戰

JSON序列化與實戰

最後,我來講一個和實際應用很貼近的知識點。

JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,它的設計意圖是把所有事情都用設計的字符串來表示,這樣既方便在互聯網上傳遞信息,也方便人進行閱讀(相比一些 binary 的協議)。JSON 在當今互聯網中應用非常廣泛,也是每一個用 Python 程序員應當熟練掌握的技能點。

設想一個情景,你要向交易所購買一定數額的股票。那麼,你需要提交股票代碼、方向(買入 / 賣出)、訂單類型(市價 / 限價)、價格(如果是限價單)、數量等一系列參數,而這些數據裏,有字符串,有整數,有浮點數,甚至還有布爾型變量,全部混在一起並不方便交易所解包。

那該怎麼辦呢?

其實,我們要講的 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() 這個函數,接受 Python 的基本數據類型,然後將其序列化爲 string;
  • 而 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 將用戶的個人配置輸出到文件,方便下次程序啓動時自動讀取。這也是現在普遍運用的成熟做法。

那麼 JSON 是唯一的選擇嗎?顯然不是,它只是輕量級應用中最方便的選擇之一。據我所知,在 Google,有類似的工具叫做 Protocol Buffer,當然,Google 已經完全開源了這個工具,你可以自己瞭解一下使用方法。

相比於 JSON,它的優點是生成優化後的二進制文件,因此性能更好。但與此同時,生成的二進制序列,是不能直接閱讀的。它在 TensorFlow 等很多對性能有要求的系統中都有廣泛的應用。

寫在後面

又堅持了一天 加油!

也希望大家多多鞭策,我怕我放棄…

知乎 :禪墨雲

CSDN : 禪墨雲

微信公衆號:興趣路人甲

在這裏插入圖片描述

個人網站: 禪墨雲

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章