显示标签为“Python”的博文。显示所有博文
显示标签为“Python”的博文。显示所有博文

2016年3月19日星期六

Python中.的意义

记得三年前前有同事一起讨论过一个问题,为什么在Python里import boto后不能访问boto.ec2,但是却可以import boto.ec2,或者from boto.ec2 import xxx。

其实这个问题的本质是:Python语法里面点操作符"."在不同语境下的不同含义。访问boto.ec2的时候,是从boto获取ec2属性;而在import语句里面,boto.ec2只是一个module或者package的名字,只不过Python碰巧用和前述语境下同样的操作符.来分割package和sub-package。而在文件系统上,package和sub-package是用/来区分的。如果Python也用/,那么文章开头的语句就变成了import boto/ec2,和from boto/ec2 import xxx。这样也就不会有开头的困惑了。

另外,import ec2只是执行了ec2/__init__.py文件,因为这个文件里面没有引入或者定义ec2这个名字,所以访问boto.ec2会出错。

2013年9月17日星期二

性能比较

以下两个Python 3函数的性能差10倍。

def upgrade(url):
    """Upgrade HTTP URL to HTTPS URL."""
    components = urllib.parse.urlsplit(url)
    if components.scheme.lower() == 'http':
        return urllib.parse.urlunsplit(['https'] + list(components[1:5]))
    else:
        return url

def upgrade1(url):
    """Optimized version of upgrade, 10x faster."""
    if url[:5].lower() == 'http:':
        return 'https' + url[4:]
    else:
        return url

用timeit分别测试100万次,耗时分别如下:
4.574674844741821
0.46399807929992676
什么时候用库函数,什么时候用基本的函数实现,需要综合考虑开发速度、灵活性和性能等因素。

2013年9月16日星期一

用Python进行HTTP基本认证

用curl调用公司Jira需要基本认证(Basic access authentication)的REST API没有问题,但是用Python3的urllib.request按照文档操作,总是返回401。

我怀疑是Jira的API需要某些特殊的header,我又用GitHub的API尝试,仍然返回401错误。我看到GitHub的API调用要求必须要有User-Agent,我以为urllib没有设UA,我又尝试设置UA,仍然是401。而实际上urllib是设置了UA的。

Google后发现不是urllib的问题,而是API服务器不遵守RFC的原因,原文在此。urllib第一次发送不带验证信息的请求,在401之后会根据服务器响应的WWW-Authenticate头来进行BA。Jira的401是有这个头,可是返回的是Outh的challenge,所以BA也会失败:
WWW-Authenticate: OAuth realm="...snip..."
而GitHub的401完全没有这个头,没有遵守RFC 2616里面对这个头的"Must"要求。

解决办法是请求前就把Authorization头加上,这样直接进行BA,就可以规避服务器不返回正确WWW-Authenticate头的问题。

2013年8月19日星期一

Python程序的日志顺序混乱

Python 2程序的日志打在文件里面,但是文件中日志出现的顺序和打印日志语句的执行顺序不一致,是混乱的。

研究发现,有的日志是用logging模块打出的,而有的用print语句打出。查看logging模块的源码,对stream输出,每次emit一条record,就会对stream进行flush。而print用的是系统默认的缓存方式,对没有tty的文件的输出,默认是块缓存。

程序启动时候用python -u就可以让stdin,stdout不缓存了,应该就不会混乱了。

参考1、2。

2012年10月23日星期二

Eval is evil

用PyYAML读取Puppet的YAML文件,会从中提取出一个ruby变量,成为Python的datetime模块定义的datetime对象。转换为JSON时会出错,因为JSON不知道怎么处理datetime对象。

试了用repr先处理这个对象,即可转换为JSON,在Django中处理时再eval,就可原样返回之前的datetime对象。这样做很方便,无需知道对象的具体类型。但是太不安全了,因为没有内容判断,把从文件中得到的任何东西都进行eval。测试的时候可以eval,实际代码中是用datetime类的strftime和strptime函数配对转换的。

2012年10月21日星期日

