本篇文章是《【转】用不到 200 行的 Python 代码构建一个最小的 NoSQL 数据库》该篇文章的扩展,同时本篇文章是由 Ricky 写的,不是转载。 |
首先先来回顾一下上篇文章《【转】用不到 200 行的 Python 代码构建一个最小的 NoSQL 数据库》。上篇文章先介绍了 SQL 和 NoSQL 的一些简单概念和区别,紧接着通过使用 Python 自带的数据结构 —— dict(字典)来简单实现了一个玩具级的 NoSQL 数据库。
这篇文章主要是在上篇文章的基础上,增强了这个玩具级 NoSQL 数据库在 Telnet 时的交互能力。接下来大致说说一些改动的部分,具体细节请下载新版(2.0)和旧版(1.0)的代码来比较。
def parse_message(data):
以下是 def parse_message(data) 函数的旧版(1.0)代码:
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
以下是 def parse_message(data) 函数的新版(2.0)代码:
def parse_message(data): """Return a tuple containing the command, the key, and (optionally) the value cast to the appropriate type.""" try: message_list = map(str.strip, data.split(';')) except: return 'Invalid input! Expected: COMMAND; [KEY]; [VALUE]; [VALUE TYPE].' command = key = value = None message_len = len(message_list) if message_len == 1: command = message_list[0] elif message_len == 2: command, key = message_list elif message_len == 3: command, key, value = message_list value = format_value_type('STR', value) elif message_len == 4: command, key, value, value_type = message_list value = format_value_type(value_type, value) return command.upper(), key, value def format_value_type(value_type, value): value_type = value_type.upper() if value_type == 'LIST': return value.split(',') elif value_type == 'INT': return int(value) else: return str(value)
现在从上至下简要地来说说改动的部分。
command, key, value, value_type = data.strip().split(';')
上例是旧版(1.0)的代码,代码只是去除 data(即输入的命令)首尾多余的空格,再根据 ” ; “ 切割字符串(data)。
message_list = map(str.strip, data.split(';'))
上例是新版(2.0)的代码,代码先根据 ” ; “ 切割字符串(data),再对切割好的若干个子字符串分别执行去除首尾多余空格的操作。所以在输入命令的时候,旧版(1.0)代码是这么输入的:
- PUT;foo;1;INT
- GET;foo;;
- PUTLIST;bar;a,b,c;LIST
- APPEND;bar;d;STRING
- GETLIST;bar;;
- STATS;;;
- INCREMENT;foo;;
- DELETE;foo;;
而新版(2.0)的代码你还可以这么输入,这使得代码的容错性大大提高:
- PUT ; foo ; 1 ; INT
- GET ; foo ; ;
- PUTLIST ; bar ; a,b,c ; LIST
- APPEND ; bar ; d;STRING
- GETLIST ; bar ; ;
- STATS ; ; ;
- INCREMENT ; foo ; ;
- DELETE ; foo ; ;
message_len = len(message_list)
if ...
...
elif ...
...
上例是新版(2.0)的代码,使用 message_len 的方式来格式化各个字段(command、key 和 value),这么做最大的好处是在输入命令时不需要输入多余的 ” ; “ 分隔符咯。所以新版(2.0)的代码还可以这么输入命令:
- PUT ; foo ; 1 ; INT
- GET ; foo
- PUTLIST ; bar ; a,b,c ; LIST
- APPEND ; bar ; d;STRING
- GETLIST ; bar
- STATS
- INCREMENT ; foo
- DELETE ; foo
command.upper()
value_type = value_type.upper()
上例是新版(2.0)的代码,代码还对 command 和 value_type 两个字符串执行了一个强制转换为大写的操作,即 command 和 value_type 两个字符串在用户输入时是不区分大小写的。所以新版(2.0)的代码还可以这么输入命令:
- put ; foo ; 1 ; int
- Get ; foo
- PutlisT ; bar ; a,b,c ; lisT
- apPENd ; bar ; d;String
- getLIST ; bar
- StatS
- IncreMENt ; foo
- delete ; foo
def main():
以下是 def main() 函数的旧版(1.0)代码:
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_HANDLERS[command](key) elif command in ( 'PUT', 'PUTLIST', 'APPEND', ): response = COMMAND_HANDLERS[command](key, value) else: response = (False, 'Unknown command type [{}]'.format(command)) update_stats(command, response[0]) connection.sendall('{};{}'.format(response[0], response[1])) connection.close()
以下是 def main() 函数的新版(2.0)代码:
def main(): """Main entry point for script.""" SOCKET.bind((HOST, PORT)) SOCKET.listen(1) print('Listening on {}'.format((HOST, PORT))) connection, address = SOCKET.accept() print('{} New connection from {}'.format(time.strftime(("%Y/%m/%d %H:%M:%S INFO"), time.localtime()), address)) while 1: connection.sendall(bytearray('REQ >> ', 'utf-8')) data = str(connection.recv(4096).decode().strip().replace("\n", "").replace("\r\n", "")) #print data command, key, value = parse_message(data) if command == 'STATS': response = handle_stats() elif command in ('QUIT', 'EXIT'): connection.sendall(bytearray('BYE >> SEE YOU NEXT TIME ...', 'utf-8')) connection.close() break elif command == '': # 匹配回车换行 continue elif command in ('GET', 'GETLIST', 'INCREMENT', 'DELETE'): response = COMMAND_HANDLERS[command](key) elif command in ('PUT', 'PUTLIST', 'APPEND'): response = COMMAND_HANDLERS[command](key, value) else: response = (False, 'Unknown command type [{}].'.format(command)) update_stats(command, response[0]) data = 'RSP >> {};{}\r\n\r\n'.format(response[0], response[1]) connection.sendall(bytearray(data, 'utf-8'))
现在从上至下简要地来说说改动的部分。
print('{} New connection from {}'.format(time.strftime(("%Y/%m/%d %H:%M:%S INFO"), time.localtime()), address))
上例是新版(2.0)的代码,首先增加了在新连接接入时的时间提示。
REQ >> RSP >> BYE >>
然后增加了三个提示符:“ REQ ” 顾名思义就是请求(request),“ RSP ” 就是响应(response),“ BYE ” 就是再见(good bye)。具体效果如 example 1 所示。
elif command in ('QUIT', 'EXIT'): connection.sendall(bytearray('BYE >> SEE YOU NEXT TIME ...', 'utf-8')) connection.close() break elif command == '': # 匹配回车换行 continue
同时还加入了 ” QUIT ” 和 ” EXIT ” 两条命令,以及支持用户在不输入任何字符时直接换行的操作。具体效果如 example 1 所示。
除此之外还修改了:在使用 Telnet 交互时采用 utf-8 的字符格式;while 循环也不再是摆设(你运行一下旧版的代码就知道咯 …);等等。
example 1

先使用 SecureCRT 等软件通过 Telnet 的方式连接该玩具级 NoSQL 数据库。

以上是操作该玩具级 NoSQL 数据库一个非常简单的例子,我们存入了一个键名为 foo 值为 1 的键值对(put;foo;1;int),数据库提示保存成功(True;key [foo] set to [1].),然后退出数据库(quit),数据库提示退出成功(SEE YOU NEXT TIME …)。
example 2
在本篇文章前半部分,有提及过下面这个例子:
- put ; foo ; 1 ; int
- Get ; foo
- PutlisT ; bar ; a,b,c ; lisT
- apPENd ; bar ; d;String
- getLIST ; bar
- StatS
- IncreMENt ; foo
- delete ; foo
现在我们来演示一下,如下图所示:

到这里本篇文章就结束了,enjoy your time ~
源代码下载
请参见文章下方的 Article Attachments 部分,或者点击右侧链接:NoSQL-2.0.py(Python 2.7.14 ,亲测可执行)
发表评论?