docker 配置部分

拉取 alpine

1
$ alpine docker pull alpine

查看当前机 下的 docker 镜像

1
$ docker images

创建容

1
2
$ docker create -ti --name python -h python -p 8080:8080 -w /root alpine sh
``` 启动容

$ docker start python

1
2

进入容器

$ docker attach python

1


停止容器

$ docker stop python

1

删除容器

$ docker rm python

1
2
3
4

# alpine 配置

更改下alpine镜像的源

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

1
2


apk update

1
2

安装常用工具

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

1
2

启动bash

$ bash

1
2

更新pip

$ pip3 install -U pip

1
2

查看 pip 版本号

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
显示了全局的系统库路径

# Python 项目管理思路

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

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

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

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

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

# 安装虚拟环境

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

pip install -U pbr

1
2


pip install -U virtualenvwrapper

1
2

查看安装的库

pip list

1
2
3


配置bashrc

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

1
2

bashrc 生效

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 # 如果 aFalse, 结果是 True, 如果 aTrue,结果是 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'

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

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

for

while

推导式

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

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

mac快速生成干净的linux开发环境

virtualbox -> vagrant/completed -> docker-root -> docker -> … ubuntu/alpine

安装vagrant 或者去官方下载

1
2
3
4
5
brew tap caskroom/cask #if not already installed
brew install brew-cask #if not already installed
brew cask install vagrant
brew tap homebrew/completions
brew install vagrant-completion

docker-root

1
2
3
4
$ git clone https://github.com/ailispaw/docker-root
$ cd docker-root
$ make vagrant
$ make

vagrant配置文件

1
2
3
4
5
6
7
8
9
$ vagrant init
$ vim Vagrantfile
config.vm.box = "ailispaw/docker-root"
config.vm.network "forwarded_port", guest: 9999, host: 1234
config.vm.synced_folder "/Users/ce/workspace/docker/data", "/home/docker/data"
config.vm.provider "virtualbox" do |vb|
vb.memory = "512"
vb.cpus = "4"
end

启动并ssh

1
vagrant up && vagrant ssh

docker

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 create -ti -h teach --name teach -v /home/docker/data:/root/data -w /root ubuntu /bin/bash

查看所有容器

1
docker ps -a -q

删除容器

1
docker rm container_id
1
docker attach

安装必备工具

1
sudo apt-get install gcc gdb binutils make git dstat sysstat htop curl wget readelf objdump pidstat