Python中的数据读入、写出以及交换

本文详细介绍了Python中不同类型的文件操作,包括普通文本文件的读写,以及结构化的CSV、XML、JSON、YAML等格式的文件处理。此外,还探讨了关系型数据库(如SQLite、MySQL、PostgreSQL)的使用,以及NoSQL存储如memcached和Redis。文章强调了数据安全和不同数据格式间的转换,并提供了相关模块和库的用法示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章会涉及不同类型的数据存储,它们基于不同的目的进行优化:普通文件结构化文件数据库

普通文件输入/输出

数据持久化最简单的类型是普通文件,有时也叫平面文件(flat file)。它仅仅是在一个文件名下的字节流,把数据从一个文件读入内存,然后从内存写入文件。

打开文件

# fileobj是open()返回的文件对象
# filename是该文件的字符串名
# mode是指明文件类型和操作的字符串
fileobj = open(filename, mode)

mode的第一个字母表明对其的操作

  • r表示读模式。
  • w表示写模式。如果文件不存在则新创建,如果存在则重写新内容。
  • x表示在文件不存在的情况下新创建并写文件。
  • a表示如果文件存在,在文件末尾追加写内容。

mode的第二个字母是文本类型

  • t(或者省略)代表文本类型。
  • b代表二进制文件。

写文本文件

使用write()

sa = 'Never\nGive\nUp'
sb = 'Try\nMy\nBest'

fout = open('record', 'wt')
fout.write(sa)
fout.write(sb)
fout.close()

结果

# record1
# 注意Up与Try首尾相接
Never
Give
UpTry
My
Best

使用print()

sa = 'Never\nGive\nUp'
sb = 'Try\nMy\nBest'

fout = open('record', 'wt')
print(sa, file = fout)
print(sb, file = fout)
fout.close()

结果

# record2
# 注意Up与Try首尾有换行
Never
Give
Up
Try
My
Best

print()默认会在每个参数后添加空格,在每行结束处添加换行。这两个特性可以由sepend两个参数设置。

  • sep分隔符:默认是一个空格’ ‘
  • end结束字符:默认是一个换行符’\n’

因此,下面的print等价于write

print(sa, file = fout, sep='', end='')
print(sb, file = fout, sep='', end='')

注意

  1. write函数的参数一定要是字符串,writelines函数的参数一定要是字符串或字符串列表,print函数的参数是任意可以被str函数转化的类型。
  2. writelines函数写出列表时,不会给列表中每个字符串元素自动添加换行符。如[‘ab’, ‘cd’],则只会输出abcd,不会输出
    ab
    cd

写出列表[1, 2, ‘ab’, ‘efg’]

# 需要结果
1
2
ab
efg

显然,直接用write、writelines以及print都是不可行的。

方法1:write
lines = [1, 2, 'ab', 'efg']
with open('filename.txt', 'w') as f:
    # [str(line) for line in lines]生成字符串列表
    # '\n'.join生成一个中间由'\n'连接的字符串
    f.write('\n'.join([str(line) for line in lines]))
方法2:writelines
lines = [1, 2, 'ab', 'efg']
with open('filename.txt', 'w') as f:
    # [str(line)+'\n' for line in lines]生成字符串列表,每个字符串后面有换行符'\n'
    f.writelines([str(line)+'\n' for line in lines])

将数据分块写入

fout = open('record2', 'wt')
sentence = sa + sb
size = len(sentence)
offset = 0
chunk = 100
while True:
    if offset > size:
        break
    fout.write(sentence[offset:offset+chunk])
    offset += chunk

使用模式x避免重写文件

try:
    fout = open('record2', 'xt')
    fout.writ(sentence)
except FileExistsError:
    print('record already exists!')

使用read()、readline()或者readlines()读文本文件

使用read()

利用read()读入文件时,1GB的文件会用到同样大小的内存

# record2
Never
Give
Up
Try
My
Best
# read()会把整个文件以字符串的形式读入
# '\n'也算一个字符
fin = open('record2', 'rt')
sentence = fin.read()
fin.close()
print(type(sentence))

结果:

<class 'str'>

同样也可以设置最大的读入字符数限制read()函数一次返回的大小。下面一次读入100个字符,然后把每一块拼接成原来的字符串sentence:

sentence = ''
fin = open('record2', 'rt')
chunk = 100
while True:
    fragment = fin.read(chunk)
    if not fragment:
        break
    sentence += fragment
fin.close()

使用readline()

readline()每次读入文件的一行。对于一个文本文件,即使空行也有1字符长度(换行字符’\n’),自然就会返回True。当文件读取结束后,readline()(类似read())同样会返回空字符串。

# record1
Never
Give
UpTry
My
Best
fin = open('record1', 'rt')
sentence = ''
while True:
    line = fin.readline()
    print(len(line))
    if not line :
        break
    sentence += line
fin.close()
print(sentence)

结果:

# 记住换行符也是一个普通的字符
6   # '\n'也在里面
5
6
3
4   # 没有'\n'
0
Never
Give
UpTry
My
Best
更好的用法
sentence = ''
fin = open('record', 'rt')
for line in fin:
    sentence += line
fin.close()

使用readlines()

函数readlines()调用时读入所有行,并返回单行字符串的列表

fin = open('record1', 'rt')
lines = fin.readlines()
fin.close()
print lines

结果:

['Never\n', 'Give\n', 'UpTry\n', 'My\n', 'Best']

使用write()写二进制文件

bdata = bytes(range(0, 256))
print(len(bdata))
fout = open('bfile', 'wb')
blen = fout.write(bdata)
# blen为write()写入的字节数
print(blen)
fout.close()

分块写入

fout = open('bfile', 'wb')
size = len(bdata)
offset = 0
chunk = 100
while True:
    if offset > size:
        break
    fout.write(bdata[offset:offset+chunk])
    offset += chunk
fout.close()

使用read()读二进制文件

fin = open('bfile', 'rb')
bdata = find.read()
print(len(bdata))
fin.close()

使用with自动关闭文件

如果你忘记关闭已经打开的一个文件,在该文件对象不再被引用之后Python会关掉此文件

这也就意味着在一个函数中打开文件,没有及时关闭它,但是在函数结束时会被关掉。

Python的上下文管理器(context manage)(什么鬼?)会清理一些资料,例如打开的文件。它的形式为with expression as variable:

# fout = open('record', 'wt')
with open('record', 'wt') as fout:
    fout.write(poem)

完成上下文管理器的代码后,文件会被自动关闭。

使用seek()改变位置(基于字节)

无论是读或者写文件,Python都会跟踪文件中的文件。

