I am LAZY bones? AN ancient AND boring SITE

python编程细节──遍历dict的两种方法比较

python以其优美的语法和方便的内置数据结构,赢得了不少程序员的亲睐。
其中有个很有用的数据结构,就是字典(dict),使用非常简单。说到遍历一个dict结构,我想大多数人都会想到 for key in dictobj 的方法,确实这个方法在大多数情况下都是适用的。但是并不是完全安全,请看下面这个例子:

#这里初始化一个dict
>>> d = {'a':1, 'b':0, 'c':1, 'd':0}
#本意是遍历dict,发现元素的值是0的话,就删掉
>>> for k in d:
...   if d[k] == 0:
...     del(d[k])
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
#结果抛出异常了,两个0的元素,也只删掉一个。
>>> d
{'a': 1, 'c': 1, 'd': 0}
 
>>> d = {'a':1, 'b':0, 'c':1, 'd':0}
#d.keys() 是一个下标的数组
>>> d.keys()
['a', 'c', 'b', 'd']
#这样遍历,就没问题了,因为其实其实这里遍历的是d.keys()这个list常量。
>>> for k in d.keys():
...   if d[k] == 0:
...     del(d[k])
... 
>>> d
{'a': 1, 'c': 1}
#结果也是对的
>>>

其实,这个例子是我简化过的,我是在一个多线程的程序里发现这个问题的,所以,我的建议是:遍历dict的时候,养成使用 for k in d.keys() 的习惯。
不过,如果是多线程的话,这样就绝对安全吗?也不见得:当两个线程都取完d.keys()以后,如果两个线程都去删同一个key的话,先删的会成功,后删的那个肯定会报 KeyError ,这个看来只能通过其他方式来保证了。

mysql数据库表名的大小写问题

记一下一个从昨天一直找到今天才找到原因的问题,希望可以帮助到也在苦苦查找原因的后来人。
有个hive开发环境,元数据库用的是mysql。然后从一个mysql迁移到另一个以后,就发生了莫名其妙的错误,表现在:mysqldump 出来,用 mysql < xxx.sql 导入以后,数据库连接正常,写入也有权限,但是对某些表插入数据的时候,莫名地报主键冲突的错误(那表的主键只有一个字段,主键约束也很简单,select 发现没有和要插入的数据重复的)。 究其原因,可以看到mysql里有两张表,表名除大小写以外都一样。。。这样判断主键是否重复的时候,就会把两个表合起来判断了(mysql 5.0.45 有此问题,其他版本未测试)。 后来通过google,发现mysql数据库在windows下,由于windows的文件名是不分大小写的,所以表名默认也不分大小写;但是在linux服务器下,默认是区分大小写的。。。但是这个方式反而容易引发一系列的兼容性问题,比如从一个复制数据到另一个的时候,可能由于表名大小写不同而访问不到。因此mysql也提供了一个关闭区分表名大小写的方法: 修改 mysql 的配置文件 /etc/my.cnf 在[mysqld]那段里添加这么一行: lower_case_table_names=1
然后再重启一下mysqld,就会自动把所有表名转换成小写存储,问题也就迎刃而解了。

关于我的网络

