tcpdump太好玩了

我有个主机,专门用来做跳板来访问一些国外网站,也分享给了几个朋友。
速度一直很稳定,今早突然变得特别卡,所以就想看看是不是我们几个人同时在用。

详细的tcpdump请看tcpdump man page

1
2
root@chuanwu:~# tail -f tmp.log | pv > /dev/null
948KiB 0:01:35 [3.97KiB/s] [ <=> ]

看了下文件增长速度,还可以,让他跑一下午。拿到数据分析一下。

刷leetcode

编程,我觉得要经历三个阶段。

  • make it run
  • make it work
  • make it faster

目前,我只做到了第二步。
等刷完,再去discuss板块里看看大神们是怎么优化的。

代码在这Leetcode

刷了有十几题,最大的感受就是无论什么样的题,总有大神,能用最短最精简的代码来搞定,而且运行时间要比你的短…

我觉得今天太忙真是一个说服自己的好托辞。

最近太忙, 没空看书。
最近项目需求多,没时间去刷题。
改个bug,累死了,今天先歇会。

诸如此类。

给自己定目标定一年了,说要刷leetcode,然后呢,一年之后,还是一题没刷。好羞耻…

一共也就358道题。

想起胡适先生的日记。

7月4日
新开这本日记,也为了督促自己下个学期多下些苦功。先要读完手边的莎士比亚的《亨利八世》……

7月13日
打牌。

7月14日
打牌。

7月15日
打牌。

7月16日
胡适之啊胡适之!你怎么能如此堕落!先前订下的学习计划你都忘了吗?
子曰:“吾日三省吾身。”…不能再这样下去了!

7月17日
打牌。

7月18日
打牌。

不想扯了。直接开刷吧。

leetcode

关于Python的property,yield,slot

yield

要理解yield,先看两段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fib(n):
a, b = 0, 1
for _ in xrange(n):
yield a
a, b = b, a + b
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i+n]
def split_by_age(children, n):
ages = set(i.age for i in children)
for age in ages:
yield age, chunks(list(set(i for i in children if i.age == age)), n)

yield和return很像。不过yield返回的是一个生成器。生成器和迭代器很像,不过生成器只能遍历一次,因为每次next()被调用时,生成器会返回它脱离的位置,就是说它只能记住语句最后一次执行的位置和所有的数据值)。

property

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

1
2
s = Student()
s.score = 9999 or 'hello world'

这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,通过这两个方法就能限制score了。

1
2
3
4
5
6
7
8
9
10
11
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

这样,我们就能对score进行参数检查了。

1
2
3
4
5
6
7
8
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!

但是看着代码,好烦,一点也不Pythonic。

Python里面有个built-in方法property可以胜任这个工作。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

如果不定义@score.setter,property默认只是给score增加了一个getter属性,它是作为一个只读属性存在。

requests里面有段源码,可以参考下。

1
2
3
4
5
6
7
8
9
10
11
@property
def unverifiable(self):
return self.is_unverifiable()
@property
def origin_req_host(self):
return self.get_origin_req_host()
@property
def host(self):
return self.get_host()

slots