函数tell()返回距离文件开始处的字节偏移量。

函数seek()允许跳转到文件其他字节偏移量的位置。这意味着可以不用从头读取文件的每一个字节,直接跳到最后位置并只读一个字节也是可行的。

这些函数对于二进制文件都是极其重要的。当文件是ASCII编码(么个字符一个字节)时,也可以使用它们,但是计算偏移量回事一件麻烦事。其实,这都取决于文本的编码格式,UTF-8每个字符的字节数都不尽相同。

>>> b'abc'[0]
97
>>> fin = open('bfile', 'rb')
>>> fin.tell()
0
>>> # 初始字节偏移量为0,最后字节偏移量为255
>>> # 使用seek()跳转到文件结束前最后一个字节
>>> fin.seek(255)
255 #255是指当前的偏移量
>>> # 读取最后一个字节
>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

seek(offset, origin)

  • 如果origin等于0(默认为0),从开头偏移offset个字节;
  • 如果origin等于1,从当前位置处偏移offset个字节;
  • 如果origin等于2,距离最后结尾处(指的是所有数据后的一个字节的位置)偏移offset个字节;

上述值也在标准os模块中被定义:

>>> import os
>>> os.SEEK_SET
0
>>> os.SEEK_CUR
1
>>> os.SEEK_END
2
读取bfile最后一个字节的方法

方法一:

fin.seek(-1, 2)
bdata = fin.read()

方法二:

fin.seek(255, 0)
bdata = fin.read()

方法三:

fin.seek(254, 0)
fin.seek(1, 1)
bdata = fin.read()

结构化的文本文件输入/输出

结构化的文本文件

对于简单的文本文件,唯一的结构层次是间隔的行

结构化的文本有很多格式,区别它们的方法如下所示:

  • 分隔符,比如tab(‘\t’)、逗号(‘,’)或者竖线(‘|’)。逗号分隔值(CSV)就是这样的例子。
  • ‘<’和’>’标签,例如XML和HTML
  • 标点符号,例如JavaScript Object Notation ( JSON2 J S O N 2 )
  • 缩进,例如YAML
  • 混合的,例如各种配置文件

CSV

带分隔符的文件一般用作数据交换格式或者数据库

你可以人工读入CSV文件,每一次读取一行,在逗号分隔符处将每行分开,并添加结果到某些数据结构中。

但是,最好使用标准的csv模块,因为这样切分会得到更加复杂的信息。

  • 除了逗号,还有其他可代替的分隔符: ‘|’和’\t’很常见。
  • 有些数据会有转义字符序列,如果分隔符出现在一块区域内,则整块都要加上引号或者在它之前加上转义字符。
  • 文件可能有不同的换行符,Unix系统的文件使用’\n’,Microsoft使用’\r\n’,Apple之前使用’\r’而现在使用’\n’
  • 在第一行可以加上列名

写出CSV

import csv
data = [\
['Doctor', 1, 1.01, True, 'a,a,,', [1], [1,2], [1, 'a'], ['a']],\
['Teacher', 'a', 2.02, False, 'b,b,,']]
with open('data', 'wt') as fout: # 一个上下文管理器
     csvout = csv.writer(fout)
     csvout.writerows(data)
# data
Doctor,1,1.01,True,"a,a,,",[1],"[1, 2]","[1, 'a']",['a']

Teacher,a,2.02,False,"b,b,,"
分析
  1. 首先每行数据输出到文件时,会默认自动换行。若不希望有换行则open(‘data’, ‘wt’, newline = ”)
  2. 每行的属性数量可以不同
  3. 数据全转化为字符串数据,然后若字符串数据’xxxx’若不包含’,’,则输出xxxx;否则,则输出”xxxx”

读入CSV

读入得到的是字符串列表,不管写入的时候,列表的元素是什么类型

import csv
with open('data', 'rt') as fin:
     cin = csv.reader(fin)
     data = [row for row in cin]

print(data)
print(data[0])
print(data[1])
print(data[0][0])
print(data[1][0])
print(data[0][6][0])

结果:

