东东东 陈煜东的博客

Python Subprocess Popen 管道阻塞问题分析解决

使用Subprocess Popen的类库困挠了我一个月的问题终于解决了。

一句话就是:等待命令返回不要使用wait(),而是使用communicate(),但注意内存,大输出使用文件。

错误的使用例子

之前的代码这样使用的。

# 不合适的代码
def run_it(self, cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                         stderr=subprocess.PIPE, close_fds=True)
    log.debug('running:%s' % cmd)
    p.wait()
    if p.returncode != 0:
        log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
    return p.stdout

这段代码之前用着一直没有问题的,后来不知道为何就不能用了(后面知道了,原来输出内容增加,输出的问题本太长,把管道给堵塞了)。

这样的代码也在之前的一个项目中使用,而且调用的次数有上亿次,也没什么问题。之前倒是也卡住了一次,不过有个大神把问题找到了,因为Python版本低于2.7.6,Python对close_fds的一些实现不太好导致的,没有把管道释放掉,一直卡住。设置close_fds=True。不过这个并没有解决我的问题。

解决了我的问题

当时想着既然卡住了,那我就看看是输出了什么才卡住的,结果现有的代码无法支持我的想法,就换了代码,没想到就不卡住了。

def run_it(cmd):
    # _PIPE = subprocess.PIPE
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                         stderr=subprocess.PIPE) #, close_fds=True)

    log.debug('running:%s' % cmd)
    out, err = p.communicate()
    log.debg(out)
    if p.returncode != 0:
        log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
    return p.stdout

看看Python文档信息

Warning

Use communicate() rather than .stdin.write, .stdout.read or .stderr.read to avoid deadlocks due to any of the other OS pipe buffers filling up and blocking the child process.

Popen.wait()
    Wait for child process to terminate. Set and return returncode attribute.

    Warning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.
Popen.communicate(input=None)
    Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child.

    communicate() returns a tuple (stdoutdata, stderrdata).

    Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.

    Note The data read is buffered in memory, so do not use this method if the data size is large or unlimited.

之前没注意,再细看一下文档,感觉豁然开朗。

Linux管道限制,为什么会阻塞呢?

下面来看看Can someone explain pipe buffer deadlock?的回答。

子进程产生一些数据,他们会被buffer起来,当buffer满了,会写到子进程的标准输出和标准错误输出,这些东西通过管道发送给父进程。当管道满了之后,子进程就停止写入,于是就卡住了。

及时取走管道的输出也没有问题

# 及时从管道中取走数据
def run_it(self, cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                         stderr=subprocess.PIPE, close_fds=True)
    log.debug('running:%s' % cmd)
    for line in iter(p.stdout.readline, b''):
        print line,          # print to stdout immediately
    p.stdout.close()
    p.wait()
    if p.returncode != 0:
        log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
    return p.stdout

看了Python的communicate()内部就是将stdout/stderr读取出来到一个list变量中的,最后函数结束时返回。

测试Linux管道阻塞问题

看到别人的例子,一直在想怎么测试输出64K的数据,发现dd这个思路很棒,是见过最优雅的例子了,精确控制输出的长度,其他都是从某些地方搞来大文件导入进来。

#!/usr/bin/env python
# coding: utf-8
# yc@2013/04/28

import subprocess

