Follower me on GitHub

今天我们学习下写ipython的magic命令. 好, magic是什么? 它是ipython自带的一些扩展命令, 类似%history, %prun, %logstart..

想查看全部的magic可以使用ismagic, 列出可用的全部magics

%lsmagic

magic分为2类:

  • line magic: 一些功能命令
  • cell magic: 主要是渲染ipython notebook页面效果以及执行某语言的代码
idb - python db.py shell extension

idb是我最近写的一个magic. 主要是给ipython提供db.py的接口,我们直接分析代码(我只截取有代表性的一段):

import os.path
from functools import wraps
from operator import attrgetter
from urlparse import urlparse

from db import DB # db.py提供的接口
from IPython.core.magic import Magics, magics_class, line_magic # 这三个就是我们需要做magic插件的组件


def get_or_none(attr):
    return attr if attr else None


def check_db(func):
    @wraps(func)
    def deco(*args):
        if args[0]._db is None: # 每个magic都需要首页实例化过db,so 直接加装饰器来判断
            print '[ERROR]Please make connection: `con = %db_connect xx` or `%use_credentials xx` first!'  # noqa
            return
        return func(*args)
    return deco


@magics_class  # 每个magic都需要加这个magics_class装饰器
class SQLDB(Magics): # 要继承至Magics
    _db = None # 每次打开ipython都是一次实例化

    @line_magic('db_connect') # 这里用了line_magic 表示它是一个line magic.(其他2种一会再说) magic的名字是db_connect. 注意 函数名不重要
                              # 最后我们用 %db_connect而不是%conn
    def conn(self, parameter_s): # 每个这样的方法都接收一个参数 就是你在ipython里输入的内容
        """Conenct to database in ipython shell.
        Examples::
            %db_connect
            %db_connect postgresql://user:pass@localhost:port/database
        """
        uri = urlparse(parameter_s) # 剩下的都是解析parameter_s的逻辑

        if not uri.scheme:
            params = {
                'dbtype': 'sqlite',
                'filename': os.path.join(os.path.expanduser('~'), 'db.sqlite')
            }
        elif uri.scheme == 'sqlite':
            params = {
                'dbtype': 'sqlite',
                'filename': uri.path
            }
        else:
            params = {
                'username': get_or_none(uri.username),
                'password': get_or_none(uri.password),
                'hostname': get_or_none(uri.hostname),
                'port': get_or_none(uri.port),
                'dbname': get_or_none(uri.path[1:])
            }

        self._db = DB(**params) # 这里给_db赋值

        return self._db # return的结果就会被ipython接收,显示出来

    @line_magic('db') # 一个新的magic 叫做%db -- 谨防取名冲突
    def db(self, parameter_s):
        return self._db

    @line_magic('table')
    @check_db
    def table(self, parameter_s):
        p = parameter_s.split() # 可能传进来的是多个参数,但是对ipython来说,传进来的就是一堆字符串,所以需要按空格分隔下
        l = len(p)
        if l == 1:
            if not p[0]:
                return self._db.tables
            else:
                return attrgetter(p[0])(self._db.tables)
        else:
            data = self._db.tables
            for c in p:
                if c in ['head', 'sample', 'unique', 'count', 'all', 'query']:
                    data = attrgetter(c)(data)()
                else:
                    data = attrgetter(c)(data)
            return data

def load_ipython_extension(ipython): # 注册一下. 假如你直接去ipython里面加 就不需要这个了
    ipython.register_magics(SQLDB)

PS:

  1. 调试中可以使用%reloa_ext idb 的方式重启magic
  2. %install_ext 之后默认放在你的ipython自定义目录/extensions里. 我这里是~/.ipython/extensions

好了,大家是不是觉得ipython的magic也不是很难嘛

来了解ipython都提供了什么?
  1. magic装饰器的类型:
  • line_magic # 刚才我们见识了, 就是%xx, xx就是magic的名字
  • cell_magic # 就是%%xx
  • line_cell_magic # 可以是%xx, 也可以是%%xx

先说cell_magic 来个例子,假如我想执行个ruby,本来应该是:

In [1]: !ruby -e 'p "hello"'
"hello"

In [2]: %%ruby # 也可以这样
   ...: p "hello"
      ...:
      "hello"

再说个notebook:

In [3]: %%javascript
   ...: require.config({
   ...:     paths: {
   ...:         chartjs: '//code.highcharts.com/highcharts'
   ...:     }
   ...: });
   ...:
   <IPython.core.display.Javascript object>
});

然后再说line_cell_magic:

In [4]: %time 2**128
CPU times: user 2 µs, sys: 1 µs, total: 3 µs
Wall time: 5.01 µs
Out[4]: 340282366920938463463374607431768211456L