# 得到的是字符串列表
[['Doctor', '1', '1.01', 'True', 'a,a,,', '[1]', '[1, 2]', "[1, 'a']", "['a']"], ['Teacher', 'a', '2.02', 'False', 'b,b,,']]
['Doctor', '1', '1.01', 'True', 'a,a,,', '[1]', '[1, 2]', "[1, 'a']", "['a']"]
['Teacher', 'a', '2.02', 'False', 'b,b,,']
Doctor
Teacher
[

具有标题行的CSV文件的生成与读入

生成

方法1
import csv
villains0 = [['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big']]
# 生成字典列表
villains = [{'first': item[0], 'last': item[1]} for item in villains0]
print(villains)

with open('villains', 'wt', newline='') as fout:
     cout = csv.DictWriter(fout, ['first', 'last'])
     # 写标题行
     cout.writeheader()
     cout.writerows(villains)

结果:

first,last
Doctor,No
Rosa,Klebb
Mister,Big
方法2
import csv
villains = [['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big']]

with open('villains', 'wt', newline='') as fout:
     cout = csv.writer(fout)
     cout.writerow(['first', 'last'])
     cout.writerows(villains)

结果同上

读入

import csv
with open('villains', 'rt') as fin:
     # 默认读入第一行作为标题行
     cin = csv.DictReader(fin)
     villains = [row for row in cin]

print(villains)

结果:

[OrderedDict([('first', 'Doctor'), ('last', 'No')]), OrderedDict([('first', 'Rosa'), ('last', 'Klebb')]), OrderedDict([('first', 'Mister'), ('last', 'Big')])]

无标题行的CSV文件的字典读入

Doctor,No
Rosa,Klebb
Mister,Big
import csv
with open('villains', 'rt') as fin:
     # 不读入第一行作为标题行,并自定义fieldnames
     # fieldnames也可以定义为['first', 2]
     cin = csv.DictReader(fin, fieldnames=['first', 'last'])
     villains = [row for row in cin]

print(villains)

结果:

[OrderedDict([('first', 'Doctor'), ('last', 'No')]), OrderedDict([('first', 'Rosa'), ('last', 'Klebb')]), OrderedDict([('first', 'Mister'), ('last', 'Big')])]

XML

带分隔符的文件(如CSV)仅有二维的数据:行和列(思考一下是否如此?)。如果你想在程序之间交换数据结构,需要一种方法把层次结构、序列、集合和其他的结构编码成文本。

XML是最突出的处理这种转换的标记(markup)格式,它使用标签(tag)分隔数据,如下面的示例文件menu.xml所示:

<?xml version="1.0"?>
<menu>
    <breakfast hours="7-11">
        <item price="$6.00">breakfast burritos</item>
        <item price="$4.00">pancakes</item>
    </breakfast>
    <lunch hours="11-3">
        <item price="$5.00">hamburger</item>
    </lunch>
    <dinner hours="3-10">
        <item price="8.00">spaghetti</item>
    </dinner>
</menu>

XML通常用于数据传送和消息,它存在一些子格式,如RSS和Atom(常用于内容订阅,两者的区别。工业界有许多定制化的XML格式,例如金融领域

解析XML

XML的灵活性导致出现了很多方法和性能各异的Python库。

  • ElementTree(直接处理XML文件)
  • xml.dom
    JavaScript开发者比较熟悉的文档对象模型(DOM)将Web文档表示成层次结构,它会把整个XML文件载入内存中,同样允许你获取所有的内容
  • xml.sax
    简单的XML API或者SAX都是通过在线解析XML不需要一次载入所有内容到内存中,因此对于处理巨大的XML文件流失一个很好的选择

以ElementTree为例,解析menu.xml

import xml.etree.ElementTree as et
tree = et.ElementTree(file = 'menu.xml')
root = tree.getroot()
print(root.tag)
for child in root:
     print('tag:', child.tag, 'attributes:', child.attrib)
     for grandchild in child:
          print('\ttag:', grandchild.tag, 'attributes:', grandchild.attrib)

# 菜单选择的数目
print(len(root))

# 早餐项的数目
print(len(root[0]))

结果:

menu
tag: breakfast attributes: {'hours': '7-11'}
    tag: item attributes: {'price': '$6.00'}
    tag: item attributes: {'price': '$4.00'}
tag: lunch attributes: {'hours': '11-3'}
    tag: item attributes: {'price': '$5.00'}
tag: dinner attributes: {'hours': '3-10'}
    tag: item attributes: {'price': '8.00'}
3
2
关于xml.dom与xml.sax

这里写图片描述

HTML

在Web网络中,海量的数据以超文本标记语言(HTML)这一基本的文档格式存储。然而许多文档不遵循HTML的规则,导致很难进行解析更多的HTML是用来格式化输出显示结果而不是用于交换数据

JSON

JavaScript Object Notation (JSON)是源于JavaScript的当今很流行的数据交换格式,它是JavaScript语言的一个子集,也是Python合法可支持的语法。对于Python的兼容性使得它成为程序间数据交换的较好选择

不同于众多的XML模块,Python只有一个主要的JSON模块json

这里写图片描述

编码/解析JSON对象时得到异常

你可能会在编码或者解析JSON对象时得到异常,包括集合、对象的时间date等等:
这里写图片描述

这里写图片描述

处理方法一

上述错误发生是因为标准JSON没有定义日期或者时间类型,需要自定义处理方式。你可以把datetime转换成JSON能够理解的类型,比如字符串或者epoch值
这里写图片描述

处理方法二

可以通过继承修改JSON的编码方式。
下面为datetime修改编码方式:
这里写图片描述

YAML

YAML主要用来处理日期和时间这样的数据类型。标准的Python库没有处理YAML的模块,因此需要安装第三方库yaml。load()将YAML字符串转换成Python数据结构,而dump()正好相反。

安装第三方库yaml

You could try the search feature in pip,

$ pip search yaml

which looks for packages in PyPI with yaml in the short description. That reveals various packages, including PyYaml, yamltools, and PySyck, among others (Note that PySyck docs recommend using PyYaml, since syck is out of date). Now you know a specific package name, you can install it:

$ pip install pyyaml

If you want to install python yaml system-wide in linux, you can also use a package manager, like aptitude or yum:

$ sudo apt-get install python-yaml
$ sudo yum install python-yaml

YAML的语法

例子

# mcintyre.yaml
name:
  first: James
  last: McIntyre
dates:
  birth: 1828-05-25
  death: 1906-03-31
details:
  beared: true
  themes: [cheese, Canada]
books:
  url: http://www.gutenberg.org
poems:
  - title: 'Motto'
    text : |
      Politeness,
      To their
  - title: 'Canadian Charms'
    text : |
      Here
      For
      And
import yaml
with open('mcintyre.yaml', 'rt') as fin:
    text = fin.read()

data = yaml.load(text)
print(data)

data_yaml = yaml.dump(data)
print(data_yaml)

结果:

{'name': {'first': 'James', 'last': 'McIntyre'}, 'dates': {'birth': datetime.date(1828, 5, 25), 'death': datetime.date(1906, 3, 31)}, 'details': {'beared': True, 'themes': ['cheese', 'Canada']}, 'books': {'url': 'http://www.gutenberg.org'}, 'poems': [{'title': 'Motto', 'text': 'Politeness,\nTo their\n'}, {'title': 'Canadian Charms', 'text': 'Here\nFor\nAnd\n'}]}

books: {url: 'http://www.gutenberg.org'}
dates: {birth: 1828-05-25, death: 1906-03-31}
details:
  beared: true
  themes: [cheese, Canada]
name: {first: James, last: McIntyre}
poems:
- {text: 'Politeness,

    To their

    ', title: Motto}
- {text: 'Here

    For

    And

    ', title: Canadian Charms}

注意

PyYAML可以从字符串中载入Python对象,但这样做是不安全的。如果导入你不信任的YAML,使用safe_load()代替load(),通过阅读war is peace进一步了解载入YAML在Ruby on Rails平台上如何折中。

安全提示

你可以使用本章中介绍的所有格式保存数据对象到文件中,或者在文件中读取它们。在这个过程中也可能会产生安全性问题

例子

引自10亿维基百科页面的XML片段定义了10个嵌套实体,每一项扩展10倍的子项,总共有10亿的扩展项。

然而,前面提到的XML库无法容纳10亿多的项。Defused XML列出了这种攻击和Python库的其他缺点,并指出了如何修改设置避免这些问题。或者使用defusedxml库作为安全的保护
from xml.etree.ElementTree import parse
改为:
from defusedxml.ElementTree import parse

配置文件

最好不要定义自己的配置文件格式,因为你需要同时维护写入配置文件的程序以及读取配置文件的程序

我们使用标准configparser模块处理Windows风格的初始化.ini文件。

# 配置文件settings.cfg
[english]
greeting = Hello

[french]
greeting = Bonjour

[files]
home = /usr/local
bin = %(home)s/bin
import configparser
cfg = configparser.ConfigParser()
cfg.read('settings.cfg')
print(cfg['files']['home'])
print(cfg['files']['bin'])
print(cfg['french']['greeting'])

结果:

/usr/local
/usr/local/bin
Bonjour

如果你需要二层以上的嵌套结构,使用YAML或者JSON

其它交换格式

这些二进制数据交换格式通常比XML或者JSON更加快速和复杂:

使用pickle序列化

存储数据结构到一个文件中称为序列化(serializing)。像JSON这样的格式需要定制的序列化数据的转换器(我猜是有些格式需要自定义吧)。

Python提供了pickle模块以特殊的二进制格式保存和恢复数据对象。

还记得JSON解析datetime对象时出现呢问题吗?但对于pickle就不存在问题:
这里写图片描述

pickle同样也适用于自己定义的类和对象

注意:因为pickle会创建Python对象,前面提到的安全问题也同样会发生,不要对你不信任的文件做反序列化。

结构化二进制文件

有些文件格式是为了存储特殊的数据结构,它们既不是关系型数据库也不是NoSQL数据库

结构化二进制文件与之前所说的序列化的区别:

  • 二进制序列化文件的结构需要反序列化才能得到,所以文件的结构仍然基于文本。而结构化二进制文件不是如此,其结构基于二进制的。

电子数据表

电子数据表,尤其是Excel,是广泛使用的二进制数据格式。如果你把电子数据表保存到一个CSV文件中,就可以利用之前提到的标准csv模块去读取它。

如果你有一个xls文件,也可以使用第三方库xlrd读写文件。

层次数据格式

层次数据格式(HDF5)是一种用于多维数据或者层次数值数据的二进制格式

它主要用在科学计算领域,快速读取海量数据集(GB或者TB)是常见的需求。

即使某些情况下HDF5能很好地替代数据库,但它在商业应用上也是默默无闻的。它能适用于WORM(Write Once/Read Many; 一次写入,多次读取)应用,不用担心写操作冲突的数据保护(?)

下面是两个可能有用的模块:

这两个模块都会在附录C中的Python科学应用中讨论,在这里提到它以防你有存储和检索海量大数据的需求以及考虑不同于普通数据库的解决方案

一个很好的例子是Million Song dataset,就是通过HDF5格式下载歌曲数据。

关系型数据库

关系型数据库提供如下功能(感觉这些知识要补补):

  • 多用户同时访问数据
  • 用户使用数据的保护
  • 高效地存储和检索数据
  • 数据被模式定义以及被约束限制
  • Joins通过连接发现不同数据之间的关系
  • 声明式(非命令式)查询语言,SQL(Structed Query Language)

之所以被称为关系型(relational)是因为数据库展现了表单(table)形式的不同类型数据之间的关系。

某一行或者某几行通常作为表单的主键,在表单中主键的值是独一无二的,防止重复增添数据项。这些键在查询时被快速索引,方便快速地找到指定行

每一个表单都附属于某数据库。两层的层次结构便于更好地组织和管理。

如果你想通过非主键的列的值查找数据,可以定义一个二级索引,否则数据库服务器需要扫描整个表单,暴力搜索每一行找到匹配的值

表单之间可以通过外键建立关系,列的值受这些键的约束。

声明式与命令式

声明式编程(Declarative programming),即利用声明式语言进行编程的方式,与命令式编程相对立。它描述目标性质,让计算机明白目标,而非流程。声明式编程不用告诉计算机问题领域,从而避免随之而来的副作用。

而指令式编程则需要用算法来明确的指出每一步该怎么做。它通常被看做是形式逻辑的理论,把计算看做推导。

声明式编程因大幅简化了并行计算的编写难度,自2009起备受关注。声明式语言包括数据库查询语言(SQL,XQuery),正则表达式,逻辑编程,函数式编程和组态管理系统。这种编程方式通过函数、推论规则或项重写(term-rewriting)规则,来描述变量之间的关系。它的语言运行器(编译器或解释器)采用了一个固定的算法,以从这些关系产生结果。目前,声明式编程语言通常用作解决人工智能和约束满足问题。

SQL

SQL是一种声明式语言,它是关系型数据库的通用语言。SQL查询时客户端发送给数据库服务器的文本字符串,指明需要执行的具体操作。SQL语言不区分大小写。

SQL语句有两种主要的类型:

  • DDL(数据定义语言)
    处理用户、数据库以及表单的创建、删除、约束和权限等
  • DML(数据操作语言)
    处理数据插入、选择、更新和删除

SQL DDL命令

操作SQL模式SQL示例
创建数据库CREATE DATABASE dbnameCREATE DATABASE d
选择当前数据库USE dbnameUSE d
删除数据库以及表单DROP DATABASE dbnameDROP DATABASE d
创建表单CREATE TABLE tbname(coldefs)CREATE TABLE t(id INT, count INT)
删除表单DROP TABLE tbnmaeDROP TABLE t
删除表单中所有的行TRUNCATE TABLE tbnameTRUNCATE TABLE t

SQL DML(CRUD)命令

  • Create: 使用INSERT语句创建
  • Read: 使用SELECT语句创建
  • Update: 使用UPDATE语句更新
  • Delete: 使用DELETE语句删除
操作SQL模式SQL示例
增加行INSERT INTO tbname VALUES(…)INSERT INTO t VALUES(7, 40)
选择全部行和全部列SELECT * FROM tbnameSELECT * FROM t
选择全部行和部分列SELECT cols FROM tbnameSELECT id,count FROM t
选择部分行和部分列SELECT cols FROM tbname WHERE conditionSELECT id, count FROM WHERE count > 5 AND id = 9
修改一列的部分行UPDATE tbname SET col = value WHERE conditionUPDATE t SET count = 3 WHERE id = 5
删除部分行DELETE FROM tbname WHERE conditionDELETE FROM t WHERE count <= 10 OR id = 16

DB-API

应用程序编程接口(API)是访问某些服务的函数集合。DB-API是Python中访问关系型数据库的标准API。

使用它可以编写简单的程序来处理多种类型的关系型数据库,**不需要为每种数据库编写独立的程序,类似于Java的JDBC或者Perl的dbi。

主要函数

  • connect()
    连接数据库,包含参数用户名、密码、服务器地址等等
  • cursor
    创建一个cursor对象来管理查询
  • execute()和executemany()
    对数据库执行一个或多个SQL命令
  • fetchone()、fetchmany()和fetchall()
    得到execute之后的结果

Python数据库模块遵循DB-API,但会有扩展和细节上的差别。

SQLite

SQLite是一种轻量级、优秀的开源关系型数据库。它使用Python的标准库实现,并且存储数据库在普通文件中

这些文件在不同机器和操作系统之间是可移植的,使得SQLite成为简易关系型数据库应用的可移植的解决方案。

它不像功能全面的MySQL或者PostgreSQL,SQLite仅仅支持原生SQL以及多用户并发操作

浏览器、智能手机和其他应用会把SQLite作为嵌入数据库。

Python3中使用sqlite3

SQLite is a C library that provides a lightweight disk-based database that doesn’t require a separate server process and allows accessing the database using a nonstandard variant of the SQL query language. Some applications can use SQLite for internal data storage.

It’s also possible to prototype an application using SQLite and then port the code to a larger database such as PostgreSQL or Oracle.

To use the module, you must first create a Connection object that represents the database. Here the data will be stored in the example.db file:

import sqlite3
conn = sqlite3.connect('example.db')

You can also supply the special name :memory: to create a database in RAM.

Once you have a Connection, you can create a Cursor object and call its execute() method to perform SQL commands:

c = conn.cursor()

# Create table
c.execute('''CREATE TABLE stocks
             (date text, trans text, symbol text, qty real, price real)''')

# Insert a row of data
c.execute("INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14)")

# Save (commit) the changes
conn.commit()

# We can also close the connection if we are done with it.
# Just be sure any changes have been committed or they will be lost.
conn.close()

The data you’ve saved is persistent and is available in subsequent sessions:

import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()

Usually your SQL operations will need to use values from Python variables. You shouldn’t assemble your query using Python’s string operations because doing so is insecure; it makes your program vulnerable to an SQL injection attack (see https://xkcd.com/327/ for humorous example of what can go wrong).

Instead, use the DB-API’s parameter substitution. Put ? as a placeholder wherever you want to use a value, and then provide a tuple of values as the second argument to the cursor’s execute() method. (Other database modules may use a different placeholder, such as %s or :1.)
For example:

# Never do this -- insecure!
symbol = 'RHAT'
c.execute("SELECT * FROM stocks WHERE symbol = '%s'" % symbol)

# Do this instead
t = ('RHAT',)
c.execute('SELECT * FROM stocks WHERE symbol=?', t)
print(c.fetchone())

# Larger example that inserts many records at a time
purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
             ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
             ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
            ]
c.executemany('INSERT INTO stocks VALUES (?,?,?,?,?)', purchases)

To retrieve data after executing a SELECT statement, you can either treat the cursor as an iterator, call the cursor’s fetchone() method to retrieve a single matching row, or call fetchall() to get a list of the matching rows.

c.execute('SELECT * FROM stocks ORDER BY price')
# The result is a list
c.fetchall()

This example uses the iterator form:

>>>
>>> for row in c.execute('SELECT * FROM stocks ORDER BY price'):
        print(row)

('2006-01-05', 'BUY', 'RHAT', 100, 35.14)
('2006-03-28', 'BUY', 'IBM', 1000, 45.0)
('2006-04-06', 'SELL', 'IBM', 500, 53.0)
('2006-04-05', 'BUY', 'MSFT', 1000, 72.0)

MySQL

MySQL是一款非常流行的开源关系型数据库。不同于SQLite,它是真正的数据库服务器,因此客户端可以通过网络从不同的设备连接它

MysqlDB是最常用的MySQL驱动程序,但至今没有支持Python3。而MySQLdb并不是唯一的Python连接MySQL的驱动程序。

支持Python3的MySQL驱动程序

  1. Mysql Connector
    Pypi Package: mysql-connector-python
    Usage : mysql.connector
    Features:

    • Officially supported by Oracle
    • Pure python which means it is slower, but it does not require a compiled C component or MySQL libraries and header files to be installed on client machines
    • A little slow
    • Not compatible with MySQLdb
  2. PYMySQL
    Pypi Package: pymysql
    Usage : pymysql
    Features:

    • Pure python
    • Faster than mysql-connector
    • Almost completely compatible with MySQLdb, after calling pymysql.install_as_MySQLdb()
  3. cymysql
    Pypi Package: cymysql
    Usage : cymysql
    Features:

    • fork of pymysql with optional C speedups
  4. mysqlclient
    Pypi Package: mysqlclient
    Usage : mysqlclient
    Features:

    • Django’s recommended library.
    • Friendly fork of the original MySQLdb, hopes to merge back some day
    • The fastest implementation, as it is C based.
    • The most compatible with MySQLdb, as it is a fork
    • Debian and Ubuntu use it to provide both python-mysqldb andpython3-mysqldb packages.

benchmarks here: https://github.com/methane/mysql-driver-benchmarks

PostgreSQL

PostgreSQL是一款功能全面的开源关系型数据库,在很多方面超过MySQL。

PostgreSQL驱动程序

  1. psycopg2
    Pypi Package: psycopg2
    Usage : psycopg2
    Features:
    • 需要来自PostgreSQL客户端工具的pg_config
  2. py-postgresql
    Pypi Package: py-postgresql
    Usage : postgresql

最流行的驱动程序是psycopg2,但是它的安装依赖PostgreSQL客户端的相关库

SQLite vs MySQL vs PostgreSQL

SQLAlchemy

对于所有的关系型数据库而言,SQL是不完全相同的,并且DB-API仅仅实现共有的部分

每一种数据库实现的是包含自己特征和哲学的方言。许多库函数用于消除它们之间的差异,最著名的跨数据库的Python库是SQLAlchemy。它不在Python的标准库,但被广泛认可,使用者众多。

SQLAlchemy实现在驱动程序的基础上。因此不需要导入驱动程序,初始化的连接字符串会作出分配,例如:

dialect + driver :// user : password @ host : port / dbname

  • dialect(数据库类型)
  • driver(使用该数据库的特定驱动程序)
  • user和password(数据库认证字符串)
  • host和port(数据库服务器的位置(只有特定情况下会使用端口号:port))
  • dbname(初始连接到服务器中的数据库)

以内置于Python的SQLite为例,连接字符串忽略host、port、user和password。dbname表示存储SQLite数据库的文件,如果省出dbname,SQLite会在内存创建数据库。如果dbname以斜线(/)开头,那么它是文件所在的绝对路径(Linux和OS X是斜线,而在Windows是例如C:\的路径名)。否则它是当前目录下的相对路径。

# 以下两句等价
'sqlite://'
'sqlite:///:memory:'

SQLAlchemy的三个层级

  • 底层负责处理数据库连接池、执行SQL命令以及返回结果,这和DB-API相似;
  • 再往上是SQL表达式语言,更像Python的SQL生成器
  • 较高级的是对象关系模型(ORM),使用SQL表达式语言,将应用程序代码和关系型数据结构结合起来。

这里写图片描述

引擎层

例子:

import sqlalchemy as sa
conn = sa.create_engine('sqlite://')

conn.execute('CREATE TABLE zoo (critter VARCHAR(20) PRIMARY KEY, count INT, damages FLOAT)')
ins = 'INSERT INTO zoo (critter, count, damages) VALUES (?, ?, ?)'
conn.execute(ins, 'duck', 10, 0.0)
conn.execute(ins, 'bear', 2, 1000.0)
conn.execute(ins, 'weasel', 1, 2000.0)
rows = conn.execute('SELECT * FROM zoo')
for row in rows:
    print(row)

这个例子几乎与SQLite DB-API提到的示例是一样的。

一个优势是在程序开始时不需要导入数据库驱动程序,SQLAlchemy从连接字符串(connection string)已经制定了。改变连接字符串就可以使得代码可移植到另一个数据库

另一个优势是SQLAlchemy的连接池

SQL表达式语言

SQL表达式语言创建了多种SQL操作的函数。相比引擎层,它能处理更多SQL方言的差异,对于关系型数据库应用是一种方便的中间层解决方案

import sqlalchemy as sa
conn = sa.create_engine('sqlite://')
# MetaData is a container object that keeps together many different features of a database (or multiple databases) being described.
meta = sa.MetaData()
# To represent a table, use the Table class.
# Its two primary arguments are the table name, then the MetaData object which it will be associated with. (把MetaData与Table user关联起来)
user = sa.Table('user', meta,
    sa.Column('user_id', sa.Integer, primary_key=True),
    sa.Column('user_name', sa.String(16), nullable=False),
    sa.Column('email_address', sa.String(60), key='email'),
    sa.Column('password', sa.String(20), nullable=False)
)

user_prefs = sa.Table('user_prefs', meta,
    sa.Column('pref_id', sa.Integer, primary_key=True),
    # 注意ForeignKey
    sa.Column('user_id', sa.Integer, sa.ForeignKey("user.user_id"), nullable=False),
    sa.Column('pref_name', sa.String(40), nullable=False),
    sa.Column('pref_value', sa.String(100))
)

# 把meta和conn关联起来
meta.create_all(conn)

conn.execute(user.insert((1, 'Jason', 'ensdomain@163.com', 'xyx')))
conn.execute(user.insert((2, 'Cui', 'hahaha@163.com', 'yxy')))

# user.select() <=> SELECT * FROM user
result = conn.execute(user.select())
rows = result.fetchall()
print(rows)

结果

[(1, 'Jason', 'ensdomain@163.com', 'xyx'), (2, 'Cui', 'hahaha@163.com', 'yxy')]
对象关系映射

在SQLAlchemy的顶层,对象关系映射(ORM)使用SQL表达式语言,但尽量隐藏实际数据库的机制。你自己定义类,ORM负责处理如何读写数据库的数据。

在ORM这个复杂短语背后,最基本的观点是:
同样使用一个关系型数据库,但操作数据的方式仍然和Python保持接近。

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
conn = sa.create_engine('sqlite:///zoo.db')

Base = declarative_base()
# 关联Zoo和Base
class Zoo(Base):
    __tablename__ = 'zoo'
    critter = sa.Column('critter', sa.String, primary_key=True)
    count = sa.Column('count', sa.Integer)
    damages = sa.Column('damages', sa.Float)
    def __init__(self, critter, count, damages):
        self.critter = critter
        self.count = count
        self.damages = damages

# 创建数据库和表单
Base.metadata.create_all(conn)

first = Zoo('duck', 10, 0.0)
second = Zoo('bear', 2, 1000.0)
third = Zoo('weasel', 1, 2000.0)

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=conn)
session = Session()
session.add(first)
session.add_all([second, third])

session.commit()

总结

4种选择:

  • 普通DB-API
  • SQLAlchemy引擎层
  • SQLAlchemy表达式语言
  • SQLAlchemy ORM

使用ORM避开复杂的SQL看似是个很自然的选择,到底应该使用哪一个?

有人认为应该避免使用ORM,但其他人觉得批判太重。不管谁正确,ORM始终是一种抽象,所有的抽象在某种情况下都会出现问题,毕竟它们是有纰漏的。当ORM不能满足需求时,必须要弄明白在SQL如何实现修正。

借用互联网的一句话:
**一些人在遇到问题时理所当然地认为“我明白了,要使用ORM”。但现在它们会有两个困扰。
1. 谨慎使用ORM,多用于简单应用
2. 如果应用足够简单的话,或许至少可以直接使用SQL(或者SQL表达式语言)

或者尝试一些更为简单的,例如dataset。它建立在SQLAlchemy之上,提供对于SQL、JSON以及CSV存储的简单ORM

什么是ORM?

NoSQL数据存储

有些数据库并不是关系型的,不支持SQL

它们用来处理庞大的数据集、支持更加灵活的数据定义以及定制的数据操作。

这些被统称为NoSQL(not only SQL)

dbm family

dbm是什么?

The dbm library was a simple database engine, originally written by Ken Thompson and released by AT&T in 1979. The name is a three letter acronym for DataBase Manager, and can also refer to the family of database engines with APIs and features derived from the original dbm.

The dbm library stores arbitrary data by use of a single key (a primary key) in fixed-size buckets and uses hashing techniques to enable fast retrieval of the data by key.

存储机制

The hashing scheme used is a form of extendible hashing, so that the hashing scheme expands as new buckets are added to the database, meaning that, when nearly empty, the database starts with one bucket, which is then split when it becomes full. The two resulting child buckets will themselves split when they become full, so the database grows as keys are added.

The dbm library and its derivatives are pre-relational databases – they manage associative arrays, implemented as on-disk hash tables.

适用场景

In practice, they can offer a more practical solution for high-speed storage accessed by key, as they do not require the overhead of connecting and preparing queries.

This is balanced by the fact that they can generally only be opened for writing by a single process at a time. An agent daemon can handle requests from multiple processes, but introduces IPC overhead.

dbm的特点

dbm格式在NoSQL出现前已存在很久了,它们是按照键值对的形式存储,封装在应用程序(如网页浏览器)中,来维护各种各样的配置。

从以下角度看,dbm数据库和Python字典是类似的:

  • 给一个键赋值,自动保存到磁盘中的数据库
  • 通过键得到对应的值
import dbm
# 'r': 读; 'w': 写; 'c': 读和写
db = dbm.open('definitions', 'c')

# 键值智能是字符串或字节数据?
db['mustard'] = 'yellow'
db['ketchup'] = 'red'
db['pesto'] = 'green'

print(len(db))
print(db['pesto'])

# 现在关掉数据库,然后重新打开验证它是否被完整保存
db.close()
db = dbm.open('definitions', 'r')
print(db['mustard'])

键和值都以字节保存,因此不能对数据库对象db进行迭代,但是可以使用函数len()得到键的数目

memcached

memcached是一种快速的、内存键值对象的缓存服务器。它一般置于数据库之前,用于存储网页服务器会话数据。如果你要尝试使用,需要一个memcached服务器Python驱动程序

pip install python-memcached

注意:数据在memcached并不是持久化保存的,后面的可能会覆盖早些写入的数据,这本来就是它的固有特性,因为它作为一个缓存服务器,通过舍弃旧数据避免程序运行时内存不足的问题

你可以同时连接到多个memcached服务器后,可以:

  1. 赋值和取值
  2. 其中一个值的自增或者自减
  3. 删除其中一个键
import memcache
db = memcache.Client(['127.0.1:11211'])
db.set('marco', 'polo')
db.get('marco')
db.set('ducks', 0)
db.get('ducks')
db.incr('ducks', 2)
db.get('ducks')

Redis

Redis是一种数据结构服务器(data structure server)

和memcached类似,Redis服务器的所有数据都是基于内存的(现在也可以选择把数据存放在磁盘)。

不同于memcached,Redis可以实现:

  1. 存储数据到磁盘,方便断电重启和提升可靠性
  2. 保存旧数据
  3. 提供多种数据结构,不限于简单字符串

Redis的数据类型和Python很相近,Redis服务器会是一个或多个Python应用程序之间共享数据的非常有帮助的中间件

Python的Redis驱动程序redis-py在Github托管代码和测试用例,也可以在此参考在线参考文档

可以使用这条命令安装: pip install redis

使用

字符串

具有单一值一个键被称作Redis的字符串。简单的Python数据类型可以自动转换成Redis字符串

# 现在连接到一些主机(默认localhost)以及端口(默认6379)上的Redis服务器
>>> import redis
# 下面句子等价于redis.Redis('localhost')或者redis.Redis('localhost', 6379)
>>> conn = redis.Redis()
# 展示所有已有的keys
>>> conn.keys('*')
[]
>>> conn.set('secret', 'ni!')
True
>>> conn.set('carats', 24)
True
>>> conn.set('fever', 101.5)
True
>>> conn.get('secret')
'ni!'
>>> conn.get('carats')
'24'
>>> conn.get('fever')
'101.5'
# setnx()方法只有当键不存在时才设定值
>>> conn.setnx('secret', 'icky-icky-icky-ptang-zoop-boing!')
False
# getset()方法会返回旧的值,同时赋予新的值
>>> conn.get('secret', 'icky-icky-icky-ptang-zoop-boing!')
'ni!'
>>> conn.get('secret')
'icky-icky-icky-ptang-zoop-boing!'
# 使用函数getrange()得到子串(偏移量offset: 0代表开始,-1代表结束)
>>> conn.getrange('secret', -6, -1)
'boing!'
# 使用函数setrange()替代子串(从开始位置偏移)
>>> conn.setrange('secret', 0, 'ICKY')
32
>>> conn.get('secret')
'ICKY-icky-icky-ptang-zoop-boing!'
# 使用函数mset()一次设置多个键值
>>> conn.mset({'pie': 'cherry', 'cordial': 'sherry'})
True
# 使用函数mget()一次取到多个键的值
>>> conn.mget(['fever', 'carats'])
['101.5', '24']
# 使用函数delete()删掉一个键
>>> conn.delete('fever')
True
#使用函数incr()或者incrbyfloat()增加值,函数decr()减少值
>>> conn.incr('carats')
25
>>> conn.incr('carats', 10)
35
>>> conn.decr('carats')
34
>>> conn.decr('carats', 15)
19
>>> conn.set('fever', 101.5)
True
>>> conn.incrbyfloat('fever')
102.5
>>> conn.incrbyfloat('fever', 0.5)
103.0
#不存在函数decrbyfloat(),可以用增加负数代替
>>> conn.incrbyfloat('fever', -2.0)
101.0
列表

Redis的列表仅能包含字符串。当第一次插入数据时列表被创建。

第10章会介绍如何使用Redis列表以及发布-订阅(publish-subscribe)用于实现任务队列。

# 使用函数lpush()在开始处插入
# 'zoo'是列表名称
>>> conn.lpush('zoo', 'bear')
1
>>> conn.lpush('zoo', 'alligator', 'duck')
3
# 使用linsert()函数在一个值的前或者后插入
>>> conn.linsert('zoo', 'before', 'bear', 'beaver')
4
>>> conn.linsert('zoo', 'after', 'bear', 'cassowary')
5
# 上面指令得到['duck', 'alligator', 'beaver', 'bear', 'cassowary']
# 使用lset()函数在偏移量处设置新值(列表必须已经存在)
>>> conn.lset('zoo', 2, 'marmoset')
True
# 上面指令得到['duck', 'alligator', 'marmoset', 'bear', 'cassowary']
# rpush()函数在结尾处插入
>>> conn.rpush('zoo', 'yak')
6
# 使用lindex()函数取到给定偏移量处的值
>>> conn.lindex('zoo', 3)
'bear'
# 使用lrange()函数去到给定偏移范围的所有值
>>> conn.lrange('zoo', 0, 2)
['duck', 'alligator', 'marmoset']
# 使用ltrim()函数仅保留列表中给定范围的值
>>> conn.ltrim('zoo', 1, 4)
True
# 上面操作后,zoo列表变为['alligator', 'marmoset', 'bear', 'cassowary']
# 使用函数lrange()得到一定范围的值
>>> conn.lrange('zoo', 0, -1)
['alligator', 'marmoset', 'bear', 'cassowary']
哈希表

Redis的哈希表类似于Python字典,但它仅包含字符串,因此只能有一层结构,不能进行嵌套。

# 使用函数hmset()在哈希表song设置字段do和字段re的值
>>> conn.hmset('song', {'do': 'a deer', 're': 'about a deer'})
True
# 使用函数hset()设置一个单一字段值
>>> conn.hset('song', 'mi', 'a note to follow re')
1
# 使用函数hget()取到一个字段的值
>>> conn.hget('song', 'mi')
'a note to follow re'
# 使用函数hmget()取到多个字段的值
>>> conn.hmget('song', 're', 'do')
['about a deer', 'a deer']
# 使用函数hkeys()取到所有字段的键
>>> conn.hkeys('song')
['do', 're', 'mi']
# 使用函数hvals()得到所有字段的值
>>> conn.hvals('song')
['a deer', 'about a deer', 'a note to follow re']
# 使用函数hlen()返回字段的总数
>>> conn.hlen('song')
3
# 使用函数hgetall()取到所有字段的键和值
>>> conn.hgetall('song')
{'do': 'a deer', 're': 'about a deer', 'mi': 'a note to follow re'}
# 使用函数hsetnx()对字段中不存在的键赋值
>>> conn.hsetnx('song', 'fa', 'a note that rhymes with la')
1
集合

Redis的集合和Python的集合是完全类似的。

>>> conn.sadd('zoo', 'duck', 'goat', 'turkey')
3
# 取得集合中的所有值的数目
>>> conn.scard('zoo')
3
# 返回集合中的所有值
>>> conn.smembers('zoo')
{'duck', 'goat', 'turkey'}
# 从集合中删掉一个值
>>> conn.srem('zoo', 'turkey')
True
# 新建一个集合以展示一些集合间的操作
>>> conn.sadd('better_zoo', 'tiger', 'wolf', 'duck')
0
# 返回集合zoo和集合better_zoo的交集
>>> conn.sinter('zoo', 'better_zoo')
{'duck'}
# 获得集合zoo和集合better_zoo的交集,并存储到新集合fowl_zoo
>>> conn.sinterstore('fowl_zoo', 'zoo', 'better_zoo')
1
>>> conn.smembers('fowl_zoo')
{'duck'}
# 返回集合zoo和集合better_zoo的并集
>>> conn.sunion('zoo', 'better_zoo')
{'duck', 'goat', 'wolf', 'tiger'}
# 返回集合zoo和集合better_zoo的差集
>>> conn.sdiff('zoo', 'better_zoo')
{'goat'}
>>> conn.sdiffstore('zoo_sale', 'zoo', 'better_zoo')
1
>>> conn.smembers('zoo_sale')
{'goat'}
有序集合

Redis中功能最强大的数据类型之一是有序表(sort set或者zset)。它里面的值都是独一无二的,但是每一个值都关联对应浮点值分数(score)。可以通过值或者分数取得每一项,有序集合有很多用途:

  • 排行榜
  • 二级索引
  • 时间序列(把时间戳作为分数)

我们把最后一个(时间序列)作为例子,通过时间戳跟踪用户的登陆。在这里,时间表达使用Unix的epoch的值,它由Python的time()函数返回:

>>> import time
>>> now = time.time()
>>> conn.zadd('logins', 'smeagol', now)
1
>>> conn.zadd('logins', 'sauron', now+(5*60))
1
>>> conn.zadd('logins', 'bilbo', now+(2*60*60))
1
>>> conn.zadd('logins', 'treeebeard', now+(24*60*60))
1
# 查看bilbo登陆的次序
>>> conn.zrank('logins', bilbo')
2
# 查看bilbo登陆的时间
>>> conn.zscore('logins', 'bilbo')

# 按照等咯的顺序查看每一位访客
>>> conn.zrange('logins', 0, -1)
['smeagol', 'sauron', 'bilbo', 'treebeard']

# 按照等咯的顺序查看每一位访客并附带他们的登陆时间
>>> conn.zrange('logins', 0, -1, withscore = True)
位图

位图(bit)是一种非常省空间且快速的处理超大集合数字的方式

假设你有一个很多用户注册的网站,想要跟踪用户的登陆频率、在某一天用户的访问量以及同一用户在固定时间内的访问频率,等等。

当然,你可以使用Redis集合,但如果使用递增的用户ID,位图的方法更加简洁和快速。

# 首先为每一天创建一个为集合(bitset)。为了测试,我们仅使用3天和部分用户ID
>>> days = ['2013-02-25', '2013-02-26', '2013-02-27']
# 三个用户ID
>>> big_spender = 1089
>>> tire_kicker = 40459
>>> late_joiner = 550212
# 第一天的访问情况
>>> conn.setbit(days[0], big_spender, 1)
0
>>> conn.setbit(days[0], tire_kicker, 1)
0
# 第二天的访问情况
>>> conn.setbit(days[1], big_spender, 1)
0
# 第三天的访问情况
>>> conn.setbit(days[2], big_spender, 1)
0
>>> conn.setbit(days[2], late_joiner, 1)
0
# 现在统计得到这三天的日访客数
>>> for day in days:
...     conn.bitcount(day)
...
2
1
2
# 查看某一天某个用户是否有访问记录?
>>> conn.getbit(days[1], tire_kicker)
0
# 有多少访客每天都会访问?
>>> conn.bitop('and', 'everyday', *days)
68777
>>> conn.bitcount('everyday')
1
# 他是谁?
>>> conn.getbit('everyday', big_spender)
1
# 这三天中独立的访客数量有多少?
>>> conn.bittop('or', 'alldays', *days)
68777
>>> conn.bitcount('alldays')
3
缓存和过期

所有的Redis键都有一个生存期或者过期时间(expiration date),默认情况下,生存期是永久的。也可以使用expire()函数构造Redis键的生存期,下面看到的设置值是以秒为单位的数。

>>> import time
>>> key = 'now you see it'
>>> conn.set(key, 'but not for long')
Ture
>>> conn.expirt(key, 5)
True
>>> conn.ttl(key)
5
>>> conn.get(key)
'but not for long'
>>> time.sleep(6)
>>> conn.get(key)
>>>

expireat()命令给一个键设定过期时间,对于更新缓存是有帮助的,并且可以限制登录会话。

各种数据结构的说明

  1. https://redis.io/topics/data-types)
  2. https://redis.io/topics/data-types-intro

各种数据结构操作的复杂度

redis中内存消耗优化

其它用法

  1. http://blog.youkuaiyun.com/zhou_1997/article/details/52624468
  2. http://blog.youkuaiyun.com/kongxx/article/details/50952090
  3. http://www.cnblogs.com/Eva-J/p/5152841.html

其它的NoSQL

NoSQL服务器都要处理远超过内存的数据,并且很多服务器需要使用多台计算机。下表列出了值得注意的服务器和它们的Python库。

SitePython API
Cassandrapycassa
CouchDBcouchdb-python
HBasehappybase
Kyoto Cabinetkyotocabinet
MongoDBmongdb
Riakriak-python-client

全文数据库

有一类特殊的数据库用于全文检索它们对所有内容都建索引,所以你可以检索到吟诵风车和满车奶酪的诗歌。下表是一些流行的开源软件以及它们的Python API。

SitePython API
Lucenepylucene
SolrSolPython
ElasticSearchpyes
Sphinxsphinxapi
Xapianxappy
Whoosh由PYthon编写,包含API
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值