def test(size):
    print 'start'

    cmd = 'dd if=/dev/urandom bs=1 count=%d 2>/dev/null' % size
    p = subprocess.Popen(args=cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
    #p.communicate()
    p.wait()  # 这里超出管道限制,将会卡住子进程

    print 'end'

# 64KB
test(64 * 1024)

# 64KB + 1B
test(64 * 1024 + 1)

# output :
start
end
start   #  然后就阻塞了。

首先测试输出为 64KB 大小的情况。使用 dd 产生了正好 64KB 的标准输出,由 subprocess.Popen 调用,然后使用 wait() 等待 dd 调用结束。可以看到正确的 start 和 end 输出;然后测试比 64KB 多的情况,这种情况下只输出了 start,也就是说程序执行卡在了 p.wait() 上,程序死锁。

总结

那死锁问题如何避免呢?官方文档里推荐使用 Popen.communicate()。这个方法会把输出放在内存,而不是管道里,所以这时候上限就和内存大小有关了,一般不会有问题。而且如果要获得程序返回值,可以在调用 Popen.communicate() 之后取 Popen.returncode 的值。

但真的如果超过内存了,那么要考虑比如文件 stdout=open("process.out", "w") 的方式来解决了,不能使用管道了。

另外说一下。管道的要用清楚,不要随意的乱世用管道。比如没有input的时候,那么stdin就不要用管道了。

还有不要把简单的事情复杂化。比如echo 1 > /sys/linux/xxx修改文件,这么简单的功能就不要用Linux的shell调用了,使用Python自带的 open('file', 'w').write('1') 。尽量保持Python范。

参考

分类: Python

Disqus已被认证

社交化评论系统Disqus在国外非常的流行,前几个月,我也是从duoshuo转到了Disqus,感觉评论的风格还算简洁,个人感觉也挺喜欢的。

用了一段时间,Disqus用HTTP无法访问了。为此专门研究了一下,原来HTTPS还可以访问,特意加将全站转向了HTTPS。没想到用了几个月,这两天发现评论框出不来了。

找了站长工具超级PING,发现基本一片红色呀。

大陆的访问disqus情况

大陆的访问disqus情况

各个地域的域名DNS解析

各个地域的disqus域名解析

各个地域的disqus域名解析

域名解析出来,已经到了『阿塞拜疆』,一个中东国家。

要想继续使用Disqus,那么只能靠有个海外的服务器进行反向代理了。目前没有购买海外服务器,改天看看有没有能搞一个海外服务器做反向代理。

本来想再用回duoshuo的,但是微博的头像竟然没有HTTPS的连接,想想还是先算了吧。真好搞,网上有很多『多说完美HTTPS』教程,看着搞代理,修改源码,都不太稳定。

分类: 网站建设

Graphviz安装失败的一个尝试

Graphviz来对sphinx进行画图,不过发现一直无法编译出来。

PS:Graphviz的官网在大陆访问非常的慢。

首先参考http://graphviz.org/Download_linux_rhel.php,增加一个repo源,

然后执行yum install 'graphviz*'安装graphviz的软件。

在安装的过程中出现了

Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Hashtbl) = 718cd6ce8bc18371ce22483e362f78b4
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Hashtbl) = 024edc3512403b725052aec8e41ed971
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Array) = 4d5efba91ec70acd7b184fd4b277708c
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Array) = 8a6bb22925744456eb66180ea42e3344
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(List) = bd7c662c09e850306a62c12fed5ef5ce
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(List) = d757117653d9319fefb7ddc78a998f41
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(runtime) = 4.00.1
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(runtime) = 4.01.1
ocaml(runtime) = 4.01.0
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Gc) = 23b8d067f883f7a218c4945a42625a31
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Gc) = 292a1cd61d8e068943589882415bdf7d
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Obj) = ad977b422bbde52cd6cd3b9d04d71db1
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Obj) = b0adfa4175f86e4394859886c1a374bb
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Int32) = 265928798c0b8a63fa48cf9ac202f0ce
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Int32) = ad06f04cfca6d404d1de76c3dc67324a
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Callback) = 6fd6d47b2f6a171a493621bc5edbfb32
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Callback) = 198fb4bcde892143b0866b03cfae8085
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(List) = bd7c662c09e850306a62c12fed5ef5ce
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(List) = d757117653d9319fefb7ddc78a998f41
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Callback) = 6fd6d47b2f6a171a493621bc5edbfb32
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Callback) = 198fb4bcde892143b0866b03cfae8085
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Pervasives) = 4836c254f0eacad92fbf67abc525fdda
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Pervasives) = 36b5bc8227dc9914c6d9fd9bdcfadb45
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Hashtbl) = 718cd6ce8bc18371ce22483e362f78b4
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Hashtbl) = 024edc3512403b725052aec8e41ed971
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Pervasives) = 4836c254f0eacad92fbf67abc525fdda
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Pervasives) = 36b5bc8227dc9914c6d9fd9bdcfadb45
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Array) = 4d5efba91ec70acd7b184fd4b277708c
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Array) = 8a6bb22925744456eb66180ea42e3344
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(runtime) = 4.00.1
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(runtime) = 4.01.1
ocaml(runtime) = 4.01.0
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Int32) = 265928798c0b8a63fa48cf9ac202f0ce
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Int32) = ad06f04cfca6d404d1de76c3dc67324a
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Gc) = 23b8d067f883f7a218c4945a42625a31
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Gc) = 292a1cd61d8e068943589882415bdf7d
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Int64) = 0d5ecd8dffcffac43aec2ebe427d3bde
Available: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Int64) = 3945db6e8df0d5a79bcbc949ee550d52
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Obj) = ad977b422bbde52cd6cd3b9d04d71db1
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Obj) = b0adfa4175f86e4394859886c1a374bb
Error: Package: graphviz-lang-ocaml-2.38.0-1.el7.x86_64 (graphviz-stable)
Requires: ocaml(Int64) = 0d5ecd8dffcffac43aec2ebe427d3bde
Installing: ocaml-runtime-4.01.0-22.6.el7.x86_64 (base)
ocaml(Int64) = 3945db6e8df0d5a79bcbc949ee550d52
You could try using --skip-broken to work around the problem
You could try running: rpm -Va --nofiles --nodigest