Django项目的路径设置

一个别人的Django项目,在命令行用manage.py启动是正常的,但在Eclipse里启动后却出错,页面显示找不到django-session表。发现是找不到SQLite数据库文件的原因。可以看出,django-session是Django进程访问的第一个表,以后如果碰到这个错误,那么一定是数据库配置的问题。

新建PyDev Django项目时,会有对话框来设置项目,包括数据库类型和库名。对于目录里已有相关文件的项目,这个设置是无效果的,该设置是为了给新建项目生成settings.py。出错开始,我还以为需要改正在这个对话框提供过的设置,就在项目属性里找相关的设置,但找不到。其实所有Django相关的设置都在settings.py里面。

在settings.py里面,SQLite数据库文件是直接写的文件名,这就是个相对路径,相对于Django进程的工作目录,即getcwd(3)的结果。如果不是从项目根目录启动manage.py(假设settings.py和manage.py都在根目录),那就找不到数据库文件,因为在PyDev里面启动Django时,进程工作目录是Eclipse的Workspace所在目录。
所以不要提供相对路径,而应该使用“动态的绝对路径”。在settings.py的开头写入:
import os.path
ROOT = os.path.dirname(os.path.abspath(__file__))
这样ROOT就是settings.py文件所在目录,根据ROOT来指定项目文件的目录,包括SQLite数据库文件、静态文件等,不论从哪里启动项目,文件都可以找到。

2012年8月29日星期三

PyDev的Debug Console

用PyDev的远程调试器(Remote Debugger)来调试Django程序,只看到调试服务器(Debug Server)的控制台(Console)(见下图),看不到PyDev的交互式调试控制器(Interactive Debug Console):
其实可以在Console视图(View)的Open Console的按钮点击,选择PyDev Console:
在弹出的对话框选择PyDev Debug Console:
然后在Console视图的Display Selected Console选择刚刚开启的PyDev Debug Console:
这样就可以出来了,在这个Console里面可以对断点处各个Frame的状态进行检查,超实用。但是还有个问题,如果鼠标在Debug视图里面点击,Console视图就会切换到Debug Server。可以点击Console视图的Pin Console按钮:
这样Debug Console就不会被自动切换了。

2012-10-12更新:
如果在Open Console对话框不能选PyDev Debug Console:
上面写着:Start the debugger and select the valid frame。在Debug的过程中,在Debug视图选中一个Frame即可选择PyDev Debug Console了。

2012年8月22日星期三

Python的文档

程序出了bug。我用rstrip('.py')来去掉文件名的扩展名,但是下面这个结果却出乎意料:
In [6]: 'gt_php.py'.rstrip('.py')
Out[6]: 'gt_ph'
在IPython里面用rstrip?看不到为啥,查Python的官方文档,原来'.py'不是当后缀来处理的,而是从后往前碰到的'.', 'p'和'y'都会去掉,所以会有上面的结果。

前不久用os.path.join的时候碰到该函数一个诡异的行为,在docstring里面没有说明,在官方文档上才有详细解释。看来docstring还是不完全,要记得查看官方文档。

2012年8月18日星期六

Python中的*和**

这里说的*和**不是乘法和次幂的运算符,而是函数定义中的形参(parameters)和函数调用中的实参(arguments)。

文档中把它们的用法很简洁得说清楚了。怎么记住*和**在函数定义和函数调用时的区别呢?简单地概况,就是“定义的压缩,调用的解开”。
  • 若出现在函数定义中,则实际调用时把接收的一串arguments或keyword arguments,压缩成元组或者字典
  • 如出现在函数调用中,则把接收的iteratable或者mapping,解开为一串arguments或者keyword arguments
在下面这个没有做任何装饰的装饰器里面:
def decorator(target):
    def caller(*args, **kwargs):
        return target(*args, **kwargs)
    return caller
可以看到放在定义和调用时的区别。

2012年5月27日星期日

换行符和BOM引起的麻烦