近一个月来,都在断断续续折腾网络,遇到蛮多不爽的,流水帐记录一下。
首先是拉宽带,杭州有电信、网通、联通,由于联通是新入的,所以搞活动比较多,也最便宜,于是优先考虑,结果打电话过去,告诉他地址之后,人家就说“对不起,我们的网络暂时还不到您的小区!”(都怪我住得太偏了,唉。。)
无奈只能问问老牌的电信,一问1M 1年980,2M 1180,但是还需要初装费100块,觉得还是有点小贵,再问网通,人家1M、2M都是980,选1M还可以送个不值钱的蚕丝被,而且不用初装费,然后又听说网通其实现在用户少,平时限速不严,常常1M的带宽下载也能300+KB/s。又贪图了那100块钱的初装费,就装了1M的网通。
话说,网通的上门速度还真不错,当天打电话,就来给安装了,晚上回去一试,速度果然还可以,就开心地以为没啥问题了呢,到后来才发现那句老话──一分钱,一分货──还是有道理,这个网通果然还是存在不少毛病:首先是网络不稳定,你上得正开心呢,冷不丁就给你来次断线,还好我不怎么玩网络游戏,不然就更郁闷了。再者,我之前都是通过ssh隧道翻墙的,但是自打用了网通以后,就很难连上国外主机的22端口(80端口非常正常)了,奇怪的是,我ssh到国内主机很正常,从国内主机ssh到国外主机也很正常,但是就是不能直接ssh出去,于是基本确定是网通封了某些IP的某些端口或者是网通没有国际线路的带宽?于是郁闷地打丫投诉电话,接线员基本不能理解我说的是什么问题,我想他毕竟只是接线员而已,只要能记录问题就行,也就不多说了,说第二天给派人上门维修,于是第二天就上门了一个可爱的网通工作人员,进门我给他开电脑看问题,一看到linux,就直接不看了,打开他自己带的笔记本,插上我的网线,用他自己的账号拨号上网,然后打开网通的主页,点了一个电影开始放起来,一边对我说:“你看,网络一切正常,速度还不错~”。。。我那个无奈啊。。。后来花了很多时间跟他解释什么是 ssh(他居然压根不知道什么是ssh,什么是端口)、上网不只是能看电影能偷菜就行的等等。然后我帮他一字一句地把上门服务地详情录入到网通的系统里。。。
还好,经过我的N次电话之后,现在勉强能ssh出去了,虽然ssh隧道的速度和看电影的速度不是一个级别的,但是好歹能上推了。。。
这里给广大网友提个醒:如果你上网不只是为了看电影的话,一定要慎选网通。不然真是劳民伤财啊。。。打96171都花了我不少钱了。

设置python的stdout为无缓存模式

考虑以下python程序:

#!/usr/bin/env python
 
import sys
 
sys.stdout.write("stdout1 ")
sys.stderr.write("stderr1 ")
sys.stdout.write("stdout2 ")
sys.stderr.write("stderr2 ")

其中的sys.stdout.write也可以换成print。
运行这程序,你觉得会输出什么?试验一下,就会发现,其实输出并不是

stdout1 stderr1  stdout2 stderr2

而是:

stderr1 stderr2 stdout1  stdout2

究其原因,是因为缓存:虽然stderr和stdout默认都是指向屏幕的,但是stderr是无缓存的,程序往stderr输出一个字符,就会在屏幕上显示一个;而stdout是有缓存的,只有遇到换行或者积累到一定的大小,才会显示出来。这就是为什么上面的会显示两个stderr的原因了。
然而,有时候,你可能还是希望stdout的行为和stderr一样,能不能实现呢?当然是可以的,而且对于python,实现起来还特别方便,以下是两个方法:

python -u stderr_stdout.py
PYTHONUNBUFFERED=1 python stderr_stdout.py

第一种方法是给python指定 -u 参数,第二种方法是在python运行时,指定 PYTHONUNBUFFERED 环境变量,这两种方法其实是等效的。
当然,也可以在程序的第一行指定 #!/usr/bin/python -u 然后程序加可执行权限来运行,或者把 export PYTHONUNBUFFERED=1 写到 .bashrc 里去。

gentoo下的pppoe拨号