In [5]: %%time
   ...: 2**128
   ...:
   CPU times: user 4 µs, sys: 0 ns, total: 4 µs
   Wall time: 9.06 µs
   Out[5]: 340282366920938463463374607431768211456L

Ps: line_cell_magic方法的参数是2个:

@line_cell_magic
def xx(self, line='', cell=None):
带参数的magic(我直接拿ipython源码提供的magic来说明):

一共2种风格:

  • 使用getopt: self.parse_options
  • 使用argparse: magic_arguments
self.parse_options
@line_cell_magic
def prun(self, parameter_s='', cell=None):
    opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
                                       list_all=True, posix=False)
    ...

getopt用法可以看这里 http://pymotw.com/2/getopt/index.html#module-getopt

我简单介绍下’D:l:rs:T:q’就是可以使用 -D, -l, -r, -s, -T, -q这些选项. :号是告诉你是否需要参数,split下就是: D:,l:,r,s:,T:,q 也就是-r和-q不需要参数其他的都是参数 类似 %prun -D

magic_arguments
@magic_arguments.magic_arguments() # 最上面
@magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
    help="""
    Set break point at LINE in FILE.
    """
) # 这种argument可以有多个
@magic_arguments.argument('statement', nargs='*',
    help="""
    Code to run in debugger.
    You can omit this in cell magic mode.
    """
)
@line_cell_magic
def debug(self, line='', cell=None):
    args = magic_arguments.parse_argstring(self.debug, line) # 要保持第一个参数等于这个方法名字,这里就是self.debug
    ...

还有个magic方法集: 用于并行计算的magics: IPython/parallel/client/magics.py

前言

以前在我的PPTpython高级编程也提到了一些关于ipython的用法. 今天继续由浅入深的看看ipython, 本文假设作为读者的你已经知道ipython并且用了一段时间了.

%run

这是一个magic命令, 能把你的脚本里面的代码运行, 并且把对应的运行结果存入ipython的环境变量中:

$cat t.py
# coding=utf-8
l = range(5)

$ipython
In [1]: %run t.py # `%`可加可不加

In [2]: l # 这个l本来是t.py里面的变量, 这里直接可以使用了
Out[2]: [0, 1, 2, 3, 4]
alias
In [3]: %alias largest ls -1sSh | grep %s
In [4]: largest to
total 42M
 20K tokenize.py
 16K tokenize.pyc
8.0K story.html
4.0K autopep8
4.0K autopep8.bak
4.0K story_layout.html

PS 别名需要存储的, 否则重启ipython就不存在了:

In [5]: %store largest
Alias stored: largest (ls -1sSh | grep %s)

下次进入的时候%store -r

bookmark - 对目录做别名
In [2]: %pwd
Out[2]: u'/home/vagrant'

In [3]: %bookmark dongxi ~/shire/dongxi

In [4]: %cd dongxi
/home/vagrant/shire/dongxi_code

In [5]: %pwd
Out[5]: u'/home/vagrant/shire/dongxi_code'
ipcluster - 并行计算

其实ipython提供的方便的并行计算的功能. 先回答ipython做并行计算的特点:

1.

$wget http://www.gutenberg.org/files/27287/27287-0.txt

第一个版本是直接的, 大家习惯的用法.

In [1]: import re

In [2]: import io

In [3]: non_word = re.compile(r'[\W\d]+', re.UNICODE)

In [4]: common_words = {
   ...: 'the','of','and','in','to','a','is','it','that','which','as','on','by',
   ...: 'be','this','with','are','from','will','at','you','not','for','no','have',
   ...: 'i','or','if','his','its','they','but','their','one','all','he','when',
   ...: 'than','so','these','them','may','see','other','was','has','an','there',
   ...: 'more','we','footnote', 'who', 'had', 'been',  'she', 'do', 'what',
   ...: 'her', 'him', 'my', 'me', 'would', 'could', 'said', 'am', 'were', 'very',
   ...: 'your', 'did', 'not',
   ...: }

In [5]: def yield_words(filename):
   ...:     import io
   ...:     with io.open(filename, encoding='latin-1') as f:
   ...:         for line in f:
   ...:             for word in line.split():
   ...:                 word = non_word.sub('', word.lower())
   ...:                 if word and word not in common_words:
   ...:                     yield word
   ...:

In [6]: def word_count(filename):
   ...:     word_iterator = yield_words(filename)
   ...:     counts = {}
   ...:     counts = defaultdict(int)
   ...:     while True:
   ...:         try:
   ...:             word = next(word_iterator)
   ...:         except StopIteration:
   ...:             break
   ...:         else:
   ...:             counts[word] += 1
   ...:     return counts
   ...:

In [6]: from collections import defaultdict # 脑残了 忘记放进去了..
In [7]: %time counts = word_count(filename)
CPU times: user 88.5 ms, sys: 2.48 ms, total: 91 ms
Wall time: 89.3 ms

现在用ipython来跑一下:

ipcluster start -n 2 # 好吧, 我的Mac是双核的

先讲下ipython 并行计算的用法:

In [1]: from IPython.parallel import Client # import之后才能用%px*的magic

In [2]: rc = Client()

In [3]: rc.ids # 因为我启动了2个进程
Out[3]: [0, 1]

In [4]: %autopx # 如果不自动 每句都需要: `%px xxx`
%autopx enabled

In [5]: import os # 这里没autopx的话 需要: `%px import os`

In [6]: print os.getpid() # 2个进程的pid
[stdout:0] 62638
[stdout:1] 62636

In [7]: %pxconfig --targets 1 # 在autopx下 这个magic不可用
[stderr:0] ERROR: Line magic function `%pxconfig` not found.
[stderr:1] ERROR: Line magic function `%pxconfig` not found.

In [8]: %autopx # 再执行一次就会关闭autopx
%autopx disabled

In [10]: %pxconfig --targets 1 # 指定目标对象, 这样下面执行的代码就会只在第2个进程下运行

In [11]: %%px --noblock # 其实就是执行一段非阻塞的代码
   ....: import time
   ....: time.sleep(1)
   ....: os.getpid()
   ....:
Out[11]: <AsyncResult: execute>

In [12]: %pxresult # 看 只返回了第二个进程的pid
Out[1:21]: 62636

In [13]: v = rc[:] # 使用全部的进程, ipython可以细粒度的控制那个engine执行的内容

In [14]: with v.sync_imports(): # 每个进程都导入time模块
   ....:     import time
   ....:
importing time on engine(s)

In [15]: def f(x):
   ....:     time.sleep(1)
   ....:     return x * x
   ....:

In [16]: v.map_sync(f, range(10)) # 同步的执行

Out[16]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [17]: r = v.map(f, range(10)) # 异步的执行

In [18]: r.ready(), r.elapsed # celery的用法
Out[18]: (True, 5.87735)

In [19]: r.get() # 获得执行的结果
Out[19]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

入正题:

In [20]: def split_text(filename):
....:    text = open(filename).read()
....:    lines = text.splitlines()
....:    nlines = len(lines)
....:    n = 10
....:    block = nlines//n
....:    for i in range(n):
....:        chunk = lines[i*block:(i+1)*(block)]
....:        with open('count_file%i.txt' % i, 'w') as f:
....:            f.write('\n'.join(chunk))
....:    cwd = os.path.abspath(os.getcwd())
....:    fnames = [ os.path.join(cwd, 'count_file%i.txt' % i) for i in range(n)] # 不用glob是为了精准
....:    return fnames

In [21]: from IPython import parallel

In [22]: rc = parallel.Client()

In [23]: view = rc.load_balanced_view()

In [24]: v = rc[:]

In [25]: v.push(dict(
   ....:     non_word=non_word,
   ....:     yield_words=yield_words,
   ....:     common_words=common_words
   ....: ))
Out[25]: <AsyncResult: _push>

In [26]: fnames = split_text(filename)

In [27]: def count_parallel():
   .....:     pcounts = view.map(word_count, fnames)
   .....:     counts = defaultdict(int)
   .....:     for pcount in pcounts.get():
   .....:         for k, v in pcount.iteritems():
   .....:             counts[k] += v
   .....:     return counts, pcounts
   .....:

In [28]: %time counts, pcounts = count_parallel() # 这个时间包含了我再聚合的时间
CPU times: user 47.6 ms, sys: 6.67 ms, total: 54.3 ms # 是不是比直接运行少了很多时间?
Wall time: 106 ms # 这个时间是

In [29]: pcounts.elapsed, pcounts.serial_time, pcounts.wall_time
Out[29]: (0.104384, 0.13980499999999998, 0.104384)

更多地关于并行计算请看这里: Parallel Computing with IPython

前言

A Guide to Python’s Magic Methodspython的绝大多数这样的特殊方法都 在这里面被提到了. 今天我来说3个他没有提到的[__dir__, __slots__, __weakref__], 再强调下他提到的2个[__missing__, __contains__]

__dir__ -> 看个小例子就知道了
In [1]: class T(object):
   ...:     pass
   ...:
In [2]: t = T()
In [3]: t.<Tab>
啥也没有...
In [4]: class T2(object):
   ...:     def __dir__(self):
   ...:         return ['a', 'b']
   ...:
In [5]: t = T2()
In [6]: t.
t.a  t.b
In [7]: dir(t)
Out[7]: ['a', 'b']

看出来了把, 不解释, 但是这个__dir__是相对于类的实例有效果的.

__slots__

这个在我初学python的时候就被模糊了, 原来的理解是它的出现替代了__dict__,也就是说你只能给__slots__ 这个变量列表项的属性赋值. 对外的接口减少了,也安全了. 后来看了这篇Saving 9 GB of RAM with Python’s slots. 好久不做运维了,在生产环境究竟怎么样我无法定论, 也提到了,在对象实例很多的时候他能帮助减少内存, 详见https://www.safaribooksonline.com/library/view/python-cookbook-3rd/9781449357337/ch08s04.html. 这里来个小实验(在Hacker News也被讨论过https://news.ycombinator.com/item?id=6750187)

代码例子(我对细节做注释):

# coding=utf-8
import sys
from itertools import starmap, product


class SlotTest(object):
    # __slots__ = ['x', 'y', 'z'] 主要对比去掉这句和包含这句程序内存占用

    def __init__(self, x, y, z):
            self.x = x
                    self.y = y
                            self.z = z

    def __str__(self):
            return "{} {} {}".format(self.x, self.y, self.z)

p = product(range(10000), range(20), [4]) # 创建0-1000 & 0-20 & 4 的笛卡尔积
a = list(starmap(SlotTest, p)) # 相当于对每个SlotTest实例化,实例化的格式是p的长度

print a[0]
sys.stdin.read(1)

结果对比:

$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 未使用__slots__
  total kB          103496   76480   73728
$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 使用了__slots__
  total kB           49960   22888   20136

结果很明显,内存占用减少了很多…

__weakref__ 弱引用

首先先说下weakref: 弱引用,与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收. 在Python中,当一个对象的引用数目为0的时候,才会被从内存中回收. 但是被循环引用呢?

In [1]: import weakref

In [2]: import gc

In [3]: class Obj(object):
   ...:     def a(self):
   ...:         return 1
   ...:
In [4]: obj = Obj()

In [5]: s = obj

In [6]: gc.collect() # 不可达引用对象的数量
Out[6]: 3

In [7]: print s is obj
True

In [8]: obj = 1 # 最初的被引用的对象改变了.

In [9]: gc.collect()
Out[9]: 0

In [10]: s is None # s还是指向了Obj 引用计数为1
Out[10]: False

In [11]: s
Out[11]: <__main__.Obj at 0x2b36510>

----华丽的分割一下

In [12]: obj = Obj()

In [13]: r = weakref.ref(obj) # 让obj变成那个弱引用

In [14]: gc.collect()
Out[14]: 211

In [15]: r() is obj
True

In [16]: obj = 1

In [17]: gc.collect()
Out[17]: 0

In [18]: r() is None # 弱引用计数器没有增加,所以当obj不在引用Obj的时候,Obj对象就被释放了
Out[18]: True

好吧, 我的总结是弱引用是个好东西, 但是加了__slots__就不支持弱引用了. 所以需要__weakref__

In [9]: class T3(object):
   ...:     __slots__ = []
      ...:

In [10]: class T4(object):
   ....:     __slots__ = '__weakref__'  # 这样就支持了weakref
      ....:

In [11]:  import weakref

In [12]: t3 = T3()

In [13]: t4 = T4()

In [14]: weakref.ref(t3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-bdb7ab7ac3bc> in <module>()
----> 1 weakref.ref(t3)

TypeError: cannot create weak reference to 'T3' object

In [15]: weakref.ref(t4)
Out[15]: <weakref at 0x2766f70; to 'T4' at 0x2586fd8>
__contains__ 判断某值 in/not in 实例
In [1]: class NewList(object):
   ...:     def __init(self, values):
   ...:         self.values = values
   ...:     def __contains__(self, value):
   ...:         return value in self.values
   ...:
In [2]: l = NewList([1, 2, 3, 4])

In [3]: 4 in l
Out[3]: True

In [4]: 10 in l
Out[4]: False
__missing__

最初看这个特殊方法是看python标准库的源码的时候(collections#L421):

class Counter(dict):
    ...

    def __missing__(self, key):
        'The count of elements not in the Counter is zero.'
        # Needed so that self[missing_item] does not raise KeyError
        return 0

什么意思呢?

In [6]: c = collections.Counter({'a':1})

In [7]: c['b'] # 没有键的count设置默认值0
Out[7]: 0

很多人可能看过这个(关于defaultdict的ppt)[http://discorporate.us/jek/talks/defaultdict/]. 内容就不说了, 讲的非常好.

前言

越来越多的人使用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的类型就好了