文件

数据持久化最简单的类型是普通文件,有时也叫平面文件(flat file)。它仅仅是在一个文件名下的字节流,把数据从一个文件读入内存,然后从内存写入文件。Python很容易实现这些文件操作,它模仿熟悉和流行的 unix 系统的操作。

读写一个文件之前需要打开它:

1
data = open(filename, mode)

下面是对该 open() 调用的简单解释:

  • data 是 open() 返回的文件对象;
  • filename 是该文件的字符串名;
  • mode 是指明文件类型和操作的字符串。

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

  • r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
  • r+ 可读可写,不会创建不存在的文件。如果直接写文件,则从顶部开始写,覆盖之前此位置的内容,如果先读后写,则会在文件最后追加内容。
  • w 表示写模式。若果文件不存在则新创建,如果该文件已存在则将其覆盖。
  • w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
  • x 表示在文件不存在的情况下新创建并写文件。
  • a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
  • a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
    mode 的第二个字母是文件类型:

  • t(或者省略) 代表文本类型。

  • b 代表二进制文件。

打开文件之后就可以调用函数来读写数据,最后需要关闭文件。

使用 write() 写文本文件

1
2
3
>>> poem = "日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。"
>>> len(poem)
32

将整首诗写到libai.txt中:

1
2
3
4
>>> fout = open('libai.txt', 'wt')
>>> fout.write(poem)
32
fout.close()

函数 write() 返回写入文件的字节数。和 print() 一样, 他没有增加空格或者换行符。同样,你也可以在一个文本文件中使用 print()

1
2
3
>>> fout = open('libai.txt', 'w')
>>> print(poem, file=fout)
>>> fout.close()

这就产生了一个问题:到底是用是 write() 还是 print()? print() 默认会在每个参数后面添加空格,在每行结束处添加换行。在之前的例子中, libai.txt 中默认添加了一个换行。为了使 print() 与 write() 有同样的输出,传入下面两个参数:

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

除非自定义参数,否则 print() 会使用默认参数。在这里,我们通过空字符串替换 print() 添加的所有多余输出:

1
2
3
>>> fout = open('libai.txt', 'w')
>>> print(poem, file=fout, sep='', end='')
>>> fout.close()

如果字符串非常大,可以将数据分块,直到所有字符被写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> fout = open('libai.txt', 'w')
>>> size = len(poem)
>>> size
32
>>> offset = 0
>>> chunk = 10
>>> while True:
... if offset > size:
... break
... fout.write(poem[offset:offset+chunk])
... offset += chunk
...
>>> fout.close()

第一次写 10 个字符,4次写完,32个字符。

如果 libai.txt 文件已经存在,使用模式 x 可以避免重写文件:

1
2
3
4
5
6
>>> font = open('libai.txt', 'xt')
Traceback (most recent call last):
File "<ipython-input-108-c2dbf65c7612>", line 1, in <module>
font = open('libai.txt', 'xt')
FileExistsError: [Errno 17] File exists: 'libai.txt'
>>>

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

你可以按照下面的示例那样,使用不带参数的 read() 函数一次读入文件的所有内容。但在读入文件时要格外注意,1GB的文件会用到相同大小的内存。

1
2
3
4
5
6
7
>>> fin = open('libai.txt', 'rt')
>>> poem = fin.read()
>>> poem
'日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。'
>>> fin.close()
>>> len(poem)
32

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> poem = ''
>>> fin = open('libai.txt', 'rt')
>>> chunk = 10
>>> while True:
... fragment = fin.read(chunk)
... if not fragment:
... break
... poem += fragment
...
>>> fin.close()
>>> len(poem)
32
>>> poem
'日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。'

读到结尾之后,再次调用 read() 会返回空字符串(‘’), if not fragment 条件被判断为 False。此时会跳出 while True 的循环。 当然, 你也能使用 readline() 每次读入文件的一行。 在下面的例子中,通过追加每一行拼接成原来的字符串 poem:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> poem = ''
>>> fin = open('libai.txt', 'rt')
>>> while True:
... line = fin.readline()
... if not line:
... break
... poem += line
...
>>> fin.close()
>>> len(poem)
32
>>> poem
'日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。'

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

读取文本文件最简单的方式是使用一个迭代器(iterator),它会每次返回一行。这和之前的例子类似,但代码会更短:

1
2
3
4
5
6
7
8
9
10
>>> poem = ''
>>> fin = open('libai.txt', 'rt')
>>> for line in fin:
... poem += line
...
>>> fin.close()
>>> len(poem)
32
>>> poem
'日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。'

前面所有的示例最终都返回单个字符串 poem。 函数 readlines() 调用时每次读取一行,并返回单行字符串的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> poem = '''日照香炉生紫烟,
... 遥看瀑布挂前川。
... 飞流直下三千尺,
... 疑是银河落九天。
... '''
>>> fin = open('libai.txt', 'wt')
>>> fin.write(poem)
36
>>> fin.close()
>>> fin = open('libai.txt', 'rt')
>>> lines = fin.readlines()
>>> fin.close()
>>> lines
['日照香炉生紫烟,\n', '遥看瀑布挂前川。\n', '飞流直下三千尺,\n', '疑是银河落九天。\n']
>>> print(len(lines), 'lines read')
4 lines read
>>> for line in lines:
... print(line, end='')
...
日照香炉生紫烟,
遥看瀑布挂前川。
飞流直下三千尺,
疑是银河落九天。

使用 write() 写二进制文件

如果文件模式字符串中包含 ‘b’, 那么文件会以二进制模式打开,这种情况下,读写的是字节而不是字符串。

我们手边没有二进制格式的诗,所以直接在 0~255 产生 256 字节的值:

1
2
3
>>> bdata = bytes(range(0, 256))
>>> len(bdata)
256

以二进制模式打开文件,并且一次写入所有的数据:

1
2
3
4
5
>>> fout = open('bfile', 'wb')
>>> fout.write(bdata)
256
>>> fout.close()
>>>

再次,write() 返回到写入的字节数。

对于文本,也可以分块写二进制数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> 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
...
100
100
56
>>> fout.close()

使用 read() 读取二进制文件

1
2
3
4
5
>>> fin = open('bfile', 'rb')
>>> bdata = fin.read()
>>> len(bdata)
256
>>> fin.close()

使用 with 自动关闭文件

如果你忘记关闭已经打开的一个文件,在该文件对象不再被引用之后 Python 会关掉此文件。这也意味着在一个函数中打开文件,并没有及时关闭它,但是在函数结束时会被关掉。然而你可能会在一直运行中的函数或者程序的主要部分打开一个文件,应该强制剩下的所有写操作完成后再关闭文件。

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

1
2
>>> with open('libai.txt', 'wt') as fout:
>>> ... fout.write(poem) ...

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

使用 seek() 改变位置

无论是读或者写文件,Python都会跟踪文件中的位置。函数 tell() 返回距离文件开始处的字节偏移量。函数 seek() 允许跳转到文件其他字节偏移量的位置。这意味着可以不用从头读取文件的没一个字节,直接跳转到最后位置并制度一个字节也是可行的。

对于这个例子,使用之前写过的 256 字节的二进制文件 ‘bfile’:

1
2
3
>>> fin = open('bfile', 'rb')
>>> fin.tell()
0

使用 seek() 读取文件结束前最后一个字节:

1
2
>>> fin.seek(255)
255

一直读到文件结束:

1
2
3
4
5
>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

seek() 同样返回当前的偏移量。

用第二个参数调用函数 seek(): seek(offset, origin)

  • 如果 origin 等于0 (默认为0),从头偏移 offset 个字节;
  • 如果 origin 等于1,从当前位置处偏移 offset 个字节;
  • 如果 origin 等于2,距离最后结尾处偏移 offset 个字节。

这些值也在标准库 os 模块中被定义:

1
2
3
4
5
6
7
>>> import os
>>> os.SEEK_SET
0
>>> os.SEEK_CUR
1
>>> os.SEEK_END
2

所以,我们可以用不通的方法读取最后一个字节:

1
>>> fin = open('bfile', 'rb')

文件结尾前的一个字节:

1
2
3
4
>>> fin.seek(-1, 2)
255
>>> fin.tell()
255

一直读到文件结尾:

1
2
3
4
5
>>> bddata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

在调用 seek() 函数时不需要额外调用 tell()。前面的例子只是想说明两个函数都可以返回同样的偏移量。

下面是从文件的当前位置寻找:

1
>>> fin = open('bfile', 'rb')

接下来的例子返回最后两个字节:

1
2
3
4
>>> fin.seek(254, 0)
254
>>> fin.tell()
254

在此基础上前进一个字节:

1
2
3
4
>>> fin.seek(1, 1)
255
>>> fin.tell()
255

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

异常

Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知 所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行; 如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告 诉Python发生异常时怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行: 显示你编写的友好的错误消息,而不是令用户迷惑的traceback。

使用 try-except 代码块

当你认为可能发生了错误时,可编写一个try-except代码来处理可能引发的异常。你让 Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办。

try 语句有两种主要形式: try-except 和 try-finally . 这两个语句是互斥的, 也就是说你 只能使用其中的一种. 一个 try 语句可以对应一个或多个 except 子句, 但只能对应一个 finally 子句, 或是一个 try-except-finally 复合语句.

处理ZeroDivisionError异常的try-except代码 类似于下面这样:

1
2
3
4
5
6
>>> try:
... print(5/0)
... except ZeroDivisionError:
... print("You can't divide by zero!")
...
You can't divide by zero!

我们将导致错误的代码行print(5/0)放在了一个try代码中。如果try代码中的代码运行起来没有问题,Python将跳过except代码; 如果try代码中的代码导致了错误,Python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同。
在这个示例中,try代码中的代码引发了ZeroDivisionError异常,因此Python指出了该如何解决问题的except代码块,并运行其中的代码。这样,用户看到的是一条 好的错误消息,而不是traceback:

1
You can't divide by zero!

如果try-except代码后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。

使用异常避免崩溃

发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。

下面来创建一个只执行除法运算的简单计算器:

1
2
3
4
5
6
7
8
9
10
11
12
13
1 # coding=utf-8
2 print("Give me two numbers, and I'll divide them.")
3 print("Enter 'q' to quit.")
4
5 while True:
6 first_number = input("\nFirst number: ")
7 if first_number == 'q':
8 break
9 second_number = input("Second number: ")
10 if second_number == 'q':
11 break
12 answer = int(first_number) / int(second_number)
13 print(answer)

在第6行,这个程序提示用户输入一个数字,并将其存储到变量first_number中; 如果用户输入的不是表示退出的q,就再提示用户输入一个数字,并将其存储到变量second_number中(见第9行 )。 接下来,我们计算这两个数字的商 (即answer,见12行)。这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将崩溃:

1
2
3
4
5
6
7
8
9
10
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
Traceback (most recent call last):
File "<ipython-input-208-1e61f850d337>", line 11, in <module>
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
>>>

程序崩溃可不好,但让用户看到 traceback 也不是好主意。不懂技术的用户会被它们搞糊涂, 而且如果用户 怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

else 代码块

通过将可能引发错误的代码放在try-except代码中,可提高这个程序抵御错误的能力。错误是执行除法运算的代码行导致的,因此我们需要将它放到try-except代码块中。这个示例还包含一个else代码块; 依赖于try代码块成功执行的代码都应放到else代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 # coding=utf-8
2 print("Give me two numbers, and I'll divide them.")
3 print("Enter 'q' to quit.")
4
5 while True:
6 first_number = input("\nFirst number: ")
7 if first_number == 'q':
8 break
9 second_number = input("Second number: ")
10 if second_number == 'q':
11 break
12 try:
13 answer = int(first_number) / int(second_number)
14 except ZeroDivisionError:
15 print("You can't divide by 0!")
16 else:
17 print(answer)

我们让Python尝试执行try代码块中的除法运算(见12行),这个代码块只包含可能导致错误的代码。依赖于try代码块成功执行的代码都放在else代码中; 在这个示例中,如果除法运算成功,我们就使用else代码块来打印结果(见16行)。
except代码块告诉Python,出现ZeroDivisionError异常时该怎么办(见14行 )。如果try代码因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户根本看不到traceback:

1
2
3
4
5
6
7
8
9
10
11
12
13
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
You can't divide by 0!
First number: 5
Second number: 2
2.5
First number: q
>>>

try-except-else代码块的工作原理大致如下: Python尝试执行try代码块中的代码; 只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的的代码; 这些代码应放在else代码中。except代码块告诉Python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。
通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击。

失败时不提示

使用 pass

决定报告那些错误

在什么情况下该向用户报告错误? 在什么情况下又应该在失败时不提示呢? 如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。 如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。 向用户显示他不想看到的信息可能会降低程序的可用性。Python的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。

编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络连接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理 ,以及出现错误时该向用户提供多少相关的信息。

finally子句

finally 子句是无论异常是否发生,是否捕捉都会执行的一段代码. 你可以将 finally 仅仅配合 try 一起使用,也可以和 try-except(else 也是可选的)一起使用. 你可以用 finally 子句 与 try-except 或 try-except-else 一起使用.

下面是 try-except-else-finally 语法的示例:

1
2
3
4
5
6
7
8
9
10
try:
A
except MyException1:
B1
except MyException2:
B2
else:
C
finally:
D

当然,无论如何,你都可以有不止一个的 except 子句,但最少有一个 except 语句,而 else 和 finally 都是可选的.A,B,C 和 D 是程序(代码块).程序会按预期的顺序执行.(注意:可能的顺序是 A-C-D[正常]或 A-B-D[异常]).无论异常发生在 A,B,和/或 C 都将执行 finally 块.

常见的异常

异常 描述
AssertionError assert(断言)语句失败
AttributeError 试图访问一个对象没有的属性,比如foo.x ,但是foo没有x这个属性。
IOError 输入/输出异常,基本上是无法打开文件。
ImportError 无法引入模块或者包,基本上是路径问题
IndentationError 语法错误,代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KerboardInterrupt Ctrl + C 被按下
NameError 使用一个还未被赋值予对象的变量
SyntaxError Python代码非法,代码不能解释
TypeError 传入对象类型与要求的不符
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另一个同名的全局变量,导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的

docker 配置部分

拉取 alpine

1
$ alpine docker pull alpine

查看当前机 下的 docker 镜像

1
$ docker images

创建容器

1
$ docker create -ti --name python -h python -p 8080:8080 -w /root alpine sh

启动容器

1
$ docker start python

进入容器

1
$ docker attach python

停止容器

1
$ docker stop python

删除容器

1
$ docker rm python

alpine 配置

更改下alpine镜像的源

1
echo "http://mirrors.aliyun.com/alpine/v3.4/main/" > /etc/apk/repositories
1
apk update

安装常用工具

1
$ apk add bash vim musl-dev gcc g++ python3 python3-dev

启动bash

1
$ bash

更新pip

1
$ pip3 install -U pip

查看 pip 版本号

1
2
$ pip -V
pip 9.0.1 from /usr/lib/python3.5/site-packages (python 3.5)

显示了全局的系统库路径

Python 项目管理思路

1.全局的只安装开发工具类,全局的保持最小化
2.建立虚拟隔离环境,各个项目管理自己的第三方依赖库

例如:
项目 a 依赖 python2
项目 b 依赖 python3

所以需要简历独立运行环境

python3 自带 venv, 但是不够好用,推荐第三方工具 virtualenv

virtualenv 提供了核心功能
virtualenvwrapper 提供了丰富功能,包装器

安装虚拟环境

docker 环境下依赖包检查不完整, 所以先安装pbr

1
pip install -U pbr

1
pip install -U virtualenvwrapper

查看安装的库

1
pip list

配置bashrc

1
2
3
4
5
6
7
vim ~/.bashrc
export PS1="\[\e[32m\]\u:\w \$\[\e[m\] " # 命令行提示符样式设置
export WORKON_HOME=/root/py/venv # 虚拟环境目录
export PROJECT_HOME=/root/py # 项目目录
export VIRTUALENVWRAPPER_PYTHON=`which python3` # 指定虚拟环境使用 python 版本
source /usr/bin/virtualenvwrapper.sh # 执行 vwrapper 脚本

让 bashrc 生效

1
source .bashrc

在数学概念中,变量(Variable)表示没有固定值,可改变的数。但从计算机系统实现角度来看,变量是一段或多段用来存储数据的内存。

编程语言允许你定义变量(variable)。所谓变量就是在程序中为了方便地引用内存中的值而为它取的名称。在 Python 中,我们用 = 来给一个变量赋值。

我们先来声明一个变量

1
2
3
>>> a = 7
>>> print(a)
7

Python 中的变量有一个非常重要的性质:它仅仅是一个名字。赋值操作并不会实际 复制值,它只是为数据对象取个相关的名字。名字是对对象的引用而不是对象本身。你可以把名字想象成贴在盒子上的标签

1
2
3
4
变量 内存
+-------------------+
a +------------> | 7 |
+-------------------+

在Python中使用变量时,需要遵守一些规则和指南。违反这些规则将引发错误,而指南旨在 让你编写的代码更容易阅读和理解。请务必牢记下述有关变量的规则。

  • 变量名只能包含字母、数字和下划线。变量名可以字母或下划线打头,但不能以数字打 头,例如,可将变量命名为message_1,但不能将其命名为1_message。
  • 变量名不能包含空格,但可使用下划线来分隔其中的单词。例如,变量名greeting_message 可行,但变量名greeting message会引发错误。
  • 不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词

Python 关键字

下面的关键字都有特殊含义,如果你将它们用作变量名,将引发错误:

Python 内置函数

将内置函数名用作变量名时,不会导致错误,但将覆盖这些函数的行为:

注意 在Python2.7中,print是关键字而不是函数。另外,Python3没有内置函数unicode()。这 两个单词都不应用作变量名。

  • 变量名应既简短又具有描述性。例如,name比n好,student_name比s_n好,name_length 比length_of_persons_name好。
  • 慎用小写字母l和大写字母O,因为它们可能被人错看成数字1和0。 要创建良好的变量名,需要经过一定的实践,在程序复杂而有趣时尤其如此。随着你编写的程序越来越多,并开始阅读别人编写的代码,将越来越善于创建有意义的变量名。

1.1.2 列表

列表由一系列按特定顺序排列的元素组成。你可以创建包含字母表中所有字母、数字0~9或 所有家庭成员姓名的列表;也可以将任何东西加入列表中,其中的元素之间可以没有任何关系。 鉴于列表通常包含多个元素,给列表指定一个表示复数的名称(如names)是个不错的主意。

列表非常适合利用顺序和位置定位某一元素,尤其是当元素的顺序或内容经常发生改变时。与字符串不同,列表是可变的。你可以直接对原始列表进行修改:添加新元素、删除 或覆盖已有元素。在列表中,具有相同值的元素允许出现多次。

常用列表方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
>>> dir(list)
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append',
'clear',
'copy',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']

append这个是向列表末尾追加一个元素,如下:

1
2
3
4
>>> numbers = [1,2,2,3,3,3]
>>> numbers.append(4)
>>> numbers
[1, 2, 2, 3, 3, 3, 4]

clear 清除一个列表中的所有元素

1
2
3
4
5
6
7
>>> n
[1, 2, 3, 4, 5]
>>> n.clear()
>>> n
[]

copy 复制一个列表

1
2
3
4
5
6
7
8
9
10
11
12
>>> n = [1,2,3,4,5]
>>> n.copy()
[1, 2, 3, 4, 5]
>>> n.copy()
[1, 2, 3, 4, 5]
>>> num = n.copy()
>>> num
[1, 2, 3, 4, 5]

