东东东 陈煜东的博客

分类存档: Python

【偏方】 Python 寻找子列表是否存在与其他列表中

有一个 Python 技术优化,是判断一个列表是否属于另外一个列表,注意列表是有顺序的,不是集合。本来一直想找一个大方的简洁方法的,但没想到最后是通过偏门来解决的。

例如:

l = [(1, 2), (3, 4), (5, 6), (7, 8)]
find = [(3, 4), (5, 6)]

# 期望输出
find in l  -> True

字符串的启发

字符串有一些操作。

l = 'abcdefg'
>>> l.index('ef')
4

>>> l.index('h')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

字符串有index方法,可以很方便的判断子字符串是否在给定的字符串中,但是列表类的操作,一直没有找到这类的方法。

在和同事的讨论中,同事给了一个启发,使用repr函数获取可以试试。

用字符串的index函数还有一个好处,就是如果子串不在父串中,直接抛出异常,Python 不像其他语言一样,返回-1

repr转成字符串来处理

l = [(1, 2), (3, 4), (5, 6), (7, 8)]
find = [(3, 4), (5, 6)]

>>> repr(l)
'[(1, 2), (3, 4), (5, 6), (7, 8)]'
>>> repr(find)
'[(3, 4), (5, 6)]'

>>> repr(l)[1:-1].index(repr(find)[1:-1])
8
>>> repr(l)[1:-1].index(repr([(5, 5)])[1:-1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

使用join操作的字符串来判断

http://stackoverflow.com/a/2250981 中获得启发,也是利用字符串的方来进行解决的。

>>> ''.join(map(repr, [(3, 4)])) in ''.join(map(repr, [(1, 2), (3, 4)]))
True

不得不说,对比两个方法,其实都是类似的原理,都是通过字符串的字串来进行比较。思路很新颖,直观给人不用for循环进行处理,代码看上去也很简洁。

但是这些方法也只能是说判断子列表是否存在于另外一个列中的,不能判断子列表在另外一个列表的索引值。

另外该方法也不能保证子串只出现一次的情况。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: 【偏方】 Python 寻找子列表是否存在与其他列表中 – https://www.chenyudong.com/archives/python-find-lists-in-lists.html

分类: Python

Python 将两层列表展开平铺成一层的5种方法

这几天和同事在讨论,如何用 Python 写出优雅的让列表中的列表展开,变成扁平化的列表。

例如

# 期望输入
input = [[('A', 1), ('B', 2)], [('C', 3), ('D', 4)]]

# 期望输出
output = [('A', 1), ('B', 2), ('C', 3), ('D', 4)]

map 函数合并

>>> new = []; map(new.extend, input); new
[None, None]
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]

这个方法看上去还可以,但是有个致命的缺点,就是map函数会返回值,并且这个返回值是没有用的。另外还需要提前声明一个变量,从代码的简洁性上,不够简洁优雅。

sum 函数合并

>>> sum(input, [])
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]

这个看上去很简洁,不过有类似字符串累加的性能陷阱。后面有性能对比。

reduce 函数

>>> reduce(list.__add__, input)
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]

做序列的累加操作。也是有累加的性能陷阱。

列表推导式

>>> [item for sublist in input for item in sublist]
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]

列表推导式,看着有些长,而且还要for循环两次,变成一行理解需要费劲一些,没有那么直观。

itertools 类库

>>> list(itertools.chain(*input))
[('A', 1), ('B', 2), ('C', 3), ('D', 4)]

通过第三方类库类实现的,相比其他的几个实现,看着还算比较优雅。最后的性能发现居然还很高。

性能大对比

python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(list.__add__,l)'
1000 loops, best of 3: 547 usec per loop

python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 509 usec per loop

python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 52.8 usec per loop

python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99; import itertools;' 'list(itertools.chain(*l))'
10000 loops, best of 3: 35.9 usec per loop

python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'new = []; map(new.extend, l); new'
10000 loops, best of 3: 34.1 usec per loop

欢迎大家共同探讨优雅的的实现和性能的优化。

参考:http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Python 将两层列表展开平铺成一层的5种方法 – https://www.chenyudong.com/archives/python-make-flat-list-of-list.html