最近,无线路由坏了,所以只能先用自己的电脑拨adsl了。
其实这本也没什么,我的win7和ubuntu都只要稍微设置一下就OK了。
这里再稍微提一下ubuntu的pppoe设置:记得以前的版本(应该是6.xx的时候吧),NetworkManager是不直接支持pppoe的,还要自己手工设置,然后执行pon/poff来拨号,但是现在进步了,直接在NM里输一下用户名和密码就可以上了。
但是我的gentoo是用wicd来管理网络的,而wicd至今都还不支持pppoe,于是只能用原始的命令行来拨号了。
于是eix一搜,发现有个net-dialup/rp-pppoe,安上,看到有 pppoe-setup、pppoe-start、pppoe-stop。啥都不用说了,先pppoe-setup,再pppoe-start,本以为会很顺利,但是几次尝试都在最后一步出错了,而且提示的错误都没啥价值,不知道从何查起~
正当我无计可施,想妥协安个NetworkManager的时候,忽然灵感一现,发现了可能的错误原因,那就是──内核模块。原来,之前我的gentoo内核基本上也是按需配置的,以前我一直都有路由器拨号,所以没有在内核选项里打开ppp的支持,才导致了这一郁闷的结果,哈哈,既然发现了可能的原因,那就好办了,make menuconfig 里面选上 Device Drivers —>Network device support —>PPP (point-to-point protocol) support 下面的所有项,编译完再重启。再 pppoe-start ,果然看到了 Connected!

湿地博物馆

话说,自从我老婆孩子从老家回到杭州以后,原来刚好够我们小两口住的那个小房间,显然是住不下了。于是只能张罗着搬家和添置一些生活用品,到现在,虽然清苦(房子还是不大,也比较破旧),也总算慢慢安定下来了。
之所以在这里先说我搬家的事情,是因为我现在的住处比较偏远,靠近西溪湿地。然后,附近有一个“中国湿地博物馆”,大热天的,空调足,又免费,所以可以算是避暑胜地。今天──好吧,已经是昨天了(昨晚写这日志由于网络不好没写完)──终于有个周末可以空下来了,就去这博物馆玩了。
由于此博物馆并没有禁止拍照,所以我就拍了些照片回来,供大家观赏,以下图片都可以点击看大图~
这是门口:

点击查看全文 »

oracle里循环搜索父子关系的键

想象一下,如果有一张oracle里的表,存着的是一个linux系统当前的进程信息,有ID、父ID、进程名之类的字段,如果给定一个进程ID,要查这个进程的所有父进程(包括爷爷进程等)的ID,sql改怎么写?这就要用到 CONNECT BY PRIOR … START WITH 子句了。
下面就是这样的一个例子:

--原先的表大致是这样的:
SELECT id, parentid FROM the_table WHERE id IN (14976, 14975);
        ID   PARENTID
---------- ----------
     14975      14657
     14975      14658
     14975      14992
     14975      15047
     14976      14975
--要查所有的父进程,这么干:
SELECT DISTINCT id FROM the_table CONNECT BY PRIOR parentid = id START WITH id = 14976;
        ID
----------
         1
       110
       127
       130
       155
       163
     12930
     13812
     14656
     14657
     14658
     14949
     14950
     14951
     14975
     14976
     14992
     15047

perl 的特色

由于工作中偶尔要接触一下perl,所以我花了点时间,很粗略地看了一遍flamephoenix的perl中文教程。本文就是我在看的过程中记录下来的点点滴滴,对大家不一定有用,但是也可以让不会perl的同学对其有个直观的印象。perl果然是个非主流,哈哈。

字符串有很不同的转义,可以转义大小写。
$a = "T\LHIS IS A \ESTRING"; # same as "This is a STRING"

比较操作符有“比较”这个操作,整数是 <=> ,字符串是 cmp ,会返回 1,0,-1

字符串能进线自加(++)操作,而且逢zZ9会进位,但是不能自减。

字符串的重复操作符是x(小写字母x),对应的python操作符是 * (星号)

条件操作符可以用来选择变量:
$condvar == 43 ? $var1 : $var2 = 14;

数组变量要有个 @ 头,而且可以和普通变量重名,列表用的是(),对应python里是 []。

列表可以用..表示范围: (2, 5..7, 11) = (2, 5, 6, 7, 11) 更神奇的是,还可以用于实数: (2.1..5.3) = (2.1, 3.1 ,4.1, 5.1) 和字符串 (“aaa”..”aad”) = (“aaa”,”aab”, “aac”, “aad”) ,还可以包含变量: ($var1..$var2+5)

