Follower me on GitHub
×

博客更新通知

感谢一直支持我的朋友,目前在做一个自己的博客项目。几个月后会有新版本的博客,新版本博客将主要涉及python,ruby,lisp,算法,devops,emacs,github开源项目等方面内容,敬请关注

前言

越来越多的人使用emacs作为开发工具. 甚至skype,gmail,豆瓣FM都能通过emacs. 作为一个产品开发,肯定使用很多插件,设置一些快捷键来提高开发效率.以前一直使用 prelude,很久之后发现有以下问题:

  1. 比如开启python语言支持需要在prelude-modules.el里面把python这样的注释去掉
  2. 我不需要支持这么多的语言,也不需要那么多快捷键
  3. aotupair实在太难用了
  4. scss/css模式不好自定义缩进空格数, tab和空格混用. 不好定制
  5. 看过源码后发现,其实很来很简单粗暴的事情弄得有点复杂了

我造了个轮子.emacs.d,主要针对python和web开发

Update

2014-09-28, 经过这一个月的继续研究,已经有了很大的改变

项目目录结构

├── Cask ; 我使用[cask](https://github.com/cask/cask)做包管理工具
├── auto-insert ; 使用auto-insert设置新增elisp/python文件自动添加基于yasnippet的模板
│   ├── elisp-auto-insert
│   └── python-auto-insert
├── custom ; 自定义插件目录,你也可以把你写的程序放进来然后在init.el里面require
│   ├── flycheck.el ; 定制flycheck,让它在保存python程序时自动执行pep8和flake8,有问题的条目会打开新的buffer打印出来
│   └── py-autopep8.el ; 我自己实现了autopep8插件,保存时自动根据pep8标准处理文件
├── functions.el ; 用到的相关函数
├── helper.el ; 我自己写了个类似`C-h b`的介绍绑定的快捷键的预览表
├── hs-minor-mode-conf.el ; python函数/类折叠
├── init.el ; emacs启动的主程序
├── keys.el ; Key-chord配置,默认被注释了,因为它和我经常大片粘贴代码中代码重复造成很多麻烦
├── local-settings.el ; 本机的本地配置,比如用户名,单独的快捷键等
├── misc.el ; 对emacs本身的一些配置
├── mode-mappings.el ; 模式映射,比如Cask会自动用emacs-lisp-mode
├── modeline.el ; 我重新定制了modeline,使用了nyan-mode和powerline,一些加颜色的hack
├── osx.el ; Mac下的一些独立配置,为我的hhkb定制
├── smartparens-config.el ; 定制了smartparens配置
├── tmp
│   └── README.md
└── xiaoming-theme.el ; 我自己写了一个主题,好吧 我就是`小明`

使用的插件列表

  1. f - 处理文件相关的库
  2. s - 处理字符串相关的库
  3. ag - 据说比ack更快的文本搜索工具the_silver_searcher的emacs插件
  4. ht - 处理哈希相关的库
  5. anzu - 显示当前匹配文本,预览替换效果和总匹配数的插件
  6. dash - 常用函数集合
  7. helm - 方便查找各种文件内容,buffer切换,emacs命令执行等
  8. jedi - python代码补全,快速需要函数/模块定义的插件
  9. smex - M-x 的命令行补全的功能
  10. direx - 展示目录树
  11. magit - git插件
  12. slime - commonlisp交互模式
  13. ac-js2 - js2-mode支持js函数定义查找
  14. rinari - 依赖,需要安装
  15. diff-hl - 在行首用颜色表示git状态-只支持图形界面的emacs
  16. dired-k - 用带不同颜色的高亮显示文件/目录,大小等信息
  17. bind-key - 本项目绑定快捷键的用法都根据这个包,没有用global-set-key
  18. css-mode - css-mode
  19. js2-mode - js-mode的升级版
  20. web-mode - 前端开发必备, html缩进,支持根据tag/元素/属性/block/dom跳转,语法高亮,支持mako,jinja2等模板
  21. git-blame - git-blame,单独版
  22. key-chord - 可以快速按键达到快捷键的作用
  23. nyan-mode - 一直可爱的小猫
  24. plim-mode - 我写的编辑plim的major-mode
  25. powerline - 提供一个漂亮的状态栏
  26. sass-mode - 编辑sass
  27. scss-mode - 编辑scss
  28. sublimity - 在图形界面的emacs能缩小预览代码-sublime-text有类似的插件
  29. undo-tree - 让undo可视化
  30. yaml-mode - 编辑yaml
  31. yasnippet - 一个神奇的模板系统,定义缩写并通过tab键自动帮你展开(一些自动的”填空题”机制)
  32. drag-stuff - 可以将代码块整体拖动
  33. helm-swoop - 项目内关键词查找,并能自动跳到对应文件和对应行
  34. ibuffer-vc - 支持版本空的ibuffer模式
  35. projectile - 管理项目,可快速访问项目里任何文件,支持全项目关键词搜索
  36. coffee-mode - 编辑coffee
  37. python-mode - 编辑python
  38. smartparens - 自动括号匹配,可以按块删除,tag跳转
  39. use-package - 本项目引用包的方式
  40. crontab-mode - 高亮编辑crontab
  41. golden-ratio - 黄金分割展示当前window
  42. helm-ipython - helm的ipython插件
  43. rainbow-mode - 在代码中通过背景色标示颜色值
  44. ace-jump-mode - 快速让光标位置到你想去的地方
  45. expand-region - 按层次块区域选择
  46. helm-css-scss - helm的css/scss插件
  47. markdown-mode - 编辑markdown
  48. switch-window - 可视化切换窗口
  49. visual-regexp - 可视化正则匹配
  50. gitconfig-mode - 单独的gitconfig-mode
  51. gitignore-mode - 单独的gitignore-mode
  52. helm-descbinds - 让默认的C-h b高亮并且按组分开
  53. imenu-anywhere - 类似于etag, 可直接跳到对应的标签
  54. multiple-cursors - 一次编辑多处/行文字
  55. discover-my-major - 告诉你当前mode的一些说明/快捷键设置
  56. virtualenvwrapper - virtualenvwrapper
  57. gitattributes-mode - 独立的gitattributes-mode
  58. rainbow-delimiters - 对内嵌的括号等pair符号加不同颜色
  59. idle-highlight-mode - 在设置的一段设置时间未操作电脑会自动高亮当前关键词,并且全文高亮相同关键词
  60. exec-path-from-shell - 可以使用$PATH环境变量
  61. find-file-in-repository - 根据git属性在项目里查找文件
  62. emmet-mode - 类似于zencoding,但是能编辑css,使用很少的代码就能构造一个复杂的div/css
  63. browse-kill-ring - 查看最近操作的删除文本,以及恢复后的效果

安装使用

curl -fsSkL https://raw.github.com/cask/cask/master/go | python
git clone https://github.com/dongweiming/emacs.d .emacs.d
cd .emacs.d
cask
sudo pip install jedi pep8 autopep8 flake8

快捷键分布

请参看项目的README.md

有了第一篇python的魔法(-)之基础知识, 我们再来说说python开发中的坑

不要使用可变对象作为函数默认值

In [1]: def append_to_list(value, def_list=[]):
   ...:         def_list.append(value)
   ...:         return def_list
   ...:

In [2]: my_list = append_to_list(1)

In [3]: my_list
Out[3]: [1]

In [4]: my_other_list = append_to_list(2)

In [5]: my_other_list
Out[5]: [1, 2] # 看到了吧,其实我们本来只想生成[2] 但是却把第一次运行的效果页带了进来

In [6]: import time

In [7]: def report_arg(my_default=time.time()):
   ...:         print(my_default)
   ...:

In [8]: report_arg() # 第一次执行
1399562371.32

In [9]: time.sleep(2) # 隔了2秒

In [10]: report_arg()
1399562371.32 # 时间竟然没有变

这2个例子说明了什么? 字典,集合,列表等等对象是不适合作为函数默认值的. 因为这个默认值实在函数建立的时候就生成了, 每次调用都是用了这个对象的”缓存”. 我在上段时间的分享python高级编程也说到了这个问题,这个是实际开发遇到的问题,好好检查你学过的代码, 也许只是问题没有暴露

可以这样改:

def append_to_list(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

生成器不保留迭代过后的结果

In [12]: gen = (i for i in range(5))

In [13]: 2 in gen
Out[13]: True

In [14]: 3 in gen
Out[14]: True

In [15]: 1 in gen
Out[15]: False # 1为什么不在gen里面了? 因为调用1->2,这个时候1已经不在迭代器里面了,被按需生成过了

In [20]: gen = (i for i in range(5))

In [21]: a_list = list(gen) # 可以转化成列表,当然a_tuple = tuple(gen) 也可以

In [22]: 2 in a_list
Out[22]: True

In [23]: 3 in a_list
Out[23]: True

In [24]: 1 in a_list # 就算循环过,值还在
Out[24]: True

lambda在闭包中会保存局部变量

In [29]: my_list = [lambda: i for i in range(5)]

In [30]: for l in my_list:
   ....:         print(l())
   ....:
4
4
4
4
4

这个问题还是上面说的python高级编程中说过具体原因. 其实就是当我赋值给my_list的时候,lambda表达式就执行了i会循环,直到 i =4,i会保留

但是可以用生成器

In [31]: my_gen = (lambda: n for n in range(5))

In [32]: for l in my_gen:
   ....:         print(l())
   ....:
0
1
2
3
4

也可以坚持用list:

In [33]: my_list = [lambda x=i: x for i in range(5)] # 看我给每个lambda表达式赋了默认值

In [34]: for l in my_list:
   ....:         print(l())
   ....:
0
1
2
3
4

有点不好懂是吧,在看看python的另外一个魔法:

In [35]: def groupby(items, size):
   ....:     return zip(*[iter(items)]*size)
   ....:

In [36]: groupby(range(9), 3)
Out[36]: [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

一个分组的函数,看起来很不好懂,对吧? 我们来解析下这里

In [39]: [iter(items)]*3
Out[39]:
[<listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>] # 看到了吧, 其实就是把items变成可迭代的, 重复三回(同一个对象哦), 但是别忘了,每次都.next(), 所以起到了分组的作用
 In [40]: [lambda x=i: x for i in range(5)]
Out[40]:
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>] # 看懂了吗?

在循环中修改列表项

In [44]: a = [1, 2, 3, 4, 5]

In [45]: for i in a:
   ....:     if not i % 2:
   ....:         a.remove(i)
   ....:

In [46]: a
Out[46]: [1, 3, 5] # 没有问题

In [50]: b = [2, 4, 5, 6]

In [51]: for i in b:
   ....:      if not i % 2:
   ....:          b.remove(i)
   ....:

In [52]: b
Out[52]: [4, 5] # 本来我想要的结果应该是去除偶数的列表

思考一下,为什么 – 是因为你对列表的remove,影响了它的index

In [53]: b = [2, 4, 5, 6]

In [54]: for index, item in enumerate(b):
   ....:     print(index, item)
   ....:     if not item % 2:
   ....:         b.remove(item)
   ....:
(0, 2) # 这里没有问题 2被删除了
(1, 5) # 因为2被删除目前的列表是[4, 5, 6], 所以索引list[1]直接去找5, 忽略了4
(2, 6)

IndexError - 列表取值超出了他的索引数

In [55]: my_list = [1, 2, 3, 4, 5]

In [56]: my_list[5] # 根本没有这个元素
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-56-037d00de8360> in <module>()
----> 1 my_list[5]

IndexError: list index out of range # 抛异常了

In [57]: my_list[5:] # 但是可以这样, 一定要注意, 用好了是trick,用错了就是坑啊
Out[57]: []

重用全局变量

In [58]: def my_func():
   ....:         print(var) # 我可以先调用一个未定义的变量
   ....:

In [59]: var = 'global' # 后赋值

In [60]: my_func() # 反正只要调用函数时候变量被定义了就可以了
global

In [61]: def my_func():
   ....:     var = 'locally changed'
   ....:

In [62]: var = 'global'

In [63]: my_func()

In [64]: print(var)
global # 局部变量没有影响到全局变量

In [65]: def my_func():
   ....:         print(var) # 虽然你全局设置这个变量, 但是局部变量有同名的, python以为你忘了定义本地变量了
   ....:         var = 'locally changed'
   ....:

In [66]: var = 'global'

In [67]: my_func()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-67-d82eda95de40> in <module>()
----> 1 my_func()

<ipython-input-65-0ad11d690936> in my_func()
      1 def my_func():
----> 2         print(var)
      3         var = 'locally changed'
      4

UnboundLocalError: local variable 'var' referenced before assignment

In [68]: def my_func():
   ....:         global var # 这个时候得加全局了
   ....:         print(var) # 这样就能正常使用
   ....:         var = 'locally changed'
   ....:

In [69]: var = 'global'

In [70]:

In [70]: my_func()
global

In [71]: print(var)
locally changed # 但是使用了global就改变了全局变量

拷贝可变对象

In [72]: my_list1 = [[1, 2, 3]] * 2

In [73]: my_list1
Out[73]: [[1, 2, 3], [1, 2, 3]]

In [74]: my_list1[1][0] = 'a' # 我只修改子列表中的一项

In [75]: my_list1
Out[75]: [['a', 2, 3], ['a', 2, 3]] # 但是都影响到了

In [76]: my_list2 = [[1, 2, 3] for i in range(2)] # 用这种循环生成不同对象的方法就不影响了

In [77]: my_list2[1][0] = 'a'

In [78]: my_list2
Out[78]: [[1, 2, 3], ['a', 2, 3]]

前言

最近读了一篇A collection of not-so-obvious Python stuff you should know!,感觉受益颇多. 翻译过来(非直接翻译),再加上一些我的理解和注释. 让大家注意python鲜为人知的”魔法”. 我会分2篇

python多继承(C3)
In [1]: class A(object):
   ...:         def foo(self):
   ...:                 print("class A")
   ...:

In [2]: class B(object):
   ...:         def foo(self):
   ...:                 print("class B")
   ...:

In [3]: class C(A, B):
   ...:         pass
   ...:

In [4]: C().foo()
class A # 例子很好懂, C继承了A和B,从左到右,发现A有foo方法,返回了

看起来都是很简单, 有次序的从底向上,从前向后找,找到就返回. 再看例子:

In [5]: class A(object):
   ...:        def foo(self):
   ...:               print("class A")
   ...:

In [6]: class B(A):
   ...:        pass
   ...:

In [7]: class C(A):
   ...:        def foo(self):
   ...:               print("class C")
   ...:

In [8]: class D(B,C):
   ...:        pass
   ...:

In [9]: D().foo()
class C # ? 按道理, 顺序是 D->B->A,为什么找到了C哪去了

这也就涉及了MRO(Method Resolution Order):

In [10]: D.__mro__
Out[10]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

简单的理解其实就是新式类是广度优先了, D->B, 但是发现C也是继承A,就先找C,最后再去找A

列表的+和+=, append和extend

In [17]: print('ID:', id(a_list))
('ID:', 4481323592)

In [18]: a_list += [1]

In [19]: print('ID (+=):', id(a_list))
('ID (+=):', 4481323592) # 使用+= 还是在原来的列表上操作

In [20]: a_list = a_list + [2]

In [21]: print('ID (list = list + ...):', id(a_list))
('ID (list = list + ...):', 4481293056) # 简单的+其实已经改变了原有列表
In [28]: a_list = []

In [29]: id(a_list)
Out[29]: 4481326976

In [30]: a_list.append(1)

In [31]: id(a_list)
Out[31]: 4481326976 # append 是在原有列表添加

In [32]: a_list.extend([2])

In [33]: id(a_list)
Out[33]: 4481326976 # extend 也是在原有列表上添加

datetime也有布尔值

这是一个

In [34]: import datetime

In [35]: print('"datetime.time(0,0,0)" (Midnight) ->', bool(datetime.time(0,0,0)))
('"datetime.time(0,0,0)" (Midnight) ->', False)

In [36]: print('"datetime.time(1,0,0)" (1 am) ->', bool(datetime.time(1,0,0)))
('"datetime.time(1,0,0)" (1 am) ->', True)

’==’ 和 is 的区别

我的理解是”is”是判断2个对象的身份, ==是判断2个对象的值

In [37]: a = 1

In [38]: b = 1

In [39]: print('a is b', bool(a is b))
('a is b', True)

In [40]: c = 999

In [41]: d = 999

In [42]: print('c is d', bool(c is d))
('c is d', False) # 原因是python的内存管理,缓存了-5 - 256的对象

In [43]: print('256 is 257-1', 256 is 257-1)
('256 is 257-1', True)

In [44]: print('257 is 258-1', 257 is 258 - 1)
('257 is 258-1', False)

In [45]: print('-5 is -6+1', -5 is -6+1)
('-5 is -6+1', True)

In [46]: print('-7 is -6-1', -7 is -6-1)
('-7 is -6-1', False)
In [47]: a = 'hello world!'

In [48]: b = 'hello world!'

In [49]: print('a is b,', a is b)
('a is b,', False) # 很明显 他们没有被缓存,这是2个字段串的对象

In [50]: print('a == b,', a == b)
('a == b,', True) # 但他们的值相同
# But, 有个特例
In [51]: a = float('nan')

In [52]: print('a is a,', a is a)
('a is a,', True)

In [53]: print('a == a,', a == a)
('a == a,', False) # 亮瞎我眼睛了~

浅拷贝和深拷贝

我们在实际开发中都可以向对某列表的对象做修改,但是可能不希望改动原来的列表. 浅拷贝只拷贝父对象,深拷贝还会拷贝对象的内部的子对象

In [65]: list1 = [1, 2]

In [66]: list2 = list1 # 就是个引用, 你操作list2,其实list1的结果也会变

In [67]: list3 = list1[:]

In [69]: import copy

In [70]: list4 = copy.copy(list1) # 他和list3一样 都是浅拷贝

In [71]: id(list1), id(list2), id(list3), id(list4)
Out[71]: (4480620232, 4480620232, 4479667880, 4494894720)

In [72]: list2[0] = 3

In [73]: print('list1:', list1)
('list1:', [3, 2])

In [74]: list3[0] = 4

In [75]: list4[1] = 4

In [76]: print('list1:', list1)
('list1:', [3, 2]) # 对list3和list4操作都没有对list1有影响

# 再看看深拷贝和浅拷贝的区别

In [88]: from copy import copy, deepcopy

In [89]: list1 = [[1], [2]]

In [90]: list2 = copy(list1) # 还是浅拷贝

In [91]: list3 = deepcopy(list1) # 深拷贝

In [92]: id(list1), id(list2), id(list3)
Out[92]: (4494896592, 4495349160, 4494896088)

In [93]: list2[0][0] = 3

In [94]: print('list1:', list1)
('list1:', [[3], [2]]) # 看到了吧 假如你操作其子对象 还是和引用一样 影响了源

In [95]: list3[0][0] = 5

In [96]: print('list1:', list1)
('list1:', [[3], [2]]) # 深拷贝就不会影响

bool其实是int的子类

这篇bool-is-int很有趣:

In [97]: isinstance(True, int)
Out[97]: True

In [98]: True + True
Out[98]: 2

In [99]: 3 * True + True
Out[99]: 4

In [100]: 3 * True - False
Out[100]: 3

In [104]: True << 10
Out[104]: 1024

元组是不是真的不可变?

In [111]: tup = ([],)

In [112]: tup[0] += [1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-112-d4f292cf35de> in <module>()
----> 1 tup[0] += [1]

TypeError: 'tuple' object does not support item assignment

In [113]: tup
Out[113]: ([1],) # 我靠 又是亮瞎我眼睛,明明抛了异常 还能修改?

In [114]: tup = ([],)

In [115]: tup[0].extend([1])

In [116]: tup[0]
Out[116]: [1] # 好吧,我有点看明白了, 虽然我不能直接操作元组,但是不能阻止我操作元组中可变的子对象(list)

这里有个不错的解释Python’s += Is Weird, Part II :

In [117]: my_tup = (1,)

In [118]: my_tup += (4,)

In [119]: my_tup = my_tup + (5,)

In [120]: my_tup
Out[120]: (1, 4, 5) # ? 嗯 不是不能操作元组嘛?

In [121]: my_tup = (1,)

In [122]: print(id(my_tup))
4481317904

In [123]: my_tup += (4,)

In [124]: print(id(my_tup))
4480606864 # 操作的不是原来的元组 所以可以

In [125]: my_tup = my_tup + (5,)

In [126]: print(id(my_tup))
4474234912

python没有私有方法/变量? 但是可以有”伪”的

In [127]: class my_class(object^E):
   .....:     def public_method(self):
   .....:         print('Hello public world!')
   .....:     def __private_method(self): # 私有以双下划线开头
   .....:         print('Hello private world!')
   .....:     def call_private_method_in_class(self):
   .....:         self.__private_method()

In [132]: my_instance = my_class()

In [133]: my_instance.public_method()
Hello public world! # 普通方法

In [134]: my_instance._my_class__private_method()
Hello private world! # 私有的可以加"_ + 类名字 + 私有方法名字”

In [135]: my_instance.call_private_method_in_class()
Hello private world! # 还可以通过类提供的公有接口内部访问

In [136]: my_instance._my_class__private_variable
Out[136]: 1

异常处理加else

In [150]: try:
   .....:     print('third element:', a_list[2])
   .....: except IndexError:
   .....:     print('raised IndexError')
   .....: else:
   .....:     print('no error in try-block') # 只有在try里面没有异常的时候才会执行else里面的表达式
   .....:
raised IndexError # 抛异常了 没完全完成
In [153]: i = 0

In [154]: while i < 2:
   .....:     print(i)
   .....:     i += 1
   .....: else:
   .....:     print('in else')
   .....:
0
1
in else # while也支持哦~
In [155]: i = 0

In [156]: while i < 2:
   .....:         print(i)
   .....:         i += 1
   .....:         break
   .....: else:
   .....:         print('completed while-loop')
   .....:
0 # 被break了 没有完全执行完 就不执行else里面的了
In [158]: for i in range(2):
   .....:         print(i)
   .....: else:
   .....:         print('completed for-loop')
   .....:
0
1
completed for-loop

In [159]: for i in range(2):
   .....:         print(i)
   .....:         break
   .....: else:
   .....:         print('completed for-loop')
   .....:
0 # 也是因为break了

前言

原来的一位同事@炮哥, 昨天在QQ问我: “想请教下一个线程获得lock之后,也可能被其他的线程释放掉?这个是因为线程之间的资源是共享的吗?这样的话普通的thread lock 都是不安全的了?”. 我第一反应就是怎么可能:’谁加锁谁解锁呗,别的线程怎么能解锁?’

炮哥发来一段python官网的文档说明:

A factory function that returns a new primitive lock object. Once a thread has acquired it, subsequent attempts to acquire it block, until it is released; any thread may release it.

最有最后一句前是分号. 简单地说,一个线程获取锁, 以后的尝试获取都会被阻塞,除非它会释放. 但是同时其他其他线程可以释放

好,写个例子验证下:

import threading
import time

lock = threading.Lock()

def lock_holder(lock):
    print('Starting')
    while True:
        lock.acquire()
        print('Holding')
        time.sleep(100)
        print('Sleep done')

def lock_release(lock):
    time.sleep(1) # 保证顺序
    lock.release()
    print('Release it')


holder = threading.Thread(target=lock_holder, args=(lock,), name='LockHolder')
holder.setDaemon(True)
holder.start()

#lock_release(lock)
release = threading.Thread(target=lock_release, args=(lock,), name='release')
release.start()

holder = threading.Thread(target=lock_holder, args=(lock,), name='LockHolder')
holder.setDaemon(True)
holder.start()

奇迹发生了…. 线程b果然可以释放线程a的锁,颠覆人生观啊.

PS: 但是Rlock不会被其他线程释放,因为它记录该线程的所有者

前言

今天是在Ad的最后一天,本来准备了一个分享.关于业务中一些吐槽和我一些trick的用法, 有兴趣的可以下载speakerdeck

主题

  1. celery celery2/celery3, py-amqp, kombu的用法, celery和djangocelery的集合
  2. expect 使用expect自动登录复杂的服务器
  3. mapreduce 一个并行处理文件的例子,说明使用python跑mapreduce多么厉害
  4. portforward 端口转发
  5. restapi 我眼中的restapi(pdf)
  6. tornado 使用tornado一部非阻塞

演示的tmux脚本:

#!/bin/bash
SESSION=$USER
COMMAND='http Space http://localhost:8000/sleep'

tmux new-session -d -s $SESSION

tmux new-window -t $SESSION -n 'Logs'
tmux split-window -h
tmux select-pane -t 0
tmux send-keys $COMMAND C-m
tmux select-pane -t 1
tmux send-keys $COMMAND C-m
tmux split-window -v
tmux send-keys $COMMAND C-m
tmux select-pane -t 0
tmux split-window -v
tmux send-keys $COMMAND C-m
# Attach to session
tmux attach-session -t $SESSION

前言

我以前在学习python模块的时候,曾经翻译pymotw的文章,其实还是有抄袭的嫌疑,从最近开始逐渐直接阅读python标准库源码, 收获颇多. 我现在不愿意教一些从网上或者书里提到的知识点,而更愿意根据我工作中常见的需求去挖掘对应的python的解法.也是在过程中对一些东西有了比较深的理解. 这个ppt是从像黑客一样使用 Linux 命令行获得的灵感. 然后角度为, 还用到了webfonts娃娃体^.^

PS: 特别推荐github上看到的”雨痕”的学习笔记. 建议大家都好好看看.

找到它

Expert-Python 或者直接下载代码: github

但是注意我的字体内嵌项目里面, 请注意流量,避免移动设备直接访问或者强制刷新

目录

  1. XX不理解python竟然没有end….
  2. 设置全局变量
  3. 字符串格式化
  4. 操作列表
  5. 操作字典
  6. 字典视图
  7. vars
  8. from future import unicode_literals
  9. from future import absolute_import
  10. 不是支持了绝对引入,而是拒绝隐式引入
  11. 我靠,我的需求呢? – 在很多开源项目是拒绝你第一次的隐式用法的,
  12. 一个关于编码的问题
  13. 原因是: encoding_example里面没有对文字自动转化为unicode,默认是ascii编码
  14. super 当子类调用父类属性时一般的做法是这样
  15. super的一种用法
  16. 假如不用super会这么惨
  17. 手写一个迭代器
  18. 标准迭代器
  19. 生成器
  20. 斐波那契数列
  21. 其实yield和协程关系很密切
  22. 来个回调(阻塞的)
  23. 来个回调(异步的)
  24. 看到这里, 就得说说contextmanager
  25. 包导入
  26. 包构建__all__
  27. 包构建__path__
  28. 静态方法和类方法的区别
  29. 静态方法和类方法的区别其实是在这里
  30. __slots__
  31. Packaging Tools的未来
  32. wheel(即将替代Eggs的二进制包格式)的优点
  33. 装饰器
  34. 给函数的类装饰器
  35. 给类的函数装饰器
  36. 带参数的装饰器
  37. @property
  38. @property的另外使用方法
  39. 元类是什么
  40. 模拟生成一个类
  41. 元类: __metaclass__(实现前面的Hello类)
  42. 一个难懂的元类
  43. 描述符
  44. 模块: itertools
  45. 模块: collections(一)
  46. 模块: collections(二)
  47. 模块: collections(三)
  48. 模块: collections(四)
  49. operator模块(一)
  50. operator模块(二)
  51. operator模块(三)
  52. functools模块之partial
  53. functools模块之wraps
  54. functools模块之cmp_to_key
  55. functools模块之total_ordering
  56. 开发陷阱(一) 可变默认参数
  57. 开发陷阱(二) 闭包变量绑定
  58. 开发陷阱(二) 闭包应该的用法
  59. 在合适的地方用合适的技巧
  60. 不是它不好,而是你没有用好
  61. ipython的技巧(一)
  62. ipython的技巧(二)
  63. 联系方式

UPDATE 2014.04.11

今天下午分享了这个ppt. 并且用quicktime录像. 想听的可以从百度网盘下载或者在线看(793.6M). 时长2小时零一分.

中间有个列表去重. 有同学说去重后无法保证第一次出现重复数据位置的顺序.

刚才想起来试了一下:

>>> l = [1, 2, 4, 7, 2, 1, 8, 6, 1]
    >>> list(set(l))
    [1, 2, 4, 6, 7, 8]
    >>> {}.fromkeys(l).keys()
    [1, 2, 4, 6, 7, 8]  # 注意这个和上面结果是一样的,也就是内部实现的去重原理相同
    >>> l = ['a', 'b', 'c', 'd', 'b', 'a']
    >>> list(set(l))
    ['a', 'c', 'b', 'd']
    >>> {}.fromkeys(l).keys()
    ['a', 'c', 'b', 'd']
    >>> from collections import OrderedDict
    >>> OrderedDict().fromkeys(l).keys()  # 只能使用这样的方法实现保证顺序的实现
    [1, 2, 4, 7, 8, 6]                                 # 感谢@杨博的提醒

新的PYPI的DEMO: http://pypi-preview.a.ssl.fastly.net

前言

在豆瓣开源项目里面有个graph-index, 提供监控服务器的状态的目录索引,基于graph-explorer. 类似衍生物很多,就包括我要说的本文用到的项目.先看看我的测试环境的几个截图

一些关键词说明

  1. graphite-web # graphite组件之一, 提供一个django的可以高度扩展的实时画图系统
  2. Whisper # graphite组件之一, 实现数据库存储. 它比rrdtool要慢,因为whisper是使用python写的,而rrdtool是使用C写的。然而速度之间的差异很小
  3. Carbon # 数据收集的结果会传给它, 它会解析数据让它可用于实时绘图. 它默认可会提示一些类型的数据,监听2003和2004端口
  4. Diamond # 他是一个提供了大部分数据收集结果功能的结合,类似cpu, load, memory以及mongodb,rabbitmq,nginx等指标.这样就不需要我大量的写各种类型,因为它都已经提供,并且它提供了可扩展的自定义类型(最后我会展示一个我自己定义的类型)
  5. grafana # 这个面板是基于node, kibana,并且可以在线编辑. 因为是kibana,所以也用到了开元搜索框架elasticsearch

PS: 其他工具可以参考这里Tools That Work With Graphite

原理解析

我没有看实际全部代码,大概的流程是这样的:

  1. 启动Carbon-cache等待接收数据(carbon用的是twisted)
  2. 启动graphite-web给grafana提供实时绘图数据api
  3. 启动grafana,调用graphite-web接口获取数据展示出来
  4. Diamond定期获取各类要监测的类型数据发给carbon(默认是5分钟,默认一小时自动重载一次配置)

实现我这个系统需要做的事情

安装graphite相关组件(我这里用的是centos)
yum --enablerepo=epel install graphite-web python-carbon -y
安装grafana需要的组件
# 增加elasticsearch的repo:
sudo  rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch
$cat /etc/yum.repos.d/elasticsearch.repo
[elasticsearch-1.0]
name=Elasticsearch repository for 1.0.x packages
baseurl=http://packages.elasticsearch.org/elasticsearch/1.0/centos
gpgcheck=1
gpgkey=http://packages.elasticsearch.org/GPG-KEY-elasticsearch
enabled=1
sudo yum install nginx nodejs npm java-1.7.0-openjdk elasticsearch -y
下载Diamond和grafana
git clone https://github.com/torkelo/grafana
cd grafana
sudo npm install
sudo pip install django-cors-headers configobj # 这可能因为我环境中已经有了一些模块,看缺什么安装什么
git clone https://github.com/BrightcoveOS/Diamond
cd Diamond

##### 开始修改配置

  1. 添加cors支持

在/usr/lib/python2.6/site-packages/graphite/app_settings.py:

INSTALLED_APPS里面添加corsheaders, MIDDLEWARE_CLASSES里面添加’corsheaders.middleware.CorsMiddleware’

  1. 使用nginx使用grafana

在nginx.conf 添加类型的一段配置

server {
  listen                *:80 ;

  server_name           monitor.dongwm.com; # 我用了虚拟主机
  access_log            /var/log/nginx/kibana.myhost.org.access.log;

  location / {
    add_header 'Access-Control-Allow-Origin' "$http_origin";
    add_header 'Access-Control-Allow-Credentials' 'true';
    root  /home/operation/dongwm/grafana/src;
    index  index.html  index.htm;
  }

  location ~ ^/_aliases$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/_nodes$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/.*/_search$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }
  location ~ ^/.*/_mapping$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
  }

  # Password protected end points
  location ~ ^/kibana-int/dashboard/.*$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
    limit_except GET {
      proxy_pass http://127.0.0.1:9200;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/dongwm.htpasswd;
    }
  }
  location ~ ^/kibana-int/temp.*$ {
    proxy_pass http://127.0.0.1:9200;
    proxy_read_timeout 90;
    limit_except GET {
      proxy_pass http://127.0.0.1:9200;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/dongwm.htpasswd;
    }
  }
  1. 修改grafana的src/config.js:

graphiteUrl: “http://”+window.location.hostname+”:8020”, # 下面会定义graphite-web启动在8020端口

  1. 修改Diamond的配置conf/diamond.conf
cp conf/diamond.conf.example conf/diamond.conf

主要修改监听的carbon服务器和端口,以及要监控什么类型的数据,看我的一个全文配置

################################################################################
# Diamond Configuration File
################################################################################

################################################################################
### Options for the server
[server]

# Handlers for published metrics.
handlers = diamond.handler.graphite.GraphiteHandler, diamond.handler.archive.ArchiveHandler

# User diamond will run as
# Leave empty to use the current user
user =

# Group diamond will run as
# Leave empty to use the current group
group =

# Pid file
pid_file = /home/dongwm/logs/diamond.pid # 换了pid的地址,因为我的服务都不会root启动

# Directory to load collector modules from
collectors_path = /home/dongwm/Diamond/src/collectors # 收集器的目录,这个/home/dongwm/Diamond就是克隆代码的地址

# Directory to load collector configs from
collectors_config_path = /home/dongwm/Diamond/src/collectors

# Directory to load handler configs from
handlers_config_path = /home/dongwm/Diamond/src/diamond/handler

handlers_path = /home/dongwm/Diamond/src/diamond/handler

# Interval to reload collectors
collectors_reload_interval = 3600 # 收集器定期会重载看有没有配置更新

################################################################################
### Options for handlers
[handlers]

# daemon logging handler(s)
keys = rotated_file

### Defaults options for all Handlers
[[default]]

[[ArchiveHandler]]

# File to write archive log files
log_file = /home/dongwm/logs/diamond_archive.log

# Number of days to keep archive log files
days = 7

[[GraphiteHandler]]
### Options for GraphiteHandler

# Graphite server host
host = 123.126.1.11

# Port to send metrics to
port = 2003

# Socket timeout (seconds)
timeout = 15

# Batch size for metrics
batch = 1

[[GraphitePickleHandler]]
### Options for GraphitePickleHandler

# Graphite server host
host = 123.126.1.11

# Port to send metrics to
port = 2004

# Socket timeout (seconds)
timeout = 15

# Batch size for pickled metrics
batch = 256

[[MySQLHandler]]
### Options for MySQLHandler

# MySQL Connection Info 这个可以你的会不同
hostname    = 127.0.0.1
port        = 3306
username    = root
password    =
database    = diamond
table       = metrics
# INT UNSIGNED NOT NULL
col_time    = timestamp
# VARCHAR(255) NOT NULL
col_metric  = metric
# VARCHAR(255) NOT NULL
col_value   = value

[[StatsdHandler]]
host = 127.0.0.1
port = 8125

[[TSDBHandler]]
host = 127.0.0.1
port = 4242
timeout = 15

[[LibratoHandler]]
user = user@example.com
apikey = abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01

[[HostedGraphiteHandler]]
apikey = abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01
timeout = 15
batch = 1

# And any other config settings from GraphiteHandler are valid here

[[HttpPostHandler]]

### Urp to post the metrics
url = http://localhost:8888/
### Metrics batch size
batch = 100


################################################################################
### Options for collectors
[collectors]
[[TencentCollector]] # 本来[collectors]下试没有东西的,这个是我定制的一个类型
ttype = server
[[MongoDBCollector]] # 一般情况,有一些类型是默认enabled = True,也就是启动的,但是大部分是默认不启动《需要显示指定True
enabled = True
host = 127.0.0.1 # 每种类型的参数不同
[[TCPCollector]]
enabled = True
[[NetworkCollector]]
enabled = True
[[NginxCollector]]
enabled = False # 没开启nginx_status 开启了也没用
[[ SockstatCollector]]
enabled = True
[[default]]
### Defaults options for all Collectors

# Uncomment and set to hardcode a hostname for the collector path
# Keep in mind, periods are seperators in graphite
# hostname = my_custom_hostname

# If you prefer to just use a different way of calculating the hostname
# Uncomment and set this to one of these values:

# smart             = Default. Tries fqdn_short. If that's localhost, uses hostname_short

# fqdn_short        = Default. Similar to hostname -s
# fqdn              = hostname output
# fqdn_rev          = hostname in reverse (com.example.www)

# uname_short       = Similar to uname -n, but only the first part
# uname_rev         = uname -r in reverse (com.example.www)

# hostname_short    = `hostname -s`
# hostname          = `hostname`
# hostname_rev      = `hostname` in reverse (com.example.www)

# hostname_method = smart

# Path Prefix and Suffix
# you can use one or both to craft the path where you want to put metrics
# such as: %(path_prefix)s.$(hostname)s.$(path_suffix)s.$(metric)s
# path_prefix = servers
# path_suffix =

# Path Prefix for Virtual Machines
# If the host supports virtual machines, collectors may report per
# VM metrics. Following OpenStack nomenclature, the prefix for
# reporting per VM metrics is "instances", and metric foo for VM
# bar will be reported as: instances.bar.foo...
# instance_prefix = instances

# Default Poll Interval (seconds)
# interval = 300

################################################################################
### Options for logging
# for more information on file format syntax:
# http://docs.python.org/library/logging.config.html#configuration-file-format

[loggers]

keys = root

# handlers are higher in this config file, in:
# [handlers]
# keys = ...

[formatters]

keys = default

[logger_root]

# to increase verbosity, set DEBUG
level = INFO
handlers = rotated_file
propagate = 1

[handler_rotated_file]

class = handlers.TimedRotatingFileHandler
level = DEBUG
formatter = default
# rotate at midnight, each day and keep 7 days
args = ('/home/dongwm/logs/diamond.log', 'midnight', 1, 7)

[formatter_default]

format = [%(asctime)s] [%(threadName)s] %(message)s
datefmt =
启动相关服务
sudo /etc/init.d/nginx reload
sudo /sbin/chkconfig --add elasticsearch
sudo service elasticsearch start
sudo service carbon-cache restart
sudo python /usr/lib/python2.6/site-packages/graphite/manage.py runserver 0.0.0.0:8020 # 启动graphite-web到8020端口
在每个要搜集信息的agent上面安装Diamond,并启动:
cd /home/dongm/Diamond
python ./bin/diamond --configfile=conf/diamond.conf

# PS: 也可以添加 -l -f在前台显示
自定义数据搜集类型,也就是上面的TencentCollector
# coding=utf-8 

"""
获取腾讯微博爬虫的业务指标
"""

import diamond.collector
import pymongo
from pymongo.errors import ConnectionFailure


class TencentCollector(diamond.collector.Collector): # 需要继承至diamond.collector.Collector
    PATH = '/home/dongwm/tencent_data'
    
    def get_default_config(self):
        config = super(TencentCollector, self).get_default_config()
        config.update({
            'enabled':  'True',
            'path':     'tencent',
            'method':   'Threaded',
            'ttype':    'agent' # 服务类型 包含agent和server
        })
        return config

    def collect(self):
        ttype = self.config['ttype']
        if ttype == 'server':
            try:
                db = pymongo.MongoClient()['tmp']
            except ConnectionFailure:
                return
            now_count = db.data.count()
            try:
                last_count = db.diamond.find_and_modify(
                    {}, {'$set': {'last': now_count}}, upsert=True)['last']
            except TypeError:
                last_count = 0
            self.publish('count', now_count)
            self.publish('update', abs(last_count - now_count))
        if ttype == 'agent':
            # somethings..........
添加你要绘图的类型. 这个就是打开grafana, 添加不同的row.给每个添加panel.选择metric的类型就好了

前言

过年在家无聊, 发现个挺有意思的项目: linux-dash,就是使用Twitter的Bootstrap做的管理模板,可以显示服务器信息, 负载, 内存,进程,硬盘,用户,安装/未安装的软件信息,网卡IP,网速,以及网络状态,在线用户等. 但是有2个问题:

  1. 它是php的…
  2. 它获取信息都是通过shell下得系统命令

我就用django写了一个python版的django-linux-dash:本来想用flask,结果被人用了,但是问题也是通过subprocess调用命令获取数据,这个轮子有以下优点:

  1. 不仅支持linux,也支持OS X
  2. 使用psutil, 项目完全不调用shell命令

安装和使用

需要django>=1.4以及psutils模块

$pip install/easy_install django
$pip install/easy_install psutil

PS: OS X 还需要netifaces模块用于获取网卡ip

$pip install/easy_install netifaces

启动:

$cd /You/install/path
$git clone https://github.com/dongweiming/django-linux-dash && cd django-linux-dash
$python manage.py runserver 0.0.0.0:8000

打开浏览器输入 http://localhost:8000 就可以看见了…

TODO:

  1. 目前还没有添加测速功能,因为我希望不要一直傻瓜式的下载某文件,根据用时计算平均值,因为第一它需要时间才会显示个速度,其次是不实时不能循环实时
  2. 添加更多信息模块
  3. 增加用户登陆和权限控制 …

前言

mapreduce在我的理解里一直都是java等语言的专利,介于python乃至于pypy的性能局限, 一直没想过用python写分布式任务,最多就是多workers从消息队列取任务执行这样,但是最近一件事真的颠覆 了我对python的认识.

先说说起因

某天分享sed和awk,领导突发奇想让我用一些顾问的实际工作需要去我们的大量数据里面获取想要的数据的需求作为一些演示的例子.其中有这样一个需求(我去掉实际一些专业晦涩的用语,用实际的内容来表达):

需求
1. 有大量的gz压缩文件, 找到其中某2天的数据, 每一行都是一条实际数据
2. 需要解压缩每个文件,遍历每行找到用逗号隔开的第21列为16233,23列为27188的行. 以第2列为键计算符合的数量
3. 在全部统计结果里面根据值计算符合的键的数量: 比如{'a':2, 'b':1, 'c':1},结果就是{1:2, 2:1},也就是2次的有2,1次的只有一个
分析

一上来真的想用awk来搞.但是和其他同事一聊,有几个难点:

1. 2天数据总量在400G以上,awk还要保留2次哈希结果-不可能用awk
2. python,据同事经验说:只是解压缩这些小文件后读取什么都不做也大概1天多的时间,完全不能忍
3. 数据还没有放到hadoop, 没有其他更好更快的方法
解题思路:
  1. 最初我想做成这样:

    1. 把需要处理的这些压缩文件放到队列里面
    2. 启动多进程出队列里面获取要处理的文件,执行,把符合的结果放到共享变量叠加
    3. 计算完成后从共享变量里面或者数据在生成上面第三条的结果

但是今天讲的是python得mapreduce,也就是我后续的版本,它源于伟大的Doug Hellmann的Implementing MapReduce with multiprocessing

#!/usr/bin/env python
#coding=utf-8
# python mapreduce 跑数实现
# Author: Dongweiming
import gzip
import time
import os
import glob
import collections
import itertools
import operator
import multiprocessing


class AdMapReduce(object):

    def __init__(self, map_func, reduce_func, num_workers=None):
        '''
        num_workers: 不指定就是默认可用cpu的核数
        map_func: map函数: 要求返回格式类似:[(a, 1), (b, 3)]
        reduce_func: reduce函数: 要求返回格式类似: (c, 10)
        '''
        self.map_func = map_func
        self.reduce_func = reduce_func
        self.pool = multiprocessing.Pool(num_workers)

    def partition(self, mapped_values):
        partitioned_data = collections.defaultdict(list)
        for key, value in mapped_values:
            partitioned_data[key].append(value)
        return partitioned_data.items()

    def __call__(self, inputs, chunksize=1):
        '''调用类的时候被触发'''
        # 其实都是借用multiprocessing.Pool.map这个函数, inputs是一个需要处理的列表,想想map函数
        # chunksize表示每次给mapper的量, 这个根据需求调整效率
        map_responses = self.pool.map(self.map_func, inputs, chunksize=chunksize)
        # itertools.chain是把mapper的结果链接起来为一个可迭代的对象
        partitioned_data = self.partition(itertools.chain(*map_responses))
        # 大家想,上面的就是[(a, [1,2]), (b, [2,3]),列表中的数就是当时符合的次数,reduce就是吧列表符合项sum
        reduced_values = self.pool.map(self.reduce_func, partitioned_data)
        return reduced_values


def mapper_match(one_file):
    '''第一次的map函数,从每个文件里面获取符合的条目'''
    output = []
    for line in gzip.open(one_file).readlines():
        l = line.rstrip().split(',')
        if int(l[20]) == 16309 and int(l[22]) == 2656:
            cookie = l[1]
            output.append((cookie, 1))
    return output


def reduce_match(item):
    '''第一次的reduce函数,给相同的key做统计'''
    cookie, occurances = item
    return (cookie, sum(occurances))


def mapper_count(item):
    '''第二次mapper函数,其实就是把某key的总数做键,但是值标1'''
    _, count = item
    return [(count, 1)]


def reduce_count(item):
    '''第二次reduce函数'''
    freq, occurances = item
    return (freq, sum(occurances))


if __name__ == '__main__':
    start = time.time()
    input_files = glob.glob('/datacenter/input/2013-12-1[01]/*')
    mapper = AdMapReduce(mapper_match, reduce_match)
    cookie_feq = mapper(input_files)
    mapper = AdMapReduce(mapper_count, reduce_count)
    cookie_feq = mapper(cookie_feq)
    cookie_feq.sort(key=operator.itemgetter(1))
    for freq, count in cookie_feq:
        print '{0}\t{1}\t{2}'.format(freq, count, freq*count)
    #cookie_feq.reverse()
    end = time.time()
    print 'cost:', end - start

后话

哇,看python做mapreduce也是可以这样优雅的, 我是用pypy跑下来,竟然只有了61分钟….

但是其实他只是借助mapreduce思想和多核的硬件基础,其实pool做的还是文件级别的处理.假如是少量的大文件,就未必有这样好的效果了.

我想很多时候这样的工作都可以交给这个Admapreduce类来做

前言

最近做一个关于sed和awk的分享,这里把源码开源:sed_and_awk,或者直接访问http://dongweiming.github.io/sed_and_awk. 我这个ppt基本覆盖90%以上的知识点.

一些说明

我测试例子都是在osx下,freebsd的sed和awk和gnu的都略有不同.甚至osx下得版本都不能使用,我会在注释中说明.

  • sed

    1. sed 通用
    2. /usr/local/bin/sed osx下编译的gnu sed
  • awk

    1. awk 通用
    2. gawk osx编译的gnu awk