分类: Python

Python 优雅获取本机 IP 方法

见过很多获取服务器本地IP的代码,个人觉得都不是很好,例如以下这些

不推荐:靠猜测去获取本地IP方法

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import fcntl
import struct

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15])
    )[20:24])

print "br1 = "+ get_ip_address('br1')
print "lo = " + get_ip_address('lo')
print "virbr0 = " + get_ip_address('virbr0')

这类代码带有猜测的行为。

如果机器上只有eth0 或者 只有bond0上有IP,那么此类代码都有可能失败,而且还不容易移植到其他平台上。

不推荐:通过hostname来获取本机IP

import socket
print(socket.gethostbyname(socket.gethostname()))

# 有可能出现这个情况
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
socket.gaierror: [Errno -2] Name or service not known

这个方法是通过获取hostname,然后再通过hostname反查处机器的IP。这个方法也是不推荐的。因为很多的机器没有规范这个hostname的设置。

另外就是有些服务器会在 /etc/hosts 中添加本机的hostname的地址,这个做法也不是不可以,但是如果设置成了 127.0.0.1,那么获取出来的IP就都是这个地址了。

通过 UDP 获取本机 IP,目前见过最优雅的方法

这个方法是目前见过最优雅获取本机服务器的IP方法了。没有任何的依赖,也没有去猜测机器上的网络设备信息。

而且是利用 UDP 协议来实现的,生成一个UDP包,把自己的 IP 放如到 UDP 协议头中,然后从UDP包中获取本机的IP。

这个方法并不会真实的向外部发包,所以用抓包工具是看不到的。但是会申请一个 UDP 的端口,所以如果经常调用也会比较耗时的,这里如果需要可以将查询到的IP给缓存起来,性能可以获得很大提升。

# 在 shell 中可以一行调用,获取到本机IP
python -c "import socket;print([(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1])"
10.12.189.16
# 可以封装成函数,方便 Python 的程序调用
import socket

def get_host_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()

    return ip

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Python 优雅获取本机 IP 方法 – https://www.chenyudong.com/archives/python-get-local-ip-graceful.html

分类: Python

Python 的 json 在 Nuitka 下性能好差

在商业项目中使用了 Nuitka 来进行源码的编译和保护。结果用户反馈使用的 HTTP 请求好慢,数据量一大就超时了。

案发现场