看这个论坛说,修改之后,再次安装,果然可以装上。

Just edit the snapshot section so that “enabled=1”, and correspondingly disable the stable section.

[graphviz-snapshot]
name=Graphviz - RHEL $releasever - $basearch
baseurl=http://www.graphviz.org/pub/graphviz/development/redhat/el$releasever/$basearch/os/
enabled=1
gpgcheck=0
skip_if_unavailable=1

[graphviz-snapshot-source]
name=Graphviz - RHEL $releasever - Source
baseurl=http://www.graphviz.org/pub/graphviz/development/SRPMS/
enabled=1
gpgcheck=0
skip_if_unavailable=1

最后安装的dot程序

dot - graphviz version 2.39.20160710.1729 (20160710.1729)
libdir = "/usr/lib64/graphviz"
Activated plugin library: libgvplugin_dot_layout.so.6
Using layout: dot:dot_layout
Activated plugin library: libgvplugin_core.so.6
Using render: dot:core
Using device: dot:dot:core
The plugin configuration file:
        /usr/lib64/graphviz/config6
                was successfully loaded.
    render      :  cairo dot dot_json fig gd json json0 map mp pic pov ps svg tk vml vrml xdot xdot_json
    layout      :  circo dot fdp neato nop nop1 nop2 osage patchwork sfdp twopi
    textlayout  :  textlayout
    device      :  bmp canon cmap cmapx cmapx_np dot dot_json eps fig gd gd2 gif gtk gv ico imap imap_np ismap jpe jpeg jpg json json0 mp pdf pic plain plain-ext png pov ps ps2 svg svgz tif tiff tk vml vmlz vrml wbmp x11 xdot xdot1.2 xdot1.4 xdot_json xlib
    loadimage   :  (lib) bmp eps gd gd2 gif ico jpe jpeg jpg pdf png ps svg xbm

PS:如果有些程序没有安装好,那么没办法生成pdf的格式。

如下面的错误信息,硬是没有pdf的格式。原来是插件没有安装全。

Graphviz produced errors. Verify it has support for filetype=pdf, or use filetype=dot.
Original error: Format: "pdf" not recognized. Use one of: canon cmap cmapx cmapx_np dot eps fig gd gd2 gif gv imap imap_np ismap jpe jpeg jpg plain plain-ext png ps ps2 svg svgz tk vml vmlz vrml wbmp xdot

不过还残留一个没有解决的问题

[stderr]
Warning: flat edge between adjacent nodes one of which has a record shape - replace records with HTML-like labels
  Edge A -> B
Error: lost A B edge

我把两个node放在同一个level,然后做了箭头,报错了,得再研究研究。

分类: Python, 软件

Python重新抛出异常的姿势

异常对于一个语言来说非常重要,异常的栈信息对于开发者特别重要,因为可以根据异常栈来找到第一次抛出异常的地方。但是懂得正确的抛出异常的人不是很多。

首先,以下是最糟糕的

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    revert_stuff()
    raise Exception("some_code failed!")
Traceback (most recent call last):
  File "1.py", line 11, in <module>
    raise Exception("some_code failed!")
Exception: some_code failed!

为什么说是最糟糕呢?因为关于some_code()的失败信息全部都丢失了。不过这些异常堆栈,可能是我们期望的信息,也可能不是。

以下代码是稍微改进过,但还是不是非常好:

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    import traceback
    traceback.print_exc()
    revert_stuff()
    raise Exception("some_code failed!")
Traceback (most recent call last):
  File "2.py", line 8, in <module>
    some_code()
  File "2.py", line 5, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur
Traceback (most recent call last):
  File "2.py", line 13, in <module>
    raise Exception("some_code failed!")
Exception: some_code failed!

使用traceback.print_exc() 把原始的异常堆栈(traceback)打印了出来。从某些角度来看这是最好的方法了,因为可以异常的错误信息找出来。但是如果不想恢复着新异常信息,那么应该这么做:

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    revert_stuff()
    raise
Traceback (most recent call last):
  File "3.py", line 8, in <module>
    some_code()
  File "3.py", line 5, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur

使用不用带任何参数raise可以重新抛出最后的异常。有时人们直接留空从来不用except:语句,但是这个特殊的形式(except: + raise)也是可以的。

有另外一种raise抛出异常的方式,知道的人比较少,但是也很容易上手。类似无参数的raise一样,这种方法也可以保留异常堆栈:

def some_code():
    raise Exception('oops, some error occur')

def maybe_raise(exc_info):
    raise exc_info[0], exc_info[1], exc_info[2]

try:
    some_code()
except:
    import sys
    exc_info = sys.exc_info()
    maybe_raise(exc_info)
Traceback (most recent call last):
  File "4.py", line 12, in <module>
    maybe_raise(exc_info)
  File "4.py", line 8, in <module>
    some_code()
  File "4.py", line 2, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur

如果你需要在异常发生的代码的其他地方处理异常是个不错的选择。但通常它不是很方便,因为这样比较晦涩难懂。

还有一类经常修改异常堆栈的情况是:想加一些额外的信息到异常堆栈中。

for lineno, line in enumerate(file):
    try:
        process_line(line)
    except Exception, exc:
        raise Exception("Error in line %s: %s" % (lineno, exc))

这里保留了异常信息,但却丢失了异常堆栈。有一个方法可以保留异常堆栈。下面的方法不仅可以保留异常,也可以改变异常的信息。

except Exception, exc:
    args = exc.args
    if not args:
        arg0 = ''
    else:
        arg0 = args[0]
    arg0 += ' at line %s' % lineno
    exc.args = arg0 + args[1:]
    raise

有些小尴尬。从技术上讲(虽然它不建议使用),你可以抛出任何的异常。如果使用except Exception:那么就不能捕获一些例如string异常或者其他异常。想要这么用取决于是否关心这些场景。不过异常也可能没有.args属性,或者异常的字符串信息不能从这些参数中获取,又或者有其他的方法展示(例如KeyError信息有些不同 ) 。因此这不是万能的。想要增强版,可以这么做:

except:
    exc_class, exc, tb = sys.exc_info()

exc_class是一个字符串类型,不过可能有人会raise "not found"。所以这种风格被废弃了。如果坚持想去把周围的东西搞的一团糟,可以这么用:

new_exc = Exception("Error in line %s: %s"
                    % (lineno, exc or exc_class))
raise new_exc.__class__, new_exc, tb

这样改变了异常类周围使得它变的混乱了,但至少保留完好异常堆栈。在异常堆栈中raise ValueError(...)或者在错误信息raise Exception可能看上去有些奇怪。

小结:Python2中的较佳方法

以下是在Python2中一个较佳重新抛出异常的方法。

try:
    code()
except:
    exc_info = sys.exc_info()
    try:
        revert_stuff()
    except:
        # If this happens, it clobbers exc_info, which is why we had
        # to save it above
        import traceback
        print >> sys.stderr, "Error in revert_stuff():"
        traceback.print_exc()
        raise exc_info[0], exc_info[1], exc_info[2]

在with语句中抛出异常

如果想在某些场景下保留异常,有些场景下又想不捕获异常,那么可以怎么做

class ignore_or_reraise_exception(object):
    def __init__(self, reraise=True):
        self.reraise = reraise

    def __enter__(self):
        self.type_, self.value, self.tb, = sys.exc_info()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            if self.reraise:
                log.error('Original exception being dropped: %s' % self.value)
        if self.reraise:
            return True   #  reraise exception
        return False    # ignore exception

....
except Exception:
   with ignore_or_reraise_exception(reraise=False) as ctxt:
       if statements to determine whether to raise a new exception:
           # Not raising a new exception, so reraise
           ctxt.reraise = True

通过这个方法,可以自由的选择是忽略异常,还是想继续抛出异常。当需要抛出异常的时候,指定属性ctxt.reraise = True即可。