列表赋值给简单变量,会得到列表的长度。。。

打开文件的模式(读、写或追加)是通过在文件名前加前缀指定的,所以,我不知道如果要只读打开文件名是 “>“ 的文件该怎么写。

文件测试操作更像bash的风格。

@ARGV[0] 就是第一个参数,而不是程序名本身。

有个 <> 操作符,可以直接按参数顺序读取指定的文件,这个就得实践过才能体会了。

可以像操作普通文件那样操作管道。而且也是用的open函数。

模式(也就是 正则表达式)操作是内置的,而不用像python那样import re。这点倒是比较像javascript。

有类似 awk 的模式匹配操作符。比如: $result = $var =~ /abc/; 这个用在逻辑判断里很方便。

模式的定界符缺省是/,但是可以用m来自定义。

模式匹配以后可以用 $1 $2 $& 等引用匹配到的组。

模式匹配操作可以放在while里循环,还可以pos定位。

elsif 也比较bash。

有 foreach 的语法。foreach语法里的 循环变量 是 循环内部 的局部变量;在循环里改变循环变量,会修改数组的对应项。。。

last就是break;next就是continue;还有个redo。循环控制很强大。

以上两点,可以用这个小程序体会一下:

#!/usr/bin/env perl
 
@list = (1, 2, 3, 4, 5);
print "@list\n";
foreach $temp (@list) {
	print "temp=$temp\n";
	if($temp==2){
		$temp=20;
		next;
	}
	if($temp==3){
		$temp=30;
		redo;
	}
	if($temp==4){
		$temp=40;
		last;
	}
}
print "@list\n";

也支持goto。

单行条件,也很有特色:语法为statement keyword condexpr。其中keyword可为if、unless、while或until。尤其是短循环时,很方便。

用 sub 来定义子程序,用 &xxx 或者 do xxx 来引用。

实参用括号的传入,(&xxx(1, 2, 3)),形参不列出,在子程序里用 @_ 来引用传入的实参。

局部变量有两种, my($xxx) 的变量自在本子程序内有效。 local($xxx) 的变量在本子程序和下级的子程序内有效。

用变量和数组混合做为参数,传给子程序时,要小心有可能传到 @_ 的时,会被重组。

传参数的时候,也可以传地址,这时候,子程序里改变的变量会影响外部。

perl5里有3个预设的子程序,BEGIN、END、AUTOLOAD,这点又有点像awk。AUTOLOAD 是在找不到子程序的时候被调用。

关联数组,就是用任意变量类型做下标的数组,用 %变量名表示,可以用 foreach $key (keys(%hashlist)) 来遍历。这其实就是python的dict。 不过其 sort keys 不知道是什么语法。。。

%fruit = ("apples",17,"bananas",9,"oranges","none");
和这个等效:
%fruit = ("apples"=>17,"bananas"=>9,"oranges"=>"none");
 
可以先数组,再关联数组:
@fruit = ("apples",17,"bananas",9,"oranges","none");
%fruit = @fruit;
反之亦然:
%fruit = ("grapes",11,"lemons",27);
@fruit = %fruit;
不过@fruit可能变成 ("lemons",27,"grapes",11)

关联数组可以直接赋值增加,用 delete 函数删除。 (“lemons”,27,”grapes”,11)

keys 对应的是 values,可以取出所有的值。

可以用each更好地循环关联数组:
%records = (“Maris”, 61, “Aaron”, 755, “Young”, 511);
while (($holder, $record) = each(%records)) {
# stuff goes here
}

$~ 是个系统变量,用于指定打印格式。

@<<< 左对齐输出 尖尖的个数,就是占的位数。 @>>> 右对齐输出
@||| 中对齐输出
@##.## 固定精度数字
@* 多行文本

select 可以改变缺省文件变量,这样就可以输出内容到文件了。

format 还可以设置页眉。。。

$^是页眉格式,$=是每页行数。还有个当前页的行计数器: $-

设置系统变量$|为非零值,则输出到文件的时候不使用缓冲。