将代码在本地运行,进行 strace 发现好慢。

     0.000136 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 22), ...}) = 0 <0.000008>
     0.000050 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5d83f8000 <0.000019>
     0.000056 write(1, "\r\n", 2)       = 2 <0.000017>
     0.000048 write(1, "\r\n", 2)       = 2 <0.000008>
     0.000042 write(1, "\n", 1)         = 1 <0.000007>
     3.033367 brk(0x2a08000)            = 0x2a08000 <0.000019>
     1.743621 brk(0x2a41000)            = 0x2a41000 <0.000019>
     2.166131 brk(0x2a86000)            = 0x2a86000 <0.000021>
     2.687461 brk(0x2aaf000)            = 0x2aaf000 <0.000017>
     0.266587 brk(0x2ae3000)            = 0x2ae3000 <0.000165>
     4.485236 brk(0x2b3a000)            = 0x2b3a000 <0.000129>
     1.482029 mmap(NULL, 344064, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa596451000 <0.000331>
     1.977664 mremap(0x7fa596451000, 344064, 389120, MREMAP_MAYMOVE) = 0x7fa5963f2000 <0.000284>
     0.781643 munmap(0x7fa5963f2000, 389120) = 0 <0.000252>
     0.053389 mmap(NULL, 1929216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5962ce000 <0.000183>

一次请求开销 20 秒,发现 brk 之间的耗时有个 14 秒,加上下面的 mmapmremap 开销 2.8 秒。但是在纯 Python 文件下执行只需要 1 秒以内,非常的纳闷。

在使用 Nuitka 是将 Python 文件转化为 so 文件,而不是全部打成一个 so 文件,因此采用了 py 文件替换来进行调试。整个路径都用 py 文件来运行了,当然依赖的一些类库使用的 so 的形式,并没有什么进展。

没办法只能使用 pdb 来进行一行一行的跟踪了。临时写了一个文件做为入口。

python -m pdb test.py

进入后,使用

n 执行这行命令
s 进入函数内部
l 查看上下文的代码

一步一步的执行下后,发现在

class CJsonEncoder(json.JSONEncoder):
    """
        to solve json.dumps datetime.datetime TypeError.
        TypeError: datetime.datetime(2014, 07, 12, 08, 56, 23) is not JSON serializable

        Use:
            json.dumps(data_obj, cls=CJsonEncoder)
    """
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, datetime.date):
            return obj.strftime('%Y-%m-%d')
        elif isinstance(obj, datetime.time):
            return obj.strftime('%H:%M:%S')
        else:
            return json.JSONEncoder.default(self, obj)

...

result = {
    'response':{
        'header':{
            'version':'1.0',
            'returnCode':self._code,
            'errorInfo':msg
            },
        'data':data
    }}
result_str = json.dumps(result, cls=CJsonEncoder)

CJsonEncoder 耗时 4 秒

一个令人惊讶的数值,对比了数据, 一个是 json.dumps(result, cls=CJsonEncoder) 一个是 json.dumps(result) ,耗时分别是 20 秒和 16 秒,光在这个自定义的json解析上居然花费 4 秒的时间。不可思议。

简直无可忍受,感觉这个 Nuitka 解决方案不行呀。

使用 simplejson 类库替换

刚好最近一直在做 json 相关类库的工作,之前也是测试过 Python 自带的 json 类库,性能也确实差,可能和我的 Python 版本有关系,还在使用 Python 2.6 的版本,吐血。

现有的 simplejson,后来 Python 将 simplejson 纳入标准类库了,但是由于 Python 分发出去,就无法更新标准类库了, 但是 simplejson 可以不断的独立更新,所以维护的更好一些,很多的很多的程序也都是用它。

看到 http://stackoverflow.com/a/17823905 上有说 simplejson 看是否有加速过,可以用这个

import simplejson
# If this is True, then c speedups are enabled.
print bool(getattr(simplejson, '_speedups', False))

如果返回 True ,那么就有使用特殊的加速过。所以性能更好一些。

具体替换实现

考虑到 simplejson 是符合 Python 的标准类库的接口,所以可以无缝兼容在使用。在使用了 simplejson 后,这个耗时回到了 1 秒的时间。

def patch_json():
    """
    using simplejson replace json
    """
    import json  # noqa
    import simplejson  # noqa
    sys.modules['json'] = sys.modules['simplejson']

在最开始的地方执行函数,替换掉标准的 json 的类库,让开发人员使用起来无感知,但是要注意做好兼容性测试。也要把这类隐形的替换在开发规范中声明好。

为什么使用了 Nuitka 变慢了

这个是我无法回答的了。因为 Nuitka 将 py 变成了 c++ 语言,再编译成 so 文件。按理不应该这么慢的。首先 strace 进入看,只有一些分配内存的情况系统调用。感觉这边在大量的一些 CPU 操作,不知道是不是 Nuitka 对这个优化不好,或者是老的 json 代码不行。

另外还发现,在 strace 中,一些查找 python 模块也还很耗时。

还有由于我的 Nuitka 解决方案是单个 py 直接变成单个 so,然后 py 和 so 混合使用的。不知道这边的切换会不会导致一些耗时的开销。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Python 的 json 在 Nuitka 下性能好差 – https://www.chenyudong.com/archives/python-json-is-poor-performance-under-nuitka.html

分类: Python

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 Subprocess Popen 管道阻塞问题分析解决 – https://www.chenyudong.com/archives/python-subprocess-popen-block.html

分类: Python

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,然后做了箭头,报错了,得再研究研究。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Graphviz安装失败的一个尝试 – https://www.chenyudong.com/archives/graphviz-install-fail.html

分类: 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重新抛出异常的姿势 – https://www.chenyudong.com/archives/python-reraise-exception.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的搜索
  • 添加测试的用例

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: sphinx doc正式支持中文搜索啦 – https://www.chenyudong.com/archives/sphinx-doc-support-chinese-search.html

分类: Python

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

SITEMAP回到顶部 ↑