count 计算某个元素在列表中出现的次数,如下:

1
2
3
4
5
6
7
8
9
10
>>> numbers
[1, 2, 2, 3, 3, 3, 4]
>>> numbers.count(1)
1
>>> numbers.count(2)
2
>>> numbers.count(3)
3
>>> numbers.count(4)
1

extend 直接向列表末尾一次性追加另一个列表,如下:

1
2
3
4
5
6
7
8
>>> numbers
[1, 2, 2, 3, 3, 3, 4]
>>> a = [4,4,4]
>>> numbers.extend(a)
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> a
[4, 4, 4]

index 从列表中找出某个值第一个匹配的索引位置

1
2
3
4
5
6
7
8
9
10
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.index(1)
0
>>> numbers.index(2)
1
>>> numbers.index(3)
3
>>> numbers.index(4)
6

insert 将对象插入列表中

1
2
3
4
5
>>> numbers
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.insert(0,0)
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

pop 移除列表中的最后一个元素,默认是最后一个

1
2
3
4
5
6
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> numbers.pop()
4
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4]

remove 移除列表中第一个匹配的元素

1
2
3
4
5
6
7
8
>>> num
[1, 2, 1, 3, 2]
>>> num.remove(1)
>>> num
[2, 1, 3, 2]
>>> num.remove(2)
>>> num
[1, 3, 2]

reverse 将列表中的元素反向存放

1
2
3
4
5
>>> numbers
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4]
>>> numbers.reverse()
>>> numbers
[4, 4, 4, 3, 3, 3, 2, 2, 1, 0]

sort 对列表排序,这个我比较喜欢,里面挺多好玩的东西,下面做个简单的介绍

默认排序:

1
2
3
4
>>> L = [1,4,3,2]
>>> L.sort()
>>> L
[1, 2, 3, 4]

自定义排序:

查看sort的使用方法

1
2
>>> L.sort.__doc__
'L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*'

根据提供的key函数为元素产生一个键,列表的元素按照这个键值来排序