最近整理电脑里的Python程序,有一个程序运行时出错:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
然后鼠标箭头变成了一个十字。如果如下:
$ python absent.py
用Python命令来执行是好的。这个文件的前5行如下:
#! /usr/bin/env python
# -*- encoding: utf-8 -*-
# 设置不同的文字屏保,2008年6月5日

import gtk
那个十字以前就碰到过,是用./执行时程序被当成了Shell脚本,所以Python的import gtk语句被当成ImageMagick的import工具来执行了,那个十字形鼠标箭头是import用来选取截图窗口的。

这个文件是我在原单位办公室的Windows电脑下创建的,所以马上意识到是换行符引起的。Linux下的换行符是\n,而Windows下创建的文件换行符是\r\n,所以第一行其实是这样的内容:
#! /usr/bin/env python\r\n
在Linux下,系统只把\n识别为换行符,把python和\r连起来识别了。我创建了一个测试程序dos.py:
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

import this
然后在Shell下用
sed -i 's/$/^M/g' dos.py
把换行符转成Windows下的\r\n。^M是用Control-V和Control-M打出来的。这个文件执行出错:
$ ./dos.py
: No such file or directory
当然,用Python命令来解释是好的。上面的错误可以在Shell下用等同的命令复现:
$ /usr/bin/env python^M
: No such file or directory
如果真的有python^M这个命令会呢?在~/bin目录里创建到Python解释器的链接:
ln -s /usr/bin/python python^M
~/bin目录在路径的前面,这样python^M命令可以找到了。再执行一次就好了:
 $ ./dos.py
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
......
 其实不要用env,直接在Shebang里面指定执行文件:
#! /home/tux/bin/python
# -*- encoding: utf-8 -*-

import this
出错会更直观:
$ ./dos.py
bash: ./dos.py: /home/tux/bin/python^M: bad interpreter: No such file or directory
在~/bin里面创建名为python^M的链接后,就又可以执行了。再回到开始的错误:
$ ./absent.py
./absent.py: line 1: $'\357\273\277#!': command not found
./absent.py: line 4: $'\r': command not found
这个错误和上面dos.py的不同,这'\357\273\277#!'是哪里来的呢?把absent.py文件这样打开:
$ cat -e absent.py
M-oM-;M-?#! /usr/bin/env python^M$
# -*- encoding: utf-8 -*-^M$
# M-hM-.M->M-gM-=M-.M-dM-8M-^MM-eM-^PM-^LM-gM-^ZM-^DM-fM-^VM-^GM-eM--M-^WM-eM-1M-^OM-dM-?M-^]M-oM-<M-^L2008M-eM-9M-46M-fM-^\M-^H5M-fM-^WM-%^M$
^M$
import gtk^M$
原来在#!之前还有几个字符,用bvi打开:
00000000  EF BB BF 23 21 20 2F 75 73 72 2F 62 69 6E 2F 65 ...#! /usr/bin/e
00000010  6E 76 20 70 79 74 68 6F 6E 0D 0A 23 20 2D 2A 2D nv python..# -*-
它们是0xEF, 0xBB和0xBF。原来这个文件是用微软的Notepad创建的,在文件头被自动加上了UTF-8的Byte Order Mark。用cat -e absent.py打开时,在#!前面显示的是M-oM-;M-?,这是指EE BB BF是把'o', ';'和'?'的高位设为1,可以如下验证:
>>> print ' '.join('%X' % (ord(b) + 128) for b in 'o;?')
EF BB BF
要解决问题,必须把前三个字节删除掉,并把换行转换为Unix格式的\n才能用./absent.py正确执行程序。

2012年5月13日星期日

Python和Bash的混合

上面的图片是我写的一个粗糙的Python脚本的一部分,功能是文本处理,这不是要说的,重点要说的是第30、35、54和55行。

30行是把src字符串所指的zip文件解压缩到tmpdir目录。Python有zip文件处理的模块zipfile,但是只是解压缩有点麻烦,就用Shell下的unzip工具搞定了。