看段代码。

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
class Class(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
m = Class(1,2)
m.age = 1
class Class(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
m = Class(1,2)
m.age = 1
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-12-b504c75d350a> in <module>()
----> 1 m.age = 1
AttributeError: 'MyClass' object has no attribute 'age'
class MyClass(Class):
pass
c = Class()
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-14-2e11f7fbc2a5> in <module>()
----> 1 c = Class()
TypeError: __init__() takes exactly 3 arguments (1 given)
c = Class(1,2)
c.age =1

使用slots有以下好处。

  • slots会限定只能设定这两个属性
  • 继承的子类不受限制
  • 能省点内存。

with

with这个关键词,挺常用的。今天偶然翻到一篇PEP,才发现原来一个with干了这么多事。

大概是这么个逻辑,PEP-343里描述得更详细,推荐读一下。

1
2
3
4
5
6
7
8
9
10
@contextmanager
def opening(filename):
f = open(filename)
try:
yield f
finally:
f.close()
with f = opening(filename):
...read data from f...

super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A(object):
def foo(self):
print 'A'
class B(A):
def foo(self):
print 'B'
super(B, self).foo()
class C(A):
def foo(self):
print 'C'
super(C, self).foo()
class D(B,C):
def foo(self):
print 'D'
super(D, self).foo()
d = D()
d.foo()
# DBCA

requests源码阅读

对网络请求一直很感兴趣,加上requests的代码很Pythonic,所以我打算读一下源码。

我fork的requests版本为2.8.1

先从cookies入手,了解整个项目,我也会对在阅读过程感觉不明白或者有趣的地方进行拓展。

网络小白,如有错误,请指正。

So, 开始吧?

cookies里面主要封装了几个类。

  • MockRequest
  • MockResponse
  • RequestsCookieJar
  • CookieConflictError

MockRequest主要是封装了一个requests.Request来模拟urllib2.Request。
里面有三个方法,挺好玩的。

requests源代码看起来其实蛮费力的,需要对HTTP有很深的认识才行。

所以,接下来,打算给自己设定两个目标。

  • 阅读HTTP权威指南
  • 熟悉pytest框架

MySQL索引

为什么要用索引以及索引是怎么工作的

多数情况下,不使用索引,试图通过其他途径来提高性能,纯粹是浪费时间(出自《MySQL技术内幕》)。
那索引是怎么提高性能的呢?

  • 通过索引能获取数据的结束位置,从而跳过其他部分
  • 定位算法,可以快速定位第一个匹配值

InnoDB总是使用“B树”来创建索引,对于这种索引,在使用<, <=,=,=>,!=,BETWEEN操作符时有会很有效率。

Tip: BETWEEN在Django ORM对应range操作符。

1
2
3
4
5
import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))

相当于

1
SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

怎么建立索引

1. 总结业务场景,分析出最常用的会在where中出现的字段

比如以我们的项目而言,instance_nameuser_id, check_date出现的频率最高,所以这三个字段肯定需要建立索引。通过这样的索引,可以避免全表查找。

2. 数据维度势

维度就是说表中容纳的非重复值的个数。我们尽量应该选择一些区分度高的,区分度=count(distinct col)/count(*),按照美团的博客来讲,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录

3. 不要滥用索引

  • 由于在写入数据时,不仅要求写到数据行,还会影响所有的索引。所以索引建立越多,就会导致写入速度越慢。
  • 索引会占据磁盘空间。

4. 为字符串的前缀编索引

索引可以减少索引空间,从而加快速度。

5. 复合索引

比如地址,Province, City,通过这两个值得组合来建立索引。

6. 最左前缀匹配

mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

7. 不要把索引列加入计算

尽量不要在where中的“=”的左边,进行计算。

怎么查询

  1. 在where,order by语句中使用索引
  2. 避免在where中去使用数据维度势低的,比如sex,isDeleted等
  3. 如果是数字型字段,则使用数字类型
  4. 尽量不要使用!=, like或者>, <,引擎可能会进行全表搜索,考虑使用between,union等来替代。

使用Expalin来优化SQL

我为了测试,在本机MySQL中,建了一张表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> create table if not exists front_end_team (id bigint auto_increment, gender tinyint not null default 0, name varchar(12) not null, age smallint not null, height decimal(6,2) not null, gmt_created datetime not null, gmt_modified datetime not null, primary key(id)) engine=innodb default charset utf8;
mysql> desc front_end_team;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(12) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
| height | decimal(6,2) | YES | | NULL | |
| gmt_created | datetime | NO | | NULL | |
| gmt_modified | datetime | NO | | NULL | |
| gender | tinyint(4) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)