1
2
3
4
>>> x = ['abc','a','bc','abcd']
>>> x.sort(key=len)
>>> x
['a', 'bc', 'abc', 'abcd’]

反向排序

1
2
3
4
>>> n = [3, 1, 2, 5]
>>> n.sort(reverse=True)
>>> n
[5, 3, 2, 1]

使用 in 判断值是否存在

判断一个值是否存在于给定的列表中的许多方式,其中最具有 Python 风格的是使用 in:

1
2
3
4
5
6
>>> num = [1, 2, 3]
>>> 1 in num
True
>>> 5 in num
False
>>>

同一个值可能出现在列表的多个位置,但只要至少出现一次,in就会返回Ture

1
2
3
>>> num = [1, 1, 2, 3]
>>> 1 in num
True

使用 len() 获取长度

len() 可以返回列表长度:

1
2
3
>>> num = [1, 2, 3]
>>> len(num)
3

使用=赋值,使用copy()复制

如果将一个列表赋值给了多个变量,改变其中的任何一处造成其他变量对应的值也被修改,如下所示:

1
2
3
4
>>> a = [1, 2, 3] >>> a [1, 2, 3] >>> b = a >>> b [1, 2, 3] >>> a[0] = 'surprise'
>>> a ['surprise', 2, 3]
>>> b
['surprise', 2, 3]

还记得解释变量的时候,那个贴标签的比喻吗?b与a实际上指向的是同一个对象,因此,无论我们是通过a还是b来修改列表的内容,其结果都会作用于双方:

1
2
3
>>> b ['surprise', 2, 3] >>> b[0] = 'I love surprises'
>>> b ['I love surprises', 2, 3]
>>> a ['I love surprises', 2, 3]

通过下面任意一种方法,都可以将一个列表的值复制到另一个新的列表中:

  • 列表 copy() 函数
  • list() 转换函数
  • 列表分片 [:]
1
2
3
4
5
>>> a = [1, 2, 3] >>> b = a.copy() >>> c = list(a) >>> d = a[:]
>>> a[0] = 'one'
>>> a
['one', 2, 3]
>>> b [1, 2, 3] >>> c [1, 2, 3] >>> d [1, 2, 3]

b、c、d 都是a的复制:它们是自身带有值的新对象,与原始的a所指向的列表对象[1, 2, 3]没有任何关联。所以改变a不影响b、c、d的复制。

1. 内置类型

1.1 变量

1.2 布尔

表示真假的类型,仅包含 True 和 False 两种取值

数字 0、None,以及元素为空的容器类对象都可视作 bool False,反之为 True。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> bool(0)
False
>>> bool(None)
False
>>> bool("")
False
>>> bool([])
False
>>> bool({})
False
>>> bool(1)
True
>>> bool([1,2])
True

bool类型支持的运算符

1
2
3
>>> a and b # 如果 a 和 b 都是 True,结果就是 Ture , 否则 False。
>>> a or b # a 和 b 至少有一个是 True 时结果是 True, 否则 False。
>>> not b # 如果 a 是 False, 结果是 True, 如果 a 是 True,结果是 False。

1.3 数字

python本身支持整数以及浮点数。你可以对这些数字进行下表中的计算。

运算符 表述 示例 运算结果
+ 加法 1 + 1 2
- 减法 4 - 2 2
* 乘法 2 * 2 4
/ 浮点数除法 7 / 2 3.5
// 整数除法 7 // 2 3
/ 摸(求余) 7 % 3 1
** 2 ** 2 4

整数

任何仅含数字的序列在 Python 中都被认为是整数:

1
2
>>> 5
5

Python还支持运算次序,因此你可在同一个表达式中使用多种运算。你还可以使用括号来修 改运算次序,让Python按你指定的次序执行运算,如下所示:

1
2
3
>>> 2 + 3*4
14
>>> (2 + 3) * 4 20

在这些示例中,空格不影响Python计算表达式的方式,它们的存在旨在让你阅读代码时,能 迅速确定先执行哪些运算。

浮点数

Python将带小数点的数字都称为浮点数。大多数编程语言都使用了这个术语,它指出了这样一个事实:小数点可出现在数字的任何位置。每种编程语言都须细心设计,以妥善地处理浮点数, 确保不管小数点出现在什么位置,数字的行为都是正常的。

1
2
3
4
5
6
7
8
>>> 0.1 + 0.1
0.2
>>> 0.2 + 0.2
0.4
>>>2 * 0.1
0.2
>>>2 * 0.2
0.4

但需要注意的是,结果包含的小数位数可能是不确定的:

1
2
3
4
>>> 0.2 + 0.1
0.30000000000000004
>>> 3 * 0.1
0.30000000000000004

所有语言都存在这种问题,没有什么可担心的。Python会尽力找到一种方式,以尽可能精确地表示结果,但鉴于计算机内部表示数字的方式,这在有些情况下很难。就现在而言,暂时忽略 多余的小数位数即可。

1.4 字符串

字符串是由多个字符组成的序列。在Python中,用引号括起的都是字符串,字符串定义简单自由,可以是单引号、双引号或者三引号。但是个人建议使用双引号表示字符串,用单引号表示字符,和其他语言习惯保持一致。字符串是不可变序列(immutable, sequence)类型,默认存储 Unicode 文本。 python3 不再使用 str 处理二进制字节数据,改为使用 bytes 和 bytearray,前者同为不可变类型。

1
2
3
4
5
6
>>> s = "abc汉字"
>>> >>> len(s)
5
>>> print(ascii(s))
'abc\u6c49\u5b57'

内置函数 ascii 将目标转换为可打印 ASCII 字符组成的字符串。

构建字符串字面量很容易,单引号、双引号,以及跨行的三个引号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>> "ab'c" # 双引号。
"ab'c"
>>> 'ab"c' # 单引号。
'ab"c'
>>> 'ab\'c' # 引号转义。
"ab'c"
>>> """ # 多行,也可以用三个单引号。
... a
... b
... c"""
'\na\nb\nc'
>>> "a" "b" 'c' # 自动合并多个相邻字符串。
'abc'

可在字面量前添加特殊指示符。

1
2
3
4
5
6
7
8
>>> r"abc\nd" # raw string,禁用转义。
'abc\\nd'
>>> type(b"abc")
<class 'bytes'>
>>> type(u"abc")
<class 'str'>

str() 类型转换

1
2
>>> str(2.2)
'2.2'

合并字符串

format

1
2
>>> "python培训哪家强:{}".format('京峰教育')
'python培训哪家强:京峰教育'
1
2
>>> "python培训哪家强:{}, 京峰教育谁最帅? {}".format('京峰教育', '斌哥')
'python培训哪家强:京峰教育, 京峰教育谁最帅? 斌哥'
1
2
>>> "python培训哪家强:{0}, 京峰教育谁最帅? {1}".format('京峰教育', '斌哥')
'python培训哪家强:京峰教育, 京峰教育谁最帅? 斌哥'

+

1
2
>>> '京峰' + '教育'
'京峰教育'

split() 分割

1
2
3
4
>>> s = 'a,b,c'
>>> s.split(',')
['a', 'b', 'c']

join() 合并

1
2
3
4
5
6
7
>>> l = s.split(',')
>>> l
['a', 'b', 'c']
>>> ','.join(l)
'a,b,c'

find 查找子串

查找到返回该子串在原字符串中的索引位置,如果无法找到,find方法会返回值-1

1
2
3
4
5
6
>>> s = "abc"
>>> s.find('a')
0
>>> s.find('d')
-1
>>>

1.5 列表

1.6 元组

与列表类似,元组也是由任意类型元素组成的序列。与列表不同的是,元组是不可改变的,这意味着一但元组被定义,将无法再进行增加、删除或者修改元素等操作。因此元组就像一个常量列表。从行为上看,元组(tuple)像是列表的只读版本。 但在内在实现上有根本不同,元组的只读性使其拥有更好的内存效率和性能。除无法修改外,其普通特征和列表类似。 在需要传递 “不可变” 参数时,应鼓励用元组替代列表。 它是可哈希(hashaable)结构,可用作字典(dict)主键(key)

使用()创建元组

可以用()创建一个空元组:

1
2
3
4
5
>>> empty_tuple = ()
>>> empty_tuple
()
>>> type(empty_tuple)
tuple

创建包含一个或多个元素的元组时,没一个元素后面需要跟着一个逗号,即使只包含一个元素也不能忽略:

1
2
3
4
>>> num = '1',
>>> num
('1',)
>>>

如果创建的元组所包含的元素数量超过1,最后一个元素后面的逗号可以忽略:

1
2
3
>>> num = '1', '2', '3'
>>> num
('1', '2', '3')

Python的交互式解释器输出元组时会自动添加一堆圆括号。你并不需要这么做——定义元组真正靠的是每个元素的后缀逗号——但如果你习惯添加一对括号也无可厚非。可以用括号将所有元素包裹起来,这会使得程序更加清晰:

1
2
3
>>> num = ('1', '2', '3')
>>> num
('1', '2', '3')

可以一口气将元组赋值给多个变量:

1
2
3
4
5
6
7
8
>>> a, b, c = num
>>> a
'1'
>>> b
'2'
>>> c
'3'
>>>

这个过程称为元组解包

可以利用元组在一条语句中对多个变量的值进行交换,而不需借助临时变量:

1
2
3
4
5
6
7
8
>>> a = 1
>>> b = 2
>>> a, b = b, a
>>> a
2
>>> b
1
>>>

tuple() 函数可以用其他类型的数据来创建元组:

1
2
3
4
>>> num = [1, 2, 3]
>>> tuple(num)
(1, 2, 3)
>>>

元组与列表

在许多地方都可以用元组代替列表,但元组的方法函数与类表相比要少一些——元组没有 append() 、insert(),等等——因为一但创建元组变无法修改。既然列表更加灵活那为什么不在所有地方都是用列表呢?原因如下:

  • 元组占用的空间小
  • 你不会意外修改元组的值
  • 可以将元组用作字典的键(详细的后面会介绍)
  • 命名元组可以作为对象的代替
  • 函数的参数是以元组形式是传递的

1.7 字典

1.8 集合

集合就像舍弃了值,仅剩下的字典一样。键与键之间也不允许重复。如果你仅仅想知道某一个元素是否存在而不关心其他的,使用集合是个非常好的选择。如果需要为键附加其他信息的话建议使用字典。

使用 set() 创建集合

可以使用set()函数创建一个集合,或者用大括号将一系列一都好分开的值包裹起来:

1
2
3
4
5
6
>>> empty_set = set()
>>> empty_set
set()
>>> num_set ={1,2,3,4,5}
>>> num_set
{1, 2, 3, 4, 5}

与字典一样,集合是无序的。

{} 创建的是一个空字典,这仅仅是因为字典出现的比较早抢占了花括号。

使用set()将其他类型转换为集合

你可以利用已有的列表、字符串、元组或字典的内容来创建集合,其中重复的值会被丢弃。

首先来试着转换一个包含重复字母的字符串:

1
2
>>> set('letters')
{'l', 'r', 'e', 't', 's'}

注意,上面得到的集合中仅含有一个 ‘e’ 和一个 ‘t’,尽管字符串 ‘letters’ 里各自包含两个。

再试试用列表建立集合:

1
2
>>> set(['one', 'two', 'three'])
{'one', 'two', 'three'}

再看下元组:

1
2
>>> set(('one', 'two', 'three'))
{'one', 'two', 'three'}

当字典作为参数传入set()函数时,只有键会被使用:

1
2
>>> set( {'apple': 'red', 'orange': 'orange', 'cherry': 'red'} )
{'cherry', 'orange', 'apple'}

使用in测试值是否存在

1
2
3
4
5
6
7
>>> num_set = {'one', 'two', 'three'}
>>> num_set
{'one', 'two', 'three'}
>>> 'one' in num_set
True
>>> 'four' in num_set
False

添加删除数据

1
2
3
4
5
6
7
>>> num_set = {'one', 'two', 'three'}
>>> num_set.add('four')
>>> num_set
{'one', 'two', 'four', 'three'}
>>> num_set.remove('one')
>>> num_set
{'two', 'four', 'three'}

交集和并集

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

1
2
3
4
5
6
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
set([2, 3])
>>> s1 | s2
set([1, 2, 3, 4])

2. 代码格式

2.1. 注释

在大多数编程语言中,注释都是一项很有用的功能。随着程序越来越大、越来越复杂,就应在其中添加说明,对你解决问题的方法进行大致的阐述。注释让你能够使用自然语言在程序中添加说明。注释是程序中会被Python解释器忽略的一段文本。通过使用注释,可以解释和明确Python代码的功能,记录将来要修改的地方,甚至写下你想写的东西。在Python中使用#字符标记注释,从#开始到当前行结束的部分都是注释。你可以把注释作为单独一行:

单行注释:

hello.py

1
2
# 向大家问好
print("Hello Python people!")

Python解释器将忽略第一行,只执行第二行.

1
print("Hello Python people!")

多行注释:

1
2
3
4
5
6
7
8
9
10
#coding=utf-8
"""这是"nester.py"模块,提供了一个名为print_lol的函数,这个函数的作用是打印列表,其中有可能包含(也可能不包含)嵌套列表。"""
def print_lol(the_list):
"""这个函数取一个位置参数,名为"the_list",这个可以是任何python列表(也可以是包含嵌套列表的列表)。所指定的列表中的每个数据项(递归地)输出到屏幕上,各数据项各占一行。"""
for each_item in the_list:
if isinstance(each_item, list):
print_lol(each_item)
else:
print(each_item)

该编写什么样的注释?

编写注释的主要目的是阐述代码要做什么,以及是如何做的。在开发项目期间,你对各个部分如何协同工作了如指掌,但过段时间后,有些细节你可能不记得了。当然,你总是可以通过研 究代码来确定各个部分的工作原理,但通过编写注释,以清晰的自然语言对解决方案进行概述, 可节省很多时间。
要成为专业程序员或与其他程序员合作,就必须编写有意义的注释。当前,大多数软件都是合作编写的,编写者可能是同一家公司的多名员工,也可能是众多致力于同一个开源项目的人员。 训练有素的程序员都希望代码中包含注释,因此你最好从现在开始就在程序中添加描述性注释。 作为新手,最值得养成的习惯之一是,在代码中编写清晰、简洁的注释。
如果不确定是否要编写注释,就问问自己,找到合理的解决方案前,是否考虑了多个解决方案。如果答案是肯定的,就编写注释对你的解决方案进行说明吧。相比回过头去再添加注释,删除多余的注释要容易得多。从现在开始,本书的示例都将使用注释来阐述代码的工作原理。

2.2 python 之禅

2.3 pep8

3. 表达式

3.1 控制流

if

elif

条件表达式(即”三元操作符”)

三元运算符可以只需要一行完成条件判断和赋值操作

1
data = x if x < y else y

while

while 是Python中的循环语句. 事实它上是一个条件循环语句. 与 if 声明相比, 如果 if 后的条件为真, 就会执行一次相应的代码块. 而 while 中的代码块会一直循环执行, 直到循环条件不再为真.

语法:

1
2
while expression:
suite_to_repeat

while 循环的 suite_to_repeat 子句会一直循环执行, 直到 expression 值为布尔假. 这种 类型的循环机制常常用在计数循环中。

1
2
3
4
count = 0
while (count < 9):
print('the index is:', count)
count += 1

无限循环

你必须小心地使用 while 循环, 因为有可能它的条件永远不会为布尔假. 这样一来循环就永远不会结束. 这些”无限”的循环不一定是坏事, 许多通讯服务器的客户端/服务器系统就是通过它来工作的. 这取决于循环是否需要一直执行下去, 如果不是, 那么这个循环是否会结束; 也就是说, 条件表达式会不会计算后得到布尔假?

1
2
3
4
while True:
handle, indata = wait_for_client_connect()
outdata = process_request(indata)
ack_result_to_client(handle, outdata)

例如上边的代码就是故意被设置为无限循环的,因为 True 无论如何都不会变成 False. 这是因为服务器代码是用来等待客户端(可能通过网络)来连接的. 这些客户端向服务器发送请求, 服务器处理请求.
请求被处理后, 服务器将向客户端返回数据, 而此时客户端可能断开连接或是发送另一个请求. 对于服务器而言它已经完成了对这个客户端的任务, 它会返回最外层循环等待下一个连接.

while使用 else 语句

在 python 中,for … else 表示这样的意思,for 中的语句和普通的没有区别,else 中的语句会在循环正常执行完(即 for 不是通过 break 跳出而中断的)的情况下执行,while … else 也是一样。

1
2
3
4
5
6
count = 0
while count < 5:
print count, " is less than 5"
count = count + 1
else:
print count, " is not less than 5"

结果:

1
2
3
4
5
6
0 is less than 5
1 is less than 5
2 is less than 5
3 is less than 5
4 is less than 5
5 is not less than 5

for

Python 提供给我们的另一个循环机制就是 for 语句. 它提供了 Python 中最强大的循环结构. 它可以遍历序列成员。

for 循环会访问一个可迭代对象(例如序列或是迭代器)中的所有元素, 并在所有条目都处理过后结束循环. 它的语法如下:

1
for iter_var in iterable: suite_to_repeat

每次循环, iter_var 迭代变量被设置为可迭代对象(序列, 迭代器, 或者是其他支持迭代的对象)的当前元素, 提供给 suite_to_repeat 语句块使用.

else

for 循环也可以有 else 用于循环后处理(post-processing). 它和 while 循环中的 else 处理方式相同. 只要 for 循环是正常结束的(不是通过 break ), else 子句就会执行.

1
2
3
4
5
6
7
8
9
10
s = ["a", "b", "c", "d", "e"]
found = False
for c in s:
if c.find("c") != -1:
found = True
print("发现c项")
break
if not found:
print("没有发现c项")
1
2
3
4
5
6
7
s = ["a", "b", "c", "d", "e"]
for c in s:
if c.find("c") != -1 :
print("发现c项")
break
else:
print("没有c项")

enumerate

1
2
3
4
5
6
7
8
>>> l = ['a', 'b', 'c']
>>>
>>> for index, item in enumerate(l):
... print(index, item)
...
0 a
1 b
2 c

range()

内建函数 range() 可以把类似 foreach 的 for 循环变成你更加熟悉的语句. 例如从 0 到 10 计数, 或者从 10 到 100 一次递增 5 .

1
range(start, end, step=1)
1
2
3
4
5
6
7
8
>>> range(5)
range(0, 5)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(0, 10, 2))
[0, 2, 4, 6, 8]