另外可以在ignore_or_reraise_exception中的处理异常,可以把异常都收集起来到一个全局变量,最后程序退出的时候,把所有的异常都打印出来。

总结:兼容Python2、Python3的重新抛出异常方法

在Python2中,重新抛出异常是用raise exc_info[0], exc_info[1], exc_info[2]这样的三个参数。但在Python3中却不一样了。

这个时候只能使用兼容Python2、Python3的类库six了。

使用方法

def some_code():
    raise Exception('oops, some error occur')

class SwaggerValidationError(Exception):
    pass

try:
    some_code()
except Exception as e:
    import six, sys
    six.reraise(
                SwaggerValidationError,
                SwaggerValidationError(str(e)),
                sys.exc_info()[2])

Traceback (most recent call last):
  File "5.py", line 14, in <module>
    sys.exc_info()[2])
  File "5.py", line 8, in <module>
    some_code()
  File "5.py", line 2, in some_code
    raise Exception('oops, some error occur')
__main__.SwaggerValidationError: oops, some error occur

参考文章:http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html

分类: Python

sphinx doc正式支持中文搜索啦

之前Sphinx doc一直不支持web的中文搜索,一个牛逼的项目怎么能不支持中文搜索呢。看到现在这个项目的一个负责人是日本人,目前支持CJK的的搜索就是日本了,我觉得不应该呀,我大中华人杰辈出。果然在github上的pull request看到了台湾的enhao兄提交了繁体中文的搜索,看了代码的实现,里面是支持中文搜索的,于是在他的基础上修改了一些完善了简体中文的支持。

我一直觉得sphinx-doc这个项目很不错,可以写文档,写书,多多关注关注一下issue的列表。

上线发布

现在sphinx-doc的master或者v1.4版本后就支持中文(简体+繁体)搜索了。

sphinx-doc的中文搜索是依靠jieba这个开源的类库来实现的,这个类库就支持简体和繁体的切分,所以就很容易实现了。

使用

第一,你的系统需要安装jieba类库, pip install jieba

第二,接下来修改sphinx的conf.py文件,为项目设置为中文的搜索配置。

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
html_search_language = 'zh'

第三,可选配置

# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
# html_search_options = {'dict': '/usr/lib/jieba.txt'}   # 根据需要设置jieba的词典路径

第四,接下来重新编译生成文档。make html

sphinx的基本搜索原理

在编译的时候:

  1. 先对文本进行切词,把中文切分,
  2. 然后制作一个大的map对象,把关键字做为key,url做为value,保存到js文件。
  3. 搜索的时候寻找对应的关键字key,拿到url作为列表展示。

后续计划

上线比较匆忙,还有一些中文搜索的计划

  • 支持python3的搜索
  • 添加测试的用例

分类: Python

腾讯云招人啦~

2016开始啦,社招、校招、实习继续啦~ http://www.qcloud.com/

联系我

联系我~ Mjg2Mjg4NDE3QHFxLmNvbQ==

如果对虚拟化技术感兴趣,就联系我吧,这里有很多不同的岗位,不要局限于以下几个~,找我内推啦~

相关的岗位有:

  • 类openstack
  • kvm虚拟化
  • SDN虚拟网络
  • web开发
  • 运营开发
  • 运维

更多岗位参考-> 腾讯云招聘

腾讯云计算平台研发工程师

岗位职责

  • 负责腾讯云主机业务后台server研发;
  • 负责腾讯云主机虚拟化控制平台的相关架构和研发;
  • 参与相关产品需求讨论以及系统架构的设计和优化工作。

岗位要求

  • 本科及以上学历;
  • 2年以上python开发经验,有C/C++经验者优先;
  • 精通TCP/IP协议,进程间通讯编程,多线程编程等,熟悉Linux常见网络服务器模型;
  • 了解libvirt、qemu等虚拟化组件的原理和代码逻辑优先;
  • 了解KVM虚拟化,熟悉openstack的架构和主要流程,有相关领域工作经验的优先;
  • 具备良好的沟通表能力及团队协作精神、有较强的主动性、责任心与执行能力,具备良好的分析解决问题能力。

运营开发工程师

岗位职责

  • 负责云平台虚拟化运营支撑系统的研发工作,工作内容主要:
  • 腾讯云的资源全景视图,从物理层面到具体云主机的分层视图,包含资源的库存管理,消耗分析,扩容流程等;
  • 云主机调度核心算法的可视化,体现不同云主机的售卖对资源池的影响,从而为主打机型,营销活动,扩充资源提供依据;
  • 虚拟机管理相关运维流程实现,如虚拟主机资源池管理、母机初始化、子机投放工具等;
  • 负责腾讯云Api的相关开发工作。