并往里面写了1w条数据。

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
mysql> select * from front_end_team limit 3;
+----+--------+--------+-----+--------+---------------------+---------------------+
| id | gender | name | age | height | gmt_created | gmt_modified |
+----+--------+--------+-----+--------+---------------------+---------------------+
| 1 | 0 | Tom | 32 | 154.32 | 2016-06-11 14:42:09 | 2016-06-11 14:42:09 |
| 2 | 0 | Sandro | 31 | 154.43 | 2016-06-11 14:42:28 | 2016-06-11 14:42:28 |
| 3 | 0 | Cgdali | 26 | 158.30 | 2016-06-11 14:42:49 | 2016-06-11 14:42:49 |
+----+--------+--------+-----+--------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> show index from front_end_team\G
*************************** 1. row ***************************
Table: front_end_team
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 10142
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
1 row in set (0.00 sec)
mysql>

这张表里面,目前只有主键建了索引。我安装的MySQL引擎是InnoDB,所以索引类型使用B树来实现。

好,目前为止,环境已经OK。来试试Expalin吧。我们的目标就是不断优化Explain返回的rows字段。

先大概解释下Expalin返回的字段名吧。

Column 意义
select_type select类型
table 展示行的table
type join类型
possible_keys 索引的可能取值
key 实际使用的索引
key_len 使用索引的长度
ref 跟索引
rows 关键指标
filtered
Extra

具体可参考MySQL Explain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 全表查询
mysql> explain select * from front_end_team\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: front_end_team
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10142
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)

此时,key为NULL,没有使用任何索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> explain select * from front_end_team where id between 1 and 10004\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: front_end_team
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 5071
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)

虽然这两句话干的都是同一件事,但是使用between已经让MySQL使用索引来查询了。rows锐减一倍。

我们来验证一下刚刚的数据维度势的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> explain select * from front_end_team where gender = 0\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: front_end_team
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10142
filtered: 10.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)

给gender加上索引,再试试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> create index gender on front_end_team (gender smallint);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from front_end_team where gender = 0\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: front_end_team
partitions: NULL
type: ref
possible_keys: gender
key: gender
key_len: 1
ref: const
rows: 4995
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)

这个地方挺出乎我意料的,我原以为这里MySQL会直接进行全表扫描。事实上,虽然gender的区分度很低,但是MySQL还是使用了这个索引。

截止目前,我们进行的都是单表查询,接下来看看多表的。

先来复习下left join,right join, inner join, outer join:

SQL Joins

Ref:

  1. 美团点评团队
  2. MySQL技术内幕

Pratical VIM笔记

1. .重复上一次操作

2. >G会增加从当前行到文档末尾的缩进

组合#1和#2来试试。

1
2
3
4
5
6
7
8
9
10
11
Txt:
1 Line One
2 Line Two
3 Line Three
Command:>Gj.j.j.
1 Line One.
2 Line Two.
3 Line Three.

3. 行尾添加分号

1
2
3
4
5
6
7
8
9
10
11
12
13
# vim中A=$a
Txt:
1 Line One
2 Line Two
3 Line Three
Command:A;<Esc>j.j.
1 Line One.;
2 Line Two.;
3 Line Three.;

4. 重复进行一些操作

1
2
3
4
5
6
Txt:
I have some delicious apples.
Command: c2wbought some<Esc>
I have bought some apples.

5. VIM一些操作符

Command Usage
g~ 反转大小写
gu 转成小写
gU 转成大写

可视模式下

Command Usage
~ 反转大小写
u 转成小写
U 转成大写

Python中的_, __, __xx__

看MySQL看得有点头大,写点好玩的吧。来看看_, __以及__xx__

_

_在Python中,有三种用途:

  • python shell中,来保存上次返回的结果。
1
2
3
4
5
6
7
In [1]: a, b = 3, 4
In [2]: a+b
Out[2]: 7
In [3]: _
Out[3]: 7
  • i18n模块中会用print _("Hello World")
    如果在翻译文件中定义了
