python数据库操作nosql_用Python写一个NoSQL数据库
Set Up
下面是我們服務(wù)器所需的一些樣板代碼:
"""NoSQL database written in Python"""
# Standard library imports
import socket
HOST = 'localhost'
PORT = 50505
SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
STATS = {
'PUT': {'success': 0, 'error': 0},
'GET': {'success': 0, 'error': 0},
'GETLIST': {'success': 0, 'error': 0},
'PUTLIST': {'success': 0, 'error': 0},
'INCREMENT': {'success': 0, 'error': 0},
'APPEND': {'success': 0, 'error': 0},
'DELETE': {'success': 0, 'error': 0},
'STATS': {'success': 0, 'error': 0},
}
很容易看到, 上面的只是一個包的導(dǎo)入和一些數(shù)據(jù)的初始化。
Set up(Cont'd)
接下來我會跳過一些代碼, 以便能夠繼續(xù)展示上面準(zhǔn)備部分剩余的代碼。 注意它涉及到了一些尚不存在的一些函數(shù), 不過沒關(guān)系, 我們會在后面涉及。 在完整版(將會呈現(xiàn)在最后)中, 所有內(nèi)容都會被有序編排。 這里是剩余的安裝代碼:
COMMAND_HANDERS = {
'PUT': handle_put,
'GET': handle_get,
'GETLIST': handle_getlist,
'PUTLIST': handle_putlist,
'INCREMENT': handle_increment,
'APPEND': handle_append,
'DELETE': handle_delete,
'STATS': handle_stats,
}
DATA = {}
def main():
"""Main entry point for script"""
SOCKET.bind(HOST, PORT)
SOCKET.listen(1)
while 1:
connection, address = SOCKET.accept()
print('New connection from [{}]'.format(address))
data = connection.recv(4096).decode()
command, key, value = parse_message(data)
if command == 'STATS':
response = handle_stats()
elif command in ('GET', 'GETLIST', 'INCREMENT', 'DELETE'):
response = COMMAND_HANDERS[command](key)
elif command in (
'PUT',
'PUTLIST',
'APPEND', ):
response = COMMAND_HANDERS[command](key, value)
else:
response = (False, 'Unknown command type {}'.format(command))
update_stats(command, response[0])
connection.sandall('{};{}'.format(response[0], response[1]))
connection.close()
if __name__ == '__main__':
main()
我們創(chuàng)建了 COMMAND_HANDLERS, 它常被稱為是一個 查找表 (look-up table) . COMMAND_HANDLERS 的工作是將命令與用于處理該命令的函數(shù)進(jìn)行關(guān)聯(lián)起來。 比如說, 如果我們收到一個 GET 命令, COMMAND_HANDLERS[command](key) 就等同于說 handle_get(key) . 記住,在 Python 中, 函數(shù)可以被認(rèn)為是一個值,并且可以像其他任何值一樣被存儲在一個 dict 中。
在上面的代碼中, 雖然有些命令請求的參數(shù)相同,但是我仍決定分開處理每個命令。 盡管可以簡單粗暴地強制所有的 handle_ 函數(shù)接受一個 key 和一個 value , 但是我希望這些處理函數(shù)條理能夠更加有條理, 更加容易測試,同時減少出現(xiàn)錯誤的可能性。
注意 socket 相關(guān)的代碼已是十分極簡。 雖然整個服務(wù)器基于 TCP/IP 通信, 但是并沒有太多底層的網(wǎng)絡(luò)交互代碼。
最后還須需要注意的一小點: DATA 字典, 因為這個點并不十分重要, 因而你很可能會遺漏它。 DATA 就是實際用來存儲的 key-value pair, 正是它們實際構(gòu)成了我們的數(shù)據(jù)庫。
Command Parser
下面來看一些 命令解析器 (command parser) , 它負(fù)責(zé)解釋接收到的消息:
def parse_message(data):
"""Return a tuple containing the command, the key, and (optionally) the
value cast to the appropriate type."""
command, key, value, value_type = data.strip().split(';')
if value_type:
if value_type == 'LIST':
value = value.split(',')
elif value_type == 'INT':
value = int(value)
else:
value = str(value)
else:
value = None
return command, key, value
這里我們可以看到發(fā)生了類型轉(zhuǎn)換 (type conversion). 如果希望值是一個 list, 我們可以通過對 string 調(diào)用 str.split(',') 來得到我們想要的值。 對于 int, 我們可以簡單地使用參數(shù)為 string 的 int() 即可。 對于字符串與 str() 也是同樣的道理。
Command Handlers
下面是命令處理器 (command handler) 的代碼. 它們都十分直觀,易于理解。 注意到雖然有很多的錯誤檢查, 但是也并不是面面俱到, 十分龐雜。 在你閱讀的過程中,如果發(fā)現(xiàn)有任何錯誤請移步 這里 進(jìn)行討論.
def update_stats(command, success):
"""Update the STATS dict with info about if executing *command* was a
*success*"""
if success:
STATS[command]['success'] += 1
else:
STATS[command]['error'] += 1
def handle_put(key, value):
"""Return a tuple containing True and the message to send back to the
client."""
DATA[key] = value
return (True, 'key [{}] set to [{}]'.format(key, value))
def handle_get(key):
"""Return a tuple containing True if the key exists and the message to send
back to the client"""
if key not in DATA:
return (False, 'Error: Key [{}] not found'.format(key))
else:
return (True, DATA[key])
def handle_putlist(key, value):
"""Return a tuple containing True if the command succeeded and the message
to send back to the client."""
return handle_put(key, value)
def handle_putlist(key, value):
"""Return a tuple containing True if the command succeeded and the message
to send back to the client"""
return handle_put(key, value)
def handle_getlist(key):
"""Return a tuple containing True if the key contained a list and the
message to send back to the client."""
return_value = exists, value = handle_get(key)
if not exists:
return return_value
elif not isinstance(value, list):
return (False, 'ERROR: Key [{}] contains non-list value ([{}])'.format(
key, value))
else:
return return_value
def handle_increment(key):
"""Return a tuple containing True if the key's value could be incremented
and the message to send back to the client."""
return_value = exists, value = handle_get(key)
if not exists:
return return_value
elif not isinstance(list_value, list):
return (False, 'ERROR: Key [{}] contains non-list value ([{}])'.format(
key, value))
else:
DATA[key].append(value)
return (True, 'Key [{}] had value [{}] appended'.format(key, value))
def handle_delete(key):
"""Return a tuple containing True if the key could be deleted and the
message to send back to the client."""
if key not in DATA:
return (
False,
'ERROR: Key [{}] not found and could not be deleted.'.format(key))
else:
del DATA[key]
def handle_stats():
"""Return a tuple containing True and the contents of the STATS dict."""
return (True, str(STATS))
有兩點需要注意: 多重賦值 (multiple assignment) 和代碼重用. 有些函數(shù)僅僅是為了更加有邏輯性而對已有函數(shù)的簡單包裝而已, 比如 handle_get 和 handle_getlist . 由于我們有時僅僅是需要一個已有函數(shù)的返回值,而其他時候卻需要檢查該函數(shù)到底返回了什么內(nèi)容, 這時候就會使用 多重賦值 。
來看一下 handle_append . 如果我們嘗試調(diào)用 handle_get 但是 key 并不存在時, 那么我們簡單地返回 handle_get 所返回的內(nèi)容。 此外, 我們還希望能夠?qū)?handle_get 返回的 tuple 作為一個單獨的返回值進(jìn)行引用。 那么當(dāng) key 不存在的時候, 我們就可以簡單地使用 return return_value .
如果它 確實存在 , 那么我們需要檢查該返回值。并且, 我們也希望能夠?qū)?handle_get 的返回值作為單獨的變量進(jìn)行引用。 為了能夠處理上述兩種情況,同時考慮需要分開處理結(jié)果的情形,我們使用了多重賦值。 如此一來, 就不必書寫多行代碼, 同時能夠保持代碼清晰。 return_value = exists, list_value = handle_get(key) 能夠顯式地表明我們將要以至少兩種不同的方式引用 handle_get 的返回值。
How Is This a Database?
上面的程序顯然并非一個 RDBMS, 但卻絕對稱得上是一個 NoSQL 數(shù)據(jù)庫。它如此易于創(chuàng)建的原因是我們并沒有任何與 數(shù)據(jù) (data) 的實際交互。 我們只是做了極簡的類型檢查,存儲用戶所發(fā)送的任何內(nèi)容。 如果需要存儲更加結(jié)構(gòu)化的數(shù)據(jù), 我們可能需要針對數(shù)據(jù)庫創(chuàng)建一個 schema 用于存儲和檢索數(shù)據(jù)。
既然 NoSQL 數(shù)據(jù)庫更容易寫, 更容易維護(hù),更容易實現(xiàn), 那么我們?yōu)槭裁床皇侵皇褂?mongoDB 就好了? 當(dāng)然是有原因的, 還是那句話,有得必有失, 我們需要在 NoSQL 數(shù)據(jù)庫所提供的數(shù)據(jù)靈活性 (data flexibility) 基礎(chǔ)上權(quán)衡數(shù)據(jù)庫的可搜索性 (searchability).
Querying Data
假如我們上面的 NoSQL 數(shù)據(jù)庫來存儲早前的 Car 數(shù)據(jù)。 那么我們可能會使用 VIN 作為 key, 使用一個列表作為每列的值, 也就是說, 2134AFGER245267 = ['Lexus', 'RX350', 2013, Black] . 當(dāng)然了, 我們已經(jīng)丟掉了列表中每個索引的 涵義 (meaning) . 我們只需要知道在某個地方索引 1 存儲了汽車的 Model , 索引 2 存儲了 Year.
糟糕的事情來了, 當(dāng)我們想要執(zhí)行先前的查詢語句時會發(fā)生什么? 找到 1994 年所有車的顏色將會變得噩夢一般。 我們必須遍歷 DATA 中的 每一個值 來確認(rèn)這個值是否存儲了 car 數(shù)據(jù)亦或根本是其他不相關(guān)的數(shù)據(jù), 比如說檢查索引 2, 看索引 2 的值是否等于 1994,接著再繼續(xù)取索引 3 的值. 這比 table scan 還要糟糕,因為它不僅要掃描每一行數(shù)據(jù),還需要應(yīng)用一些復(fù)雜的規(guī)則來回答查詢。
NoSQL 數(shù)據(jù)庫的作者當(dāng)然也意識到了這些問題,(鑒于查詢是一個非常有用的 feature) 他們也想出了一些方法來使得查詢變得不那么 “遙不可及”。一個方法是結(jié)構(gòu)化所使用的數(shù)據(jù),比如 JSON, 允許引用其他行來表示關(guān)系。 同時, 大部分 NoSQL 數(shù)據(jù)庫都有名字空間 (namespace) 的概念, 單一類型的數(shù)據(jù)可以被存儲在數(shù)據(jù)庫中該類型所獨有的 "section" 中,這使得查詢引擎能夠利用所要查詢數(shù)據(jù)的 "shape" 信息。
當(dāng)然了,盡管為了增強可查詢性已經(jīng)存在 (并且實現(xiàn)了)了一些更加復(fù)雜的方法, 但是在存儲更少量的 schema 與增強可查詢性之間做出妥協(xié)始終是一個不可逃避的問題。 本例中我們的數(shù)據(jù)庫僅支持通過 key 進(jìn)行查詢。 如果我們需要支持更加豐富的查詢, 那么事情就會變得復(fù)雜的多了。
Summary
至此, 希望 "NoSQL" 這個概念已然十分清晰。 我們學(xué)習(xí)了一點 SQL, 并且了解了 RDBMS 是如何工作的。 我們看到了如何從一個 RDBMS 中檢索數(shù)據(jù) (使用 SQL 查詢 (query)). 通過搭建了一個玩具級別的 NoSQL 數(shù)據(jù)庫, 了解了在可查詢性與簡潔性之間面臨的一些問題, 還討論了一些數(shù)據(jù)庫作者應(yīng)對這些問題時所采用的一些方法。
即便是簡單的 key-value 存儲, 關(guān)于數(shù)據(jù)庫的知識也是浩瀚無窮。雖然我們僅僅是探討了其中的星星點點, 但是仍然希望你已經(jīng)了解了 NoSQL 到底指的是什么, 它是如何工作的, 什么時候用比較好。
22/2<12
總結(jié)
以上是生活随笔為你收集整理的python数据库操作nosql_用Python写一个NoSQL数据库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 惠普台式机 bios怎么进 惠普台式电脑
- 下一篇: 怎么提升u盘的读写速度 U盘读写速度如何