54行是截取othersrc文件的一段,这个用Python的文件处理其实更自然,但是在写脚本前面已经用过现成的Bash命令了,所以就懒得再写Python,直接把Bash命令抄过来。

55行是比较文件的,这个想也没想就用Shell下的diff命令搞定了。

35行计算文件的MD5校验和,这个首先想到的应该是Shell命令,但是Shell下md5sum命令计算出来的结果还得截取字段才能得到,而Python的hashlib直接得到校验和也很简单,就用Python来完成。

上面的例子是在Python里面混合进Bash语句。我又想到一个Bash里面用Python的实例:
第49行用Python开了一个临时的HTTP服务。

这样一会儿Python,一会儿Bash的做法,我以前觉得挺别扭的,会觉得不够纯洁。可是现实情况往往是这样的:很多问题写脚本,用Bash或者 Python都可以完成,但是应该有一个最佳的办法;对脚本里面的单一功能,也是一样的,Bash或者Python的实现总有一个更好的,比如更快实现、 更简洁、已有现成的等等。现在我觉得,更加重要的是完成事情,而不是所谓的“纯洁性”,什么好用就用什么。再说,初看开头的图片,也没觉得因为Python和Bash的混合而变得很乱吧,呵呵。

2011年1月20日星期四

Python序列索引

s[i:j:k]的i和j是头和尾,k是步长,我却把中间的j当成了步长,再加上负数索引就更纠结了。代码:
s="foobar"
k=3
''.join([chr(ord(c)+1) for c in s[-1:-k-1:-1]])
其实用range可读性更好:
''.join([chr(ord(s[-i-1])+1) for i in range(k)])
发送自我的Windows Mobile 电话

2010年4月29日星期四

用Python写二进制文件

参考了http://linux.byexamples.com/archives/478/python-writing-binary-file/。
binaascii可以在二进制数据和ASCII表达之间互相转换。Python程序如下:

#! /usr/bin/env python
import binascii
f = open('m', 'w')
hs='2dd31d1c'
hb=binascii.a2b_hex(hs)
f.write(hb)
f.close()

之后可以用bvi来验证写入结果。

2010年4月15日星期四

用Python计算日期

经常会碰到这样的问题:比如我的一个水木社区的账号生命力还有281天,由于生命力是一天减一,所以281天后我不上线这个账号生命力就会变成零,账号会死掉。那么今天后的281天后是哪天呢?用Python可以非常方便地计算出来:

In [1]: import datetime
In [2]: datetime.date.today() + datetime.timedelta(281)
Out[2]: datetime.date(2011, 1, 21)

就是说281天后是2011年1月21日。

(2011年9月20日更新)如果要计算两个日期之间的天数,可以:
>>> datetime.date.today()-datetime.date(2010,12,19)
datetime.timedelta(275)
就是说从2010年12月19日到“今天”过了275天。

2009年7月18日星期六

SHOW ME THE IP

从内网获得路由器的广域网IP地址的Python脚本(仅限特定硬件和特定固化软件):
#! /usr/bin/env python
"""
Get my IP address from my TP-Link TL-R402 router.
Pan Yongzhi@ Jul 18, 2009
"""
import urllib2
x = urllib2.HTTPPasswordMgrWithDefaultRealm()
x.add_password(None, 'http://192.168.1.1', 'user', 'pass')
auth = urllib2.HTTPBasicAuthHandler(x)
opener = urllib2.build_opener(auth)
urllib2.install_opener(opener)
flob = urllib2.urlopen('http://192.168.1.1/userRpm/StatusRpm.htm')
"""IP address contained in line 75 of the web page, looks like this:
0, "00-21-27-3C-01-A5", "221.217.204.232", 3, "255.255.255.255", 0, 0, "221.217.204.232", 0, 1, 0, "202.106.46.151, 202.106.195.68", "0 day 00:19:31", "up",
"""
print flob.readlines()[74].split(',')[2].strip().strip('"')

2009年6月20日星期六

Python Shell中切换目录

import os
os.getcwd() # 输出当前工作目录
os.chdir(path) # 改变工作目录至path