岗位要求

  • 本科及以上学历;
  • 2年以上B/S架构系统分析设计经验;
  • 精通PHP开发技术,熟悉python,shell等脚本语言;
  • 熟练掌握常用Linux命令,熟练掌握la(n)mp架构,能够熟练对la(n)mp进行架设及配置;
  • 熟悉ITIL体系和运维流程,有相关自动化运维系统开发经验者优先;
  • 有数据库开发,数据分析经验者优先;
  • 熟悉虚拟化技术者(XEN、KVM)优先。

高级系统运维工程师

岗位职责:

  • 负责对虚拟化疑难问题分析解决,形成方法论,提升团队技术能力;
  • 负责通过技术手段、流程制度提升虚拟化平台可用性;
  • 负责分析业务不合理、不高效地方,提出优化改进方案并推进实施;
  • 负责腾讯云虚拟化运维平台规划,建设,不断提升运维效率。

岗位要求:

  • 本科及以上学历;
  • 3年以上相关工作经验,精通linux,windows操作系统,对系统性能相关问题有较深刻理解;
  • 熟悉主流虚拟化技术原理(如kvm,xen,hyper-v,lxc),有实际的对虚拟化疑难问题trouble shooting经验;
  • 熟悉TCP/IP协议,了解SDN相关技术,能够定位linux虚拟化环境下网络异常;
  • 擅长shell,python,perl中一种或几种,熟练应用awk、sed、grep、strace、tcpdump、gdb等常用命令;
  • 有过kernel研究及开发经验者优先。

分类: 生活

sphinx文档使用graphviz来画图

Sphinx是用来写文档的利器,虽然比Markdown来的麻烦一些,但是功能强大,用起来也还很不错。考虑到不同文档之间内的跳转,还有把文档从拆分成多个小文档,而且reStructuredText语言入门成本不高,所以用了sphinx-docs来做文档的编写。

Word来编写文档?呃。。。请赐我一刀。使用Word来进行编排文档,在多人协作的情况下,简直惨不忍睹,文档的格式乱七八糟。

reStructuredText再通过LeTax来编译生成pdf也是很方便的。

为什么要用graphviz来画图?

画图的软件并不是太多,一般的人会选择使用visio来进行画图,visio来画图确实可以,也无可厚非,所见即所得,容易上手。但是用于一个版本控制的文档,visio的版本控制就不是太好了。用文本来说就方便很多了。

有个ditaa使用ascii字符来画图的,http://ditaa.sourceforge.net/。很多工业界的IETF文档用它来画图

+--------+   +-------+    +-------+
|  cRED  | --+ ditaa +--> |       |
|  Text  |   +-------+    |diagram|
|Document|   |!magic!|    |  cGRE |
|     {d}|   | cYEL  |    |       |
+---+----+   +-------+    +-------+
    :                         ^
    |       Lots of work      |
    +-------------------------+

ditaa

但是和中文结合在一起排版就不是太好了。中文一个字符占两个英文字符的位置,导致原图不和谐。还有另外一个原因,感觉编译ditaa的速度有些慢。

下载安装Graphviz

Windows下载Graphviz,解压到一个路径下,然后把其bin目录加入到环境变量中。在cmd中dot -V来验证是否可以使用dot编译了。

Linux请使用系统自带的包管理程序来安装atp-get install graphviz,和Windows同样的验证方法。

Sphinx 配置文件修改

Sphinx项目也需要做一些变更,因为开启了Graphviz插件。修改conf.py

# 通过配置开启graphviz插件
extensions = ['sphinx.ext.graphviz']

# 设置 graphviz_dot 路径
graphviz_dot = 'dot'
# 设置 graphviz_dot_args 的参数,这里默认了默认字体
graphviz_dot_args = ['-Gfontname=Georgia', 
                     '-Nfontname=Georgia',
                     '-Efontname=Georgia']
# 输出格式,默认png,这里我用svg矢量图
graphviz_output_format = 'svg'

这里graphviz_dot的值是dot,为了不把绝对路径写到配置中,防止其他人的路径不一样,所以这里要求dot这个程序在环境变量中,能够直接使用。