1
2
msgid "Hello World"
msgstr "世界你好"

执行print _("Hello World"),则会显示世界你好。其实,这是因为Django i18n在翻译时会把gettext缩写成( from django.utils.translation import ugettext as )。

  • 遇到一次性的变量时,会给它起名叫_

在Python Cookbook中,有这样一段源码。

1
2
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> _, shares, price, _ = data >>> shares 50 >>> price 91.1

此外,在可以在类的方法或属性前加一个“_”单下划线,意味着该方法或属性不应该去调用,它并不属于API。
Django中有段源码:

1
2
3
4
5
6
7
8
9
10
class BaseForm(StrAndUnicode):
def _get_errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
errors = property(_get_errors)

_表示该方法是私有的,不应该访问。

__

__避免子类覆盖其内容。

__xx__

__xx__经常是操作符或本地函数调用的magic methods

几个有趣的Python模块

glances

1
2
3
pip install glances
glances

能实时看到自己主机的信息。效果很赞。

when.py

直接上demo。还是很简洁的。

1
2
3
4
5
6
7
8
9
>>> import when
>>> when.timezone()
'Asia/Shanghai'
>>> when.today()
datetime.date(2013, 5, 14)
>>> when.tomorrow()
datetime.date(2013, 5, 15)
>>> when.now()
datetime.datetime(2013, 5, 14, 21, 2, 23, 78838)

fn.py

fn.py,最近正在学习的一个函数式编程模块,也很酷。
结合fn.py之后的lambda语句更加简洁,比如:

1
2
3
from fn import _
assert list(map(_ * 2, range(5))) == [0,2,4,6,8]
assert list(filter(_ < 10, [9,10,11])) == [9]

sh

对这个模块简直大爱。sh可以让py很完美地兼容bash。比如

1
2
3
4
5
6
from sh import git
print git.branch("-v")
# result
* master commit-id commit-msg

So cool!

那我们可以用这个模块做一些更炫酷的事情。比如我每天都需要查看服务器日志。

我可以通过sh模块,先scp我需要的日志下来,然后程序分析,对于需要实时盯着的日志,我们还可以引入tail -f来配合。好吧,我在一本正经地胡说八道。随便一个log文件,都要过g,怎么scp…
举个栗子:


from sh import tail

for line in tail("-f", "/var/log/some_log_file.log", _iter=True):
    print line

这样就可以tail日志了。

Python 2中的字符序列

Python 2 有两种表示字符序列的类型
• str (原始的八位值)
• unicode

注意: Python 2的unicode和Python 3的str都没有关联相应的进制编码形式。
所以在把unicode转化成二进制数据时,需要指明encode方法,反之,则需要
指定decode方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
def to_unicode(unicode_or_str):
if isinstance(unicode_or_str, str):
value = unicode_or_str.decode('utf-8')
else:
value = unicode_or_str
return value
def to_str(unicode_or_str):
if isinstance(unicode_or_str, unicode):
value = unicode_or_str.encode('utf-8')
else:
value = unicode_or_str
return value

unicode是为了解决各种编码方案不一致而提出来的。在Python 2中,也是通过unicode来解决编码冲突这个问题。unicode相当于是一种中间编码,可以将各种类型的str来decode成unicode,也可以把unicode通过encode来转化成在某种编码下的str。

把msg转化成utf-8编码,存成str

1
2
3
4
5
# -*- coding:utf-8 -*-
msg = u'小公主GG'
encoded_msg = msg.encode('utf-8')
print repr(msg)
# \xe5\xb0\x8f\xe5\x85\xac\xe4\xb8\xbbGG

相反的,

1
2
3
4
5
6
7
# -*- coding:utf-8 -*-
msg = '\xe5\xb0\x8f\xe5\x85\xac\xe4\xb8\xbbGG'
decoded_msg = msg.decode('utf-8')
print decoded_msg
# 小公主GG