zip

定义:zip([iterable, …])

zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的list(列表)。若传入参数的长度不等,则返回list的长度和参数中长度最短的对象相同。利用*号操作符,可以将list unzip(解压),看下面的例子就明白了:

1
2
3
4
5
6
7
8
9
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b)
[(1, 4), (2, 5), (3, 6)]
>>> zip(a,c)
[(1, 4), (2, 5), (3, 6)]
>>> zip(*zipped)
[(1, 2, 3), (4, 5, 6)]

pass

空语句。

如果你在需要子语句块的地方不写任何语句, 解释器会提示你 语法错误. 因此, Python 提供了 pass 语句, 它不做任何事情 - 即 NOP , ( No OPeration , 无操作) 我们从汇编语言中借用这个概念. pass 同样也可作为开发中的小技巧, 标记你后来要完成的代码

break

Python 中的 break 语句可以结束当前循环然后跳转到下条语句, 类似 C 中的传统 break . 常用在当某个外部条件被触发(一般通过 if 语句检查), 需要立即从循环中退出时. break 语句可以用在 while 和 for 循环中.

continue

不管是 Python, C, Java 还是其它任何支持 continue 语句的结构化语言中, 一些初学者有这样的一个误解: continue 语句”立即启动循环的下一次迭代”. 实际上, 当遇到 continue 语句时, 程序会终止当前循环, 并忽略剩余的语句, 然后回到循环的顶端. 在开始下一次迭代前, 如果是条件循环, 我们将验证条件表达式. 如果是迭代循环, 我们将验证是否还有元素可以迭代. 只有在验证成功的情况下, 我们才会开始下一次迭代.

Python 里的 continue 语句和其他高级语言中的传统 continue 并没有什么不同. 它可以被用在 while 和 for 循环里. while 循环是条件性的, 而 for 循环是迭代的, 所以 continue 在开始下一次循环前要满足一些先决条件, 否则循环会正常结束.

1
2
3
4
5
6
7
8
>>> for s in 'hello':
... if s == 'l':
... continue
... print(s)
...
h
e
o

推导式

推导式是从一个或者多个迭代器快速简洁地创建数据结构的一种方法。他可以讲循环和条件判断结合,从而避免语法冗长的代码。会使用推导式有时可以说明你已经超过 Python初学者的水平。也就是说,使用推导式更像 Python 风格

列表推导式

你可以从1到5创建一个整数列表,每次增加一项:

1
2
3
4
5
6
7
8
>>> number_list = []
>>> number_list.append(1)
>>> number_list.append(2)
>>> number_list.append(3)
>>> number_list.append(4)
>>> number_list.append(5)
>>> number_list
[1, 2, 3, 4, 5]

或者,可以结合 range() 函数使用一个迭代器:

1
2
3
4
5
6
>>> number_list = []
>>> for number in range(1, 6):
... number_list.append(number)
...
>>> number_list
[1, 2, 3, 4, 5]

上面这些方法都是可行的Python代码,会得到相同的结果。然而,更像 Python 风格的创建列表方法是列表推导。语法如下:

1
[ expression for item in iterable ]

将通过列表推导创建一个整数列表:

1
2
3
>>> number_list = [number for number in range(1,6)]
>>> number_list
[1, 2, 3, 4, 5]

在第一行中,第一个 number 变量为列表生成值,也就是说,把循环的结果放在列表 number_list 中。 第二个 number 可以为表达式, 看下下面的例子:

1
2
3
>>> number_list = [number-1 for number in range(1,6)]
>>> number_list
[0, 1, 2, 3, 4]

列表推到把循环放在方括号内部。这种例子和之前碰到的不大一样,但却是更为常见的方式。同样,列表推导也可以像下面的例子加上条件表达式:

1
[expression for item in iterable if condition]

现在,通过推导创建一个在1到5之间的偶数列表(当 number % 2 为真时,代表奇数;为假时代表偶数):

1
2
3
>>> a_list = [number for number in range(1,6) if number % 2 == 1]
>>> a_list
[1, 3, 5]

正如很多嵌套循环一样,在对应的推导中会有多个for语句,我们先来看一个简单的嵌套循环例子:

1
2
3
4
5
6
7
8
9
10
11
12
>>> rows = range(1,4)
>>> cols = range(1,3)
... for row in rows:
... for col in cols:
... print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2

使用一次推导,将结果赋值给变量 cells,使 row,col 成为元组:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> for cell in cells:
... print(cell)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
>>>

另外,在对 cells 列表进行迭代时可以通过元组拆封将变量 row 和 col 的值分别取出:

1
2
3
4
5
6
7
8
9
>>> for row, col in cells:
... print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2

其中,列表推导中 for row …和 for col …都可以有自己单独的 if 条件判断。

1
2
3
4
5
6
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows if row % 2 == 1 for col in cols if col % 2 == 1]
>>> cells
[(1, 1), (3, 1)]
>>>

3.2 id is ==

Python中的对象包含三要素:id、type、value
其中id返回一个对象的唯一标识,type标识对象的类型,value是对象的值
is判断的是a对象是否就是b对象,是通过id来判断的
==判断的是a对象的值是否和b对象的值相等,是通过value来判断的
如下代码或许可以帮助你理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> a = 1
>>> b = 1.0
>>> a is b
False
>>> a == b
True
>>> id(a)
12777000
>>> id(b)
14986000
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a == b
True
>>> id(a)
12777000
>>> id(b)
12777000

就 CPython 而言,id 返回的就是运行期内存地址。因此这个标识属阶段性的,不能保证不被重复使用。 但对于其他实现来说,id 返回的未必就是内存地址。

字典推导式

集合推导式

集合也不例外,同样有推导式。最简单的版本和之前的列表、字典推导类似:

1
{expression for expression in iterable }

也可以使用条件判断:

1
2
3
>>> a_set = {number for number in range(1,6) if number % 3 == 1}
>>> a_set
{1, 4}

4. 函数

代码复用的第一步是使用函数,它是命名的用于区分的代码段。函数可以接受任何数字或者其他类型的输入作为参数,并且返回数字或者其他类型的结果。

你可以使用函数做一下两件事情:

  • 定义函数
  • 调用函数

定义:

语句 def 在运行期创建函数对象,并与指定名字关联。

1
2
def func_name():
pass # 写入你的逻辑

参数

传入到函数的值称为参数。当调用含参数的函数时,这些参数的值会被复制给函数中的对应参数。

1
2
3
4
5
6
7
8
9
10
>>> def get_name(num):
... if num == 1:
... return '老大'
... elif num == 2:
... return '老二'
...
...
>>> name = get_name(1)
>>> name
'老大'

这个函数做了如下事情:

  • 把 1 赋值给函数的内部参数 num
  • 运行 if-elif 的逻辑链
  • 返回一个字符串
  • 将该字符串赋值给变量 name

一个函数可以接受任何数量(包括0)的任何类型的值作为输入变量,并且返回任何数量(包括0)的任何类型的结果。如果函数不显示调用 return 函数,那么会默认返回 None。

1
2
3
4
5
6
>>> def func_name():
... pass
...
>>> print(func_name())
None
>>>

None

None 是 Python 中一个特殊的值,虽然它不表示任何数据,但仍然具有重要的作用。 虽然 None 作为布尔值和 False 是一样的,但是它和 False 有很多差别。下面是一个例子:

1
2
3
4
5
6
7
>>> thing = None
>>> if thing:
... print("It's some thing")
... else:
... print("It's no thing")
...
It's no thing

为了区分 None 和布尔值 False , 使用 Python 的 is 操作符:

1
2
3
4
5
6
>>> if thing is None:
... print("It's nothing")
... else:
... print("It's something")
...
It's nothing

这虽然是一个微妙的区别,但是对于 Python 来说是很重要的。你需要把 None 和不含 任何值的空数据结构区分开来。0 值的整型 / 浮点型、空字符串(‘’)、空列表([])、 空元组((,))、空字典({})、空集合(set())都等价于 False,但是不等于 None。
现在,快速写一个函数,输出它的参数是否是 None:

1
2
3
4
5
6
7
>>> def is_none(thing):
... if thing is None:
... print("It's None")
... elif thing:
... print("It's True")
... else:
... print("It's False")

现在,运行一些测试函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> is_none(None)
It's None
>>> is_none(True)
It's True
>>> is_none(False)
It's False
>>> is_none(0)
It's False
>>> is_none(0.0)
It's False
>>> is_none(())
It's False
>>> is_none([])
It's False
>>> is_none({})
It's False
>>> is_none(set())
It's False

位置参数

Python 处理参数的方式要比其他语言更加灵活。其中,最熟悉的参数类型是位置参数,传入参数的值是按照顺序依次复制过去的。

1
2
3
4
5
6
7
8
9
10
>>> def name (n1, n2, n3):
... print('1', n1)
... print('2', n2)
... print('3', n3)
...
>>> name('老大', '老二', '老三')
1 老大
2 老二
3 老三
>>>