graphviz_dot_args这个参数注意正确使用'-Gfontname=Microsoft YaHei',以下是错误的'-Gfontname="Microsoft YaHei"'。 通过-G, -N, -E 来设置全局的 graphnodeedge 属性。

开始使用Graphviz

好了,接下来就可以在sphinx文档中插入你的图片了。

例如:

.. graphviz::

    digraph abc{
        a;
        b;
        c;
        d;

        a -> b;
        b -> d;
        c -> d;
    }

demo图

简单的图形

或者通过

.. graphviz:: external.dot

这个使用一个dot文件内容。

测试一下你的graphviz能否支持好中文


.. graphviz::
   
   digraph idp_modules{
     fontname = "Microsoft YaHei";
     rankdir = TB;
     fontsize = 12;
     
     node [fontname = "Microsoft YaHei", fontsize = 12, shape = "record" ];
     edge [fontname = "Microsoft YaHei", fontsize = 12 ];
     
         subgraph cluster_sl{
             label="IDP支持层";
             bgcolor="mintcream";
             node [shape="Mrecord", color="skyblue", style="filled"];
             network_mgr [label="网络管理器"];
             log_mgr [label="日志管理器"];
             module_mgr [label="模块管理器"];
             conf_mgr [label="配置管理器"];
             db_mgr [label="数据库管理器"];
         };
     
         subgraph cluster_md{
             label="可插拔模块集";
             bgcolor="lightcyan";
             node [color="chartreuse2", style="filled"];
             mod_dev [label="开发支持模块"];
             mod_dm [label="数据建模模块"];
             mod_dp [label="部署发布模块"];
         };
     
     mod_dp -> mod_dev [label="依赖..."];
     mod_dp -> mod_dm [label="依赖..."];
     mod_dp -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
     mod_dev -> mod_dm [label="依赖..."];
     mod_dev -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
     mod_dm -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
   }

如果你的graphviz能够对上面的片段能够输出全部的中文,那么你的graphviz就对中文支持很好了。如图:

中文识别不太好

如果使用Windows的可能node节点里面的文字会看不见,而Linux下的graphviz可以工作的很好。

在中文label的前面增加一个空白字符,要使用以下的:

.. graphviz::
   
   digraph idp_modules{
     fontname = "Microsoft YaHei";
     rankdir = TB;
     fontsize = 12;
     
     node [fontname = "Microsoft YaHei", fontsize = 12, shape = "record" ];
     edge [fontname = "Microsoft YaHei", fontsize = 12 ];
     
         subgraph cluster_sl{
             label=" IDP支持层";
             bgcolor="mintcream";
             node [shape="Mrecord", color="skyblue", style="filled"];
             network_mgr [label=" 网络管理器"];
             log_mgr [label=" 日志管理器"];
             module_mgr [label=" 模块管理器"];
             conf_mgr [label=" 配置管理器"];
             db_mgr [label=" 数据库管理器"];
         };
     
         subgraph cluster_md{
             label=" 可插拔模块集";
             bgcolor="lightcyan";
             node [color="chartreuse2", style="filled"];
             mod_dev [label=" 开发支持模块"];
             mod_dm [label=" 数据建模模块"];
             mod_dp [label=" 部署发布模块"];
         };
     
     mod_dp -> mod_dev [label="依赖..."];
     mod_dp -> mod_dm [label="依赖..."];
     mod_dp -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
     mod_dev -> mod_dm [label="依赖..."];
     mod_dev -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
     mod_dm -> module_mgr [label="安装...", color="yellowgreen", arrowhead="none"];
   }

修正后,可以更好的支持中文了。

修正后,中文识别的更好

参考

分类: 实用技巧

Sublime Text预览Graphviz图

经常用Sublime Text来进行画Graphviz图,如果每次用命令行来进行预览就麻烦了。这个时候当然要利用热心的网友提供的插件了。

下载安装Graphviz

Windows下载Graphviz,解压到一个路径下,然后把其bin目录加入到环境变量中。在cmd中dot -V来验证是否可以使用dot编译了。

Linux请使用系统自带的包管理程序来安装atp-get install graphviz,和Windows同样的验证方法。

自动安装插件不兼容Windows

打开Package Control来搜索了一下graphviz,还真的发现了一个插件GraphvizPreview。于是安装它,发现并不能用。使用快捷键win + shift + g 出现

Graphviz: Could not open PDF, only works for Mac… fork this repo for your OS!’