内置了一套完整的文件处理函数,基本上和linux命令是同名的,用法也类似。比如: read/getc/mkdir/readdir/rmdir/rename/link/unlink/chown/stat 等等。

perl和C类似,存在指针,也可以叫“引用”。和C的&取地址符类似的是 \ 地址可以指向所有的类型,包括子程序。

perl也是面向对象的──类是一个Perl包,其中含提供对象方法的类。方法是一个Perl子程序,类名是其第一个参数。对象是对类中数据项的引用。

与包的引用结合,可以用单引号(‘)操作符来定位类中的变量,类中成员的定位形式如:$class’$member。在Perl5中,可用双冒号替代单引号来获得引用,如:$class’$member与$class::$member相同。

一个perl程序可以用package切分成很多个“包”,各包之间有独立的命名空间,而且程序可以随时在包之间来回切换。

perl里有两个和python的import类似的语法,require和use,require更像C的宏替换,use更像import。然后和 sys.path 类似的数组叫 @INC。

还有 cpan 很强大~

Common Internet File System

Common Internet File System 是samba的一部分,用于取代 smbfs 来挂载windows的共享文件夹,cifs比smbfs应用更广。
要使用 Common Internet File System 需要linux内核开启 cifs 支持。具体是要打开 File systems —> Network File Systems —> CIFS support (advanced network filesystem, SMBFS successor) 这个选项。如果是模块的话,使用前确保加载了。
然后,挂载共享文件夹,可以用mount命令的 -t cifs 选项来调用 mount.cifs。具体是:

sudo mount -t cifs //机器名或IP/远程/目录/ 本地挂载点 -o user=域/用户名%密码,iocharset=utf8

当然,没有域的话,也可以省略域。如果要指定其他mount的选项也是可以的,比如指定uid和gid之类的,这里就不多说了。

另外,如果gnome-base/gvfs开启了samba支持的话,也可以在nautilus的地址栏里直接输入 smb://机器名或IP/远程/目录/ 来打开远程目录,有密码时会弹出对话框输入。
这两种方法各有各的好处。

Google Storage

Google Storage是一个Google旗下的云存储服务,其数据存于美国的数个数据中心。主要面向开发者,免费提供100G的空间和300G的流量,当然,如果你愿意出点钱,可以获得更多。但是目前还处于测试阶段,不能直接注册,可以在这里申请,运气好的话,过几天就会收到试用邀请了。
我有幸获得了试用资格,体验了一下,就在这里记一下感想。

首先,Google Storage把所有存在上面的数据都放在一个个的“Bucket”里面,而这个bucket的名字,是所有用户共享的,也就是说,我建了个叫bones的bucket以后,其他人就不能再建这名的bucket了。
然后bucket里,就是一个个的object了。也就是说数据其实只有 bucket/object 两层,并没有常见文件树的嵌套目录结构。幸好object的名字可以带斜杆,而且web操作上也确实可以用带斜杆的object name来模拟目录结构。

其实,Google Storage采用Key/secret对的形式来进行权限管理,一个google账号可以同时建立5个Key/secret对。这样,在保证安全性的同时,也最大程度地提供了便利性。
权限这块,好像还可以把某个bucket的权限开放给指定的用户和组(就是google group),这个要用下面介绍的 GSUtil。

另外,Google Storage还提供了一个python写的命令行工具GSUtil,不过只有linux版和mac版,当我windows下安个cygwin还是可以跑的,呵呵。安装这个工具也很方便,直接下载解压到任意目录,然后配置一下环境变量,让系统能找到gsutil及相应的库就可以了,详见这里
首次运行gsutil,程序会让你输入Key/secret对,你把web上生成的信息输入进去,就可以使用了。
目前,使用这个工具可以建立/删除 bucket和object,以及上传或重命名文件;还可以用于设置权限等。

就玩了这么多,不过它还有个python的开发库,应该是可以重点研究的,如果能写个东西把这100G空间挂载到本地目录,应该还是蛮好玩的,呵呵。