尽管这种方式很常见,但是位置参数的一个弊端是必须熟记没个位置的参数的含义。在调用函数name() 时误把最后一个参数当做第一个参数,会得到完全不同的结果:

1
2
3
4
5
>>> name('老二', '老大', '老三')
1 老二
2 老大
3 老三
>>>

关键字参数

为了避免位置参数带来的混乱,调用参数时可以指定对应的名字,甚至可以采用与函数定义不同的顺序调用:

1
2
3
4
5
>>> name(n2='老二', n1='老大', n3='老三')
1 老大
2 老二
3 老三
>>>

你也可以把位置参数和关键字参数混合起来。

1
2
3
4
>>> name('老大', n2='老二', n3='老三')
1 老大
2 老二
3 老三

如果同时出现两种参数形式,首先应该考虑的是位置参数。

1
2
3
4
5
>>> name(n2='老二', '老大', n3='老三')
File "<ipython-input-60-2a320a67eb2a>", line 1
name(n2='老二', '老大', n3='老三')
^
SyntaxError: positional argument follows keyword argument

指定默认参数

当调用方没有提供对应的参数值时,你可以指定默认参数值。

1
2
3
4
5
6
7
8
9
10
11
>>> def name (n2, n3, n1='老大'):
... print('1', n1)
... print('2', n2)
... print('3', n3)
...
...
>>> name('老二', '老三')
1 老大
2 老二
3 老三
>>>

默认参数值在函数被定义时已经计算出来,而不是在程序运行时。Python程序员经常犯的一个错误是把可变的数据类型(例如列表或者字典)当做默认参数值。

在函数 box() 在每次调用时,添加参数 arg 到一个空的列表 result, 然后打印输出一个单值列表。但存在一个问题:只有在第一次调用时列表是空的,第二次调用时就会存在之前调用的返回值:

1
2
3
4
5
6
7
8
>>> def box(arg, result=[]):
... result.append(arg)
... print(result)
...
>>> box('a')
['a']
>>> box('b')
['a', 'b']

如果写成下面的样子就会解决刚才的问题:

1
2
3
4
5
6
7
8
9
>>> def box(arg):
... result = []
... result.append(arg)
... return result
...
>>> box('a')
['a']
>>> box('b')
['b']

这样的修改也是为了表明第一次调用跳过一些操作:

1
2
3
4
5
6
7
8
9
10
>>> def box(arg, result=None):
... if result is None:
... result = []
... result.append(arg)
... print(result)
...
>>> box('a')
['a']
>>> box('b')
['b']

5. 类

6. 文件异常

7. 模块

操作系统:mac OSX 10.12 或 centos 7
编辑器: vim、 sublime、atom
要求会翻墙

准备工具

centos:
http://mirror.bit.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1611.iso

virtualbox:
https://www.virtualbox.org/wiki/Downloads

sublime:
http://www.sublimetext.com/3

SecureCRT:
http://www.xdowns.com/soft/softdown.asp?softid=23625

操作系统

下载 Centos
http://mirror.bit.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1611.iso

虚拟机

如果不想直接安装Centos,建议使用虚拟机安装

virtualbox

https://www.virtualbox.org/wiki/Downloads
下载完后安装

安装增强功能:

在【设备】中选择安装【安装增强功能】,由于我们安装的是 minimal 最小化的centos,所以我们需要安装一些库来支持插件的安装

1
2
3
yum update # 更新软件
yum -y install gcc kernel-devel kenel-headers make bzip2 # 安装依赖库
reboot # 重启

挂载执行脚本

1
2
3
mount /dev/cdrom /mnt # 挂载光驱到 mnt 目录下
cd /mnt # 进入到mnt目录
sh ./VBoxLinuxAdditions.run # 执行脚本,进行安装

安装完成后使用 reboot 重启,就已经生效

然后对现有的环境做一次快照,以便日后恢复。

python环境

版本: python3.5.2

centos os 默认使用的是 Python 2.7.5 , 我们需要使用 python3 走在时尚的最前沿,所以需要在系统中安装多个Python,但是又不能影响系统自带的 Python(比如yum 等等系统中好多程序是要依赖于系统本身的python的)。pyenv就是一个Ptyhon版本管理工具

安装工具

###1. pyenv

安装 pyenv

linux:

centos 的配置

1
2
3
4
5
6
$ yum install readline readline-devel readline-static -y
$ yum install openssl openssl-devel openssl-static -y
$ yum install sqlite-devel -y
$ yum install bzip2-devel bzip2-libs -y
$ yum install patch vim git

给系统 python 安装 pip

1
2
3
$ yum -y install epel-release # 安装 epel 扩展源
$ yum -y install python-pip
$ yum clean all # 清除 cache

系统 python 安装 virtualenvwrapper (为了做pyenv切换多个虚拟环境做兼容)

1
2
3
4
$ pip install virtualenvwrapper
$ vim ~/.bashrc
export WORKON_HOME=$HOME/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh

pyenv 安装

1
2
3
4
5
$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"'>> ~/.bashrc # 指明环境变量
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"'>> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc # 开启shims and autocompletion
$ exec $SHELL -l # 重新启动shell让其生效

mac:

1
2
3
4
5
6
7
8
9
10
11
12
$ brew update
$ brew install pyenv //安装
$ brew upgrade pyenv //升级
$ echo 'export PYENV_ROOT="$HOME/.pyenv"'>> ~/.bash_profile # 指明环境变量
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"'>> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile //只需要执行一次即可
$ vim ~/.bash_profile
if [[ -r /usr/local/bin/virtualenvwrapper.sh ]]; then
source /usr/local/bin/virtualenvwrapper.sh
else
echo "WARNING: Can't find virtualenvwrapper.sh"
fi

或者使用 pyenv-installer 脚本进行安装

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.2 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
$ pyenv versions
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.2

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.2
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

2. virtualenvwrapper

linux

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

mac

1
2
$ pip install virtualenvwrapper
$ brew install pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
3
4
$ mkvirtualenv env352 -p $(which python3.5) # 基于 python3 创建 env352虚拟环境
$ workon env352 # 切换到 env352 环境 (开发时使用)
$ pip install virtualenvwrapper # 为了兼容 pyenv 多虚拟环境
$ pyenv global system # 默认全局的 python 可以切换到 系统的python

项目地址:https://github.com/go-redis/redis

#寻找入口

寻找入库,一般从 Quickstart 会给我们很多线索

1
2
3
4
5
6
7
8
9
10
11
func ExampleNewClient() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
pong, err := client.Ping().Result()
fmt.Println(pong, err)
// Output: PONG <nil>
}