什么情况,插件只能支持Mac,不支持Windows。

上官方的github的issue搜索看了下,有人已经提交了Windows版本的修复,可是Package Control的版本还是很老的,没有把master的版本进行更新。所以通过release版本下载的是2014年4月份的v0.1.1版本,而Windows的修复版本是在2015年2月份。

所以只能通过下载master来进行手动安装。

手动安装步骤

第一步:下载https://github.com/munro/SublimeGraphvizPreview/archive/master.zip 第二步:打开Preferences -> Packages Settings -> Packages Control -> Settings User,来确认一下installed_packages没有GraphVizPreview。并且增加"remove_orphaned": false防止Sublime Text 把手动安装的插件包给删除了。

{
    "bootstrapped": true,
    "in_process_packages":
    [
    ],
    "installed_packages":
    [
        "EncodingHelper",
        "Package Control",
        "Theme - Spacegray"
    ],
    "remove_orphaned": false
}

第三步:打开Preferences -> Browse Packages...进入到Sublime Text的插件包下Packagas

第四步:解压zip文件到Packagas下,并且更改文件夹SublimeGraphvizPreview-masterGraphVizPreview

第五步:重启Sublime Text。

效果图预览

参考

分类: 实用技巧

sudo保持前用用户的env环境变量

在虚拟机里安装了一个Ubuntu,在局域网下,需要设置代理才能访问网络和安装程序。很自然的给sudo vim /etc/bash.bashrc添加代理。

# 设置代理
export http_proxy=http://web-proxy.oo.com:8080
export https_proxy=http://web-proxy.oo.com:8080
export no_proxy=localhost,.oo.com,.local

然后很愉快的sudo apt-get update发现根本无法连接服务器。如果切换到sudo su的root用户下,是可以的执行的。

研究了半天,原来是sudo在切换成root用户的时候,env并不会去保留这些环境变量,需要特别的指明才可以。

通过visudo命令来设置,需要保留的环境变量,新增下面的第10行。Defaults env_keep="http_proxy https_proxy ftp_proxy no_proxy DISPLAY XAUTHORITY"。这样就可以继续愉快的访问网络了。

#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults        env_reset
Defaults        env_keep="http_proxy https_proxy ftp_proxy no_proxy DISPLAY XAUTHORITY"
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

友情提示。只能通过visudo命令来更新,不能修改/etc/sudoers文件,会被覆盖的。

分类: 未分类

发现一个漂亮的IDE皮肤spacegray

偶然经过WEB开发的座位,发现的他的编辑器很漂亮,问了一下是什么编辑器。原来是Sublime Text,求问是什么皮肤,回答是spacegray

Sublime Text 中的spacegray

sublime text spacegray theme

sublime text spacegray theme

个人在皮肤上做了一些设置。使得更适合我

{
    "color_scheme": "Packages/User/base16-eighties.dark (SL).tmTheme",
    "theme": "Spacegray.sublime-theme",

    "highlight_line": true,

    "spacegray_sidebar_font_xlarge": true,
    "spacegray_sidebar_tree_large": true,
    "spacegray_tabs_auto_width": true,
    "spacegray_tabs_font_large": true,
    "spacegray_tabs_xlarge": true,

    "line_padding_bottom": 3,
    "line_padding_top": 3
    ....
}

获取地址:http://kkga.github.io/spacegray/

jetbrains系列中的spacegray设置

spacegray-pycharm 经过我修改后的样式

spacegray-pycharm 经过我修改后的样式

我是从https://github.com/abhimanyusharma003/phpstorm-spacegray上面下载,并根据自己的喜好做了一些变化的。因为默认的颜色有的地方感觉不好看。

我的变更:

  • 当前光标行高亮
  • 选中文字颜色默认是白色,我把它去除了
  • 选中的背景色太亮,把它调的更灰一些。
  • 普通变量文字颜色太灰,和注释的一些灰,不好区分,调的更白色一些

我的配置设置下载myspacegray-pycharm.jar

另外还有一个ocean的皮肤我也觉得很漂亮,http://www.ideacolorthemes.org/themes/96/但是有些颜色也不容易区分。

从jar包导入配置的方法

jetbrains系列从jar包导入配置的方法。

  1. 选择编辑器FileImport Setting
  2. 重启
  3. 在设置中EditorColors and fonts,选择一个主题

分类: Programming

« 较早的 文章

Copyright © 2016 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