我们看到 创建client的时候调用 redis.NewClient 方法,我们不妨寻找一下这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ git clone https://github.com/go-redis/redis.git
$ cd redis
$ grep -r -n "NewClient" *.go [10:17:37]
bench_test.go:12: client := redis.NewClient(&redis.Options{
cluster.go:239: Client: NewClient(opt),
cluster_test.go:64: client := redis.NewClient(&redis.Options{
command_test.go:14: client = redis.NewClient(redisOptions())
commands_test.go:19: client = redis.NewClient(redisOptions())
example_test.go:15: client = redis.NewClient(&redis.Options{
example_test.go:26:func ExampleNewClient() {
example_test.go:27: client := redis.NewClient(&redis.Options{
iterator_test.go:47: client = redis.NewClient(redisOptions())
main_test.go:204: client := redis.NewClient(&redis.Options{
pipeline_test.go:17: client = redis.NewClient(redisOptions())
pool_test.go:17: client = redis.NewClient(redisOptions())
pubsub_test.go:19: client = redis.NewClient(redisOptions())
race_test.go:23: client = redis.NewClient(redisOptions())
race_test.go:114: client = redis.NewClient(redisOptions())
race_test.go:175: client := redis.NewClient(opt)
race_test.go:198: client := redis.NewClient(opt)
redis.go:167:// NewClient returns a client to the Redis Server specified by Options.
redis.go:168:func NewClient(opt *Options) *Client {
redis_test.go:17: client = redis.NewClient(redisOptions())
redis_test.go:40: custom := redis.NewClient(&redis.Options{
redis_test.go:109: db2 := redis.NewClient(&redis.Options{
redis_test.go:141: client = redis.NewClient(&redis.Options{
redis_test.go:195: client = redis.NewClient(redisOptions())
ring.go:157: ring.addClient(name, NewClient(clopt))
tx_test.go:17: client = redis.NewClient(redisOptions())

我们可以看到 redis.go 的168行有 NewClient 函数的定义

1
redis.go:168:func NewClient(opt *Options) *Client {

这样我们找到了一个入口

绘制项目地图

我们找查看一个项目,绘制这个项目的地图很重要

通过 redis.go 168行我们可以看到,输入的参数 Options 和 返回值 Client

我们看下 Optins 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Options struct {
Network string
Addr string
Dialer func() (net.Conn, error)
Password string
DB int
MaxRetries int
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
PoolSize int
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
ReadOnly bool
}

都是一些连接是用的参数,往下看有初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func (opt *Options) init() {
if opt.Network == "" {
opt.Network = "tcp"
}
if opt.Dialer == nil {
opt.Dialer = func() (net.Conn, error) {
return net.DialTimeout(opt.Network, opt.Addr, opt.DialTimeout)
}
}
if opt.PoolSize == 0 {
opt.PoolSize = 10
}
if opt.DialTimeout == 0 {
opt.DialTimeout = 5 * time.Second
}
if opt.ReadTimeout == 0 {
opt.ReadTimeout = 3 * time.Second
}
if opt.WriteTimeout == 0 {
opt.WriteTimeout = opt.ReadTimeout
}
if opt.PoolTimeout == 0 {
opt.PoolTimeout = opt.ReadTimeout + time.Second
}
if opt.IdleTimeout == 0 {
opt.IdleTimeout = 5 * time.Minute
}
if opt.IdleCheckFrequency == 0 {
opt.IdleCheckFrequency = time.Minute
}
}

对于参数提供了默认值

然后我们看一下 Clinet 的定义, redis.go 151行 :

1
2
3
4
5
6
type Client struct {
baseClient
cmdable
}
var _ Cmdable = (*Client)(nil)

Client 是由 baseClient 和 cmdable 组合而成。 从命名上来看 baseClient是保存一些基础不变的东西,cmdable可能是负责命令。

1
var _ Cmdable = (*Client)(nil)

这行的意思 Client 要实现 Cmdable 这样的接口,要不然编译器会报错。 从 Client的结构上看只有 cmdble才能实现这样的一个接口

我们看下 Cmdable 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
type Cmdable interface {
Pipeline() *Pipeline
Pipelined(fn func(*Pipeline) error) ([]Cmder, error)
Echo(message interface{}) *StringCmd
Ping() *StatusCmd
Quit() *StatusCmd
Del(keys ...string) *IntCmd
Dump(key string) *StringCmd
Exists(key string) *BoolCmd
Expire(key string, expiration time.Duration) *BoolCmd
ExpireAt(key string, tm time.Time) *BoolCmd
Keys(pattern string) *StringSliceCmd
Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd
Move(key string, db int64) *BoolCmd
ObjectRefCount(keys ...string) *IntCmd
ObjectEncoding(keys ...string) *StringCmd
ObjectIdleTime(keys ...string) *DurationCmd
Persist(key string) *BoolCmd
PExpire(key string, expiration time.Duration) *BoolCmd
PExpireAt(key string, tm time.Time) *BoolCmd
PTTL(key string) *DurationCmd
RandomKey() *StringCmd
Rename(key, newkey string) *StatusCmd
RenameNX(key, newkey string) *BoolCmd
Restore(key string, ttl time.Duration, value string) *StatusCmd
RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd
Sort(key string, sort Sort) *StringSliceCmd
SortInterfaces(key string, sort Sort) *SliceCmd
TTL(key string) *DurationCmd
Type(key string) *StatusCmd
Scan(cursor uint64, match string, count int64) Scanner
SScan(key string, cursor uint64, match string, count int64) Scanner
HScan(key string, cursor uint64, match string, count int64) Scanner
ZScan(key string, cursor uint64, match string, count int64) Scanner
Append(key, value string) *IntCmd
BitCount(key string, bitCount *BitCount) *IntCmd
BitOpAnd(destKey string, keys ...string) *IntCmd
BitOpOr(destKey string, keys ...string) *IntCmd
BitOpXor(destKey string, keys ...string) *IntCmd
BitOpNot(destKey string, key string) *IntCmd
BitPos(key string, bit int64, pos ...int64) *IntCmd
Decr(key string) *IntCmd
DecrBy(key string, decrement int64) *IntCmd
Get(key string) *StringCmd
GetBit(key string, offset int64) *IntCmd
GetRange(key string, start, end int64) *StringCmd
GetSet(key string, value interface{}) *StringCmd
Incr(key string) *IntCmd
IncrBy(key string, value int64) *IntCmd
IncrByFloat(key string, value float64) *FloatCmd
MGet(keys ...string) *SliceCmd
MSet(pairs ...interface{}) *StatusCmd
MSetNX(pairs ...interface{}) *BoolCmd
Set(key string, value interface{}, expiration time.Duration) *StatusCmd
SetBit(key string, offset int64, value int) *IntCmd
SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd
SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd
SetRange(key string, offset int64, value string) *IntCmd
StrLen(key string) *IntCmd
HDel(key string, fields ...string) *IntCmd
HExists(key, field string) *BoolCmd
HGet(key, field string) *StringCmd
HGetAll(key string) *StringStringMapCmd
HIncrBy(key, field string, incr int64) *IntCmd
HIncrByFloat(key, field string, incr float64) *FloatCmd
HKeys(key string) *StringSliceCmd
HLen(key string) *IntCmd
HMGet(key string, fields ...string) *SliceCmd
HMSet(key string, fields map[string]string) *StatusCmd
HSet(key, field, value string) *BoolCmd
HSetNX(key, field, value string) *BoolCmd
HVals(key string) *StringSliceCmd
BLPop(timeout time.Duration, keys ...string) *StringSliceCmd
BRPop(timeout time.Duration, keys ...string) *StringSliceCmd
BRPopLPush(source, destination string, timeout time.Duration) *StringCmd
LIndex(key string, index int64) *StringCmd
LInsert(key, op string, pivot, value interface{}) *IntCmd
LInsertBefore(key string, pivot, value interface{}) *IntCmd
LInsertAfter(key string, pivot, value interface{}) *IntCmd
LLen(key string) *IntCmd
LPop(key string) *StringCmd
LPush(key string, values ...interface{}) *IntCmd
LPushX(key string, value interface{}) *IntCmd
LRange(key string, start, stop int64) *StringSliceCmd
LRem(key string, count int64, value interface{}) *IntCmd
LSet(key string, index int64, value interface{}) *StatusCmd
LTrim(key string, start, stop int64) *StatusCmd
RPop(key string) *StringCmd
RPopLPush(source, destination string) *StringCmd
RPush(key string, values ...interface{}) *IntCmd
RPushX(key string, value interface{}) *IntCmd
SAdd(key string, members ...interface{}) *IntCmd
SCard(key string) *IntCmd
SDiff(keys ...string) *StringSliceCmd
SDiffStore(destination string, keys ...string) *IntCmd
SInter(keys ...string) *StringSliceCmd
SInterStore(destination string, keys ...string) *IntCmd
SIsMember(key string, member interface{}) *BoolCmd
SMembers(key string) *StringSliceCmd
SMove(source, destination string, member interface{}) *BoolCmd
SPop(key string) *StringCmd
SPopN(key string, count int64) *StringSliceCmd
SRandMember(key string) *StringCmd
SRandMemberN(key string, count int64) *StringSliceCmd
SRem(key string, members ...interface{}) *IntCmd
SUnion(keys ...string) *StringSliceCmd
SUnionStore(destination string, keys ...string) *IntCmd
ZAdd(key string, members ...Z) *IntCmd
ZAddNX(key string, members ...Z) *IntCmd
ZAddXX(key string, members ...Z) *IntCmd
ZAddCh(key string, members ...Z) *IntCmd
ZAddNXCh(key string, members ...Z) *IntCmd
ZAddXXCh(key string, members ...Z) *IntCmd
ZIncr(key string, member Z) *FloatCmd
ZIncrNX(key string, member Z) *FloatCmd
ZIncrXX(key string, member Z) *FloatCmd
ZCard(key string) *IntCmd
ZCount(key, min, max string) *IntCmd
ZIncrBy(key string, increment float64, member string) *FloatCmd
ZInterStore(destination string, store ZStore, keys ...string) *IntCmd
ZRange(key string, start, stop int64) *StringSliceCmd
ZRangeWithScores(key string, start, stop int64) *ZSliceCmd
ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
ZRank(key, member string) *IntCmd
ZRem(key string, members ...interface{}) *IntCmd
ZRemRangeByRank(key string, start, stop int64) *IntCmd
ZRemRangeByScore(key, min, max string) *IntCmd
ZRevRange(key string, start, stop int64) *StringSliceCmd
ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd
ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
ZRevRank(key, member string) *IntCmd
ZScore(key, member string) *FloatCmd
ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd
PFAdd(key string, els ...interface{}) *IntCmd
PFCount(keys ...string) *IntCmd
PFMerge(dest string, keys ...string) *StatusCmd
BgRewriteAOF() *StatusCmd
BgSave() *StatusCmd
ClientKill(ipPort string) *StatusCmd
ClientList() *StringCmd
ClientPause(dur time.Duration) *BoolCmd
ClientSetName(name string) *BoolCmd
ConfigGet(parameter string) *SliceCmd
ConfigResetStat() *StatusCmd
ConfigSet(parameter, value string) *StatusCmd
DbSize() *IntCmd
FlushAll() *StatusCmd
FlushDb() *StatusCmd
Info(section ...string) *StringCmd
LastSave() *IntCmd
Save() *StatusCmd
Shutdown() *StatusCmd
ShutdownSave() *StatusCmd
ShutdownNoSave() *StatusCmd
SlaveOf(host, port string) *StatusCmd
Time() *TimeCmd
Eval(script string, keys []string, args ...interface{}) *Cmd
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
ScriptExists(scripts ...string) *BoolSliceCmd
ScriptFlush() *StatusCmd
ScriptKill() *StatusCmd
ScriptLoad(script string) *StringCmd
DebugObject(key string) *StringCmd
PubSubChannels(pattern string) *StringSliceCmd
PubSubNumSub(channels ...string) *StringIntMapCmd
PubSubNumPat() *IntCmd
ClusterSlots() *ClusterSlotsCmd
ClusterNodes() *StringCmd
ClusterMeet(host, port string) *StatusCmd
ClusterForget(nodeID string) *StatusCmd
ClusterReplicate(nodeID string) *StatusCmd
ClusterResetSoft() *StatusCmd
ClusterResetHard() *StatusCmd
ClusterInfo() *StringCmd
ClusterKeySlot(key string) *IntCmd
ClusterCountFailureReports(nodeID string) *IntCmd
ClusterCountKeysInSlot(slot int) *IntCmd
ClusterDelSlots(slots ...int) *StatusCmd
ClusterDelSlotsRange(min, max int) *StatusCmd
ClusterSaveConfig() *StatusCmd
ClusterSlaves(nodeID string) *StringSliceCmd
ClusterFailover() *StatusCmd
ClusterAddSlots(slots ...int) *StatusCmd
ClusterAddSlotsRange(min, max int) *StatusCmd
GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd
GeoPos(key string, members ...string) *GeoPosCmd
GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd
GeoDist(key string, member1, member2, unit string) *FloatCmd
GeoHash(key string, members ...string) *StringSliceCmd
Command() *CommandsInfoCmd
}

我们看到了 Cmdable 这个接口其实就是实现了 redis 命令的封装。

我们接着看 baseClient

1
2
3
4
5
6
type baseClient struct {
connPool pool.Pooler
opt *Options
onClose func() error // hook called when client is closed
}

baseClient 引用了连接池,

我们接着往下看 redis.go 这个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func (c *baseClient) Process(cmd Cmder) error {
for i := 0; i <= c.opt.MaxRetries; i++ {
if i > 0 {
cmd.reset()
}
cn, _, err := c.conn()
if err != nil {
cmd.setErr(err)
return err
}
readTimeout := cmd.readTimeout()
if readTimeout != nil {
cn.ReadTimeout = *readTimeout
} else {
cn.ReadTimeout = c.opt.ReadTimeout
}
cn.WriteTimeout = c.opt.WriteTimeout
if err := writeCmd(cn, cmd); err != nil {
c.putConn(cn, err, false)
cmd.setErr(err)
if err != nil && internal.IsRetryableError(err) {
continue
}
return err
}
err = cmd.readReply(cn)
c.putConn(cn, err, readTimeout != nil)
if err != nil && internal.IsRetryableError(err) {
continue
}
return err
}
return cmd.Err()
}

发现 baseClient 有个 Process 方法 这个应该是处理执行的过程 需要我们注意。

根据广度优先原则,我们再看 Client 上还有什么关联的内容

我们可以看到 redis.go 这个文件里面 还包含了 Pipeline 和 pubSub 两个函数

1
2
3
4
5
func (c *Client) Pipeline() *Pipeline {
}
func (c *Client) pubSub() *PubSub {
}

然后我们再看下,项目文件,还有什么我们并没有涉及到的

1
2
3
4
5
6
7
$ ls
CHANGELOG.md cluster_client_test.go doc.go main_test.go pubsub.go ring.go tx.go
LICENSE cluster_test.go example_test.go options.go pubsub_test.go ring_test.go tx_test.go
Makefile command.go export_test.go parser.go race_test.go script.go
README.md command_test.go internal/ pipeline.go redis.go sentinel.go
bench_test.go commands.go iterator.go pipeline_test.go redis_test.go sentinel_test.go
cluster.go commands_test.go iterator_test.go pool_test.go result.go testdata/

答案就是 cluster,cluster 是连接 redis 集群的方式,会提供给用户使用

我们详细看一下 cluster 源码,cluster 里面有自己的 Options,值得注意的是,有 Node 逻辑

1
2
3
4
5
type clusterNode struct {
Client *Client
Latency time.Duration
loading time.Time
}

使用组合的方式把 Client 包装进来。

1
2

在web开发过程中,有些时候需要隐藏你的静态文件url,也有些时候需要频繁更替你的静态资源服务商,如果你的静态资源服务商(七牛、又拍云)发生了变更,然后你又要修改对外暴漏的url是非常不方便的。这时候我们使用一个统一的自己能控制的对外url对于我们非常方便,也算是变相的对外解耦,是个非常有用的办法。

基于以上的想法,我们来进行技术选型,由于现在大家写web都脱离不了nginx,所以就想在nginx层面上解决这个问题。不出所料,nginx 里的 x-accell-redirect 就可以实现此功能。下面我们使用nginx和django来实现此功能。

配置nginx

修改 nginx.conf 新增 location

1
2
3
4
5
6
location ~* ^/protected/ {
internal; # 只允许内部重定向
rewrite ^/protected/(.*?)\|(.*) /$2 break;
proxy_set_header Authorization $1; # 鉴权的东西放到header中 如果没有鉴权可以去掉,$1的值是服务端传过来的,详见后面
proxy_pass http://images.com; # 你的静态文件地址
}

保存,reload 。

django view

这里面使用了 django-rest-framework 来进行API的开发

1
2
3
4
5
6
7
8
class NginxAccel(APIView):
def get(self, request, img_name):
auth = image_auth() # 类似私有空间的验证
response = Response()
response["X-Accel-Redirect"] = "/protected/{0}|{1}".format(
auth, img_name)
return response

然后通过url来访问,鉴权成功,就可以访问到你要的图片。如果不需要鉴权,渠道auth的相关代码就可以。


现在开发环境原来越复杂,为了方便开发,让团队每个人的环境一致,最近使用docker进行打包image,发放给团队使用。

#安装docker
本人使用mac,直接下载docker for mac 方便很多,其他os网上一搜一大把。so easy~!

下载images

1
2
3
4
5
6
[docker@docker-root ~]$ sudo docker pull alpine
[docker@docker-root ~]$ sudo docker pull ubuntu
[docker@docker-root ~]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
alpine latest 3e467a6273a3 2 days ago 4.793 MB
ubuntu latest 17b6a9e179d7 5 days ago 120.7 MB

启动

1
docker run -ti -h dev --net=host -v ~/workspace/sohu:/root/workspace -w /root develop:base /bin/bash

把本地目录 ~/workspace/sohu 映射到容器 /root/workspace 目录

自定义images

先更新下源,安装几个必备工具

1
2
apt-get update
apt-get install vim curl git wget

修改阿里云源

修改成阿里云源,加快安装软件的速度

1
2
3
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak #备份
sudo vim /etc/apt/sources.list #修改
sudo apt-get update #更新列表

阿里云源

1
2
3
4
5
6
7
8
9
10
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse

python开发环境

安装工具和必备依赖

1
2
3
4
5
6
7
apt-get install gcc gdb binutils make git dstat sysstat htop curl wget
apt-get install libjpeg-dev
apt-get install net-tools
apt-get install libffi-dev
apt-get install bzip2
apt-get install libssl
apt-get install libssl-dev

如需要sqlit支持需要先装如下库,再安装python:

1
sudo apt-get install sqlite3 libsqlite3-dev

pyenv 安装

1
$ curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash

在 ~/.bashrc 中添加

1
2
3
export PATH="/root/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.2 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
4
$ pyenv versions
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.1
sohu351

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.1
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

安装virtualenv

1
2
$ pyenv global system 切换到系统python
$ pip install virtualenv

安装virtualenvwrapper

安装 virtualenvwrapper 并让pyenv支持

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
3
$ pyenv global 3.5.2
$ mkvirtualenv env2
$ workon env2

Go开发环境

编译go1.3需要的参数

1
CGO_ENABLED=0 ./make.bash

ToDo……

image 字符集修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ export LANG="en_US.UTF-8"
$ sudo locale-gen "en_US.UTF-8"
Generating locales...
en_US.UTF-8... done
Generation complete.
$ sudo dpkg-reconfigure locales
Generating locales...
en_US.UTF-8... up-to-date
Generation complete.
$ locale charmap
UTF-8

syslog

如果使用syslog,需要在启动的时候做一次目录映射

1
-v /dev/log:/dev/log

保存images

1
2
3
$ docker ps [17:06:10]
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
111be6691cc8 c5940ba1089c "/bin/bash" 4 days ago Up 9 hours dev

把 image 中 c5940ba1089c 这个字段记住

1
docker commit c5940ba1089c develop:base #进行保存

进行查看

1
2
3
4
5
6
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
develop base 5a893de95205 40 hours ago 1.87 GB
ubuntu latest 42118e3df429 9 weeks ago 124.8 MB
alpine latest 4e38e38c8ce0 3 months ago 4.799 MB
hello-world latest 690ed74de00f 11 months ago 960 B

私有仓库

查看如下文章 :

创建私有仓库

操作系统:mac OSX 10.11 或 Ubuntu 16.04
编辑器: vim、 sublime、PyCharm

python环境

版本: python3.5.1

安装工具

###1. pyenv

安装 pyenv

linux:

1
2
3
4
5
$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"'>> ~/.bashrc # 指明环境变量
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"'>> ~/.bashrc
$ echo 'eval"$(pyenv init -)"' >> ~/.bashrc # 开启shims and autocompletion
$ exec $SHELL -l # 重新启动shell让其生效

mac:

1
2
3
4
$ brew update
$ brew install pyenv //安装
$ brew upgrade pyenv //升级
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile //只需要执行一次即可

查看可安装的版本

1
$ pyenv install --list

安装指定版本

1
$ pyenv install 3.5.1 -v

更新数据库

1
$ pyenv rehash

查看当前已安装的python版本

1
2
3
4
$ pyenv versions
* system (set by /Users/ce/workspace/sohu/.python-version)
3.5.1
sohu351

设置全局的python版本

1
2
3
4
$ pyenv global 3.5.1
$ pyenv versions
system
* 3.5.1 (set by /Users/ce/workspace/sohu/.python-version)

2. virtualenvwrapper

linux

1
2
$ pip install virtualenvwrapper
$ git clone https://github.com/yyuu/pyenv-virtualenvwrapper.git ~/.pyenv/plugins/pyenv-virtualenvwrapper

mac

1
2
$ pip install virtualenvwrapper
$ brew install pyenv-virtualenvwrapper

使用python3.5创建一个虚拟环境

1
2
$ mkvirtualenv env2 -p $(which python3.5)
$ workon env2

direnv

安装

1
2
3
git clone https://github.com/direnv/direnv
cd direnv
make install

配置

设置全局配置文件

vim .direnvrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use_venv () {
export VIRTUAL_ENV = "${HOME} /.virtualenvs/${1}"
PATH_add "$VIRTUAL_ENV/bin"
}
use_vwrapper () {
source /usr/local/bin/virtualenvwrapper.sh
}
use_python() {
local python_root=$HOME/.pyenv/versions/$1
load_prefix "$python_root"
layout_python "$python_root/bin/python"
}

在项目中创建.envrc文件

1
2
3
layout python
use vwrapper
workon env2

https://github.com/direnv/direnv/wiki/Python