TimothyQiu's Blog

keep it simple stupid

SS 源码阅读小记

分类:技术

阅读一下某源代码,学习一些 Python 的基本知识 = =

注意:以下内容均为 Python 基础知识笔记,几乎都可以通过上述途径获得。

肉馅

使用 hashlib 模组求哈希值,需要首先指定算法来创建 hash object,而后使用。

该模组提供了 new 函数,通过填入算法名得到 hash object,例如 hashlib.new('sha1') 创建一个使用 SHA-1 算法的 hash object,而另一种更快的方法是使用特定的函数直接创建,例如 hashlib.sha1()

至于 hash object,主要方法不外乎 update(添加被哈希的数据)和 digest(取哈希值的二进制表示)、hexdigest(取哈希值的十六进制表示)。

行李

所谓 Python 与 C Struct 交互的说法可能说得比较玄乎,也许可以描述成 Python 值与二进制数据的相互转换吧。

struct.pack(fmt, v1, v2, ...) 即将 v1v2 等数据以 fmt 规定的形式转换为二进制数据。而 struct.unpack(fmt, string) 反之,是将二进制数据解释到若干个 Python 值。

顺带一提,Lua 也有类似的模组:http://www.inf.puc-rio.br/~roberto/struct/

地图

接下来,string.maketrans(from, to) 的大意是返回一个长度为 256 的字符串作映射表用(可以提供给 string.translate 使用,表示将 ASCII 为 N 的字符映射到字符串中的第 N 个字节),其中已经把 from 中的每一个字节映射到了 to 里的相应位置。

题外话,Python 中的字符串求长度是没有 length 方法的(虽然有 __len__),只有一个 len 函数统一对各种可以求长度的对象求长度。这个让我想起了 C++11 中建议将 vector::begin 改为使用 ::begin 的做法。

阅读理解

List(列表)表示一个可变的序列。创建 List 可以使用方括号:

L = [] # 空
L = [expression, ...]

也可以使用 list 函数:

L = list() # 空
L = list(sequence)

更可以使用让 Python 做阅读理解的方法(好吧,是 List Comprehensions)。

正如喆哥在 Pipeline 中所说,将一个集合变换到另一个集合是一种非常常见的操作。Python 提供了 List Comprehensions 来提供这一机能:通过类似 [expression for variable in sequence] 的语法产生一个新的 List ,使得其中每一项都是满足后面「for 和 if 的组合」的 expression。举个栗子:

>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]

另外需要顺带一提的是,Python 的 for 循环只有类似 foreach 的 for x in y 形式。

列表排序

终于有一个正常向的小标题了 >.<

使用全局的 sorted 函数可以返回排序后的列表。而如果需要就地排序,可以使用 List 类型自带的 list.sort 方法来进行。

a = [5, 1, 4, 3]
print sorted(a) # [1, 3, 4, 5]
print a         # [5, 1, 4, 3]

可以通过给出其它参数来对排序过程进行详细控制,比如:

a = ['an', 'Apple', 'is', 'good']
print sorted(a)                # ['Apple', 'an', 'good', 'is']
print sorted(a, key=str.lower) # ['an', 'Apple', 'good', 'is']
print sorted(a, reverse=True)  # ['is', 'good', 'an', 'Apple']

也可以用 lambda 定义用于比较的函数:

a = ['an', 'Apple', 'is', 'good']
print sorted(a, lambda x,y: cmp(str.lower(x), str.lower(y))) # 效果和上面使用 key 的例子相同

古董

曾几何时的 Visual Basic 中就有这么一对函数:ChrAsc,分别用于将 ASCII 值转为字符、将字符转为 ASCII 值。

而到了 Python 中,就是 chrord 函数,不同的是,针对 Unicode 还有一个 unichr

开膛破肚

对 List 除了可以使用下标进行普通的 index 操作外,还有一种名为 slice 的操作,使用「slice notation」表示:起始下标:结束下标:步长

yuruyuri = ['akari', 'kyouko', 'yui', 'chinatsu']
print yuruyuri[1]    # 'kyouko'
print yuruyuri[1:3]  # ['kyouko', 'yui']
print yuruyuri[1:-1] # ['kyouko', 'yui']
print yuruyuri[1::2] # ['kyouko', 'chinatsu']

可以看出,结束下标是不包含在最终结果里的哟。另外,每一个数字都可以省略:省略起始下标则默认为 0,省略结束下标则默认为全长,省略步长则默认为 1。

而对 slice 的直接赋值,会对原 List 造成修改:

yuruyuri[0:1] = []
print yuruyuri        # ['kyouko', 'yui', 'chinatsu']
yuruyuri[0:1][0] = ''
print yuruyuri        # ['kyouko', 'yui', 'chinatsu']

题外话,「slice notation」还可以用在字符串上,使得产生一个字串。而由于 Python 的字符串不像 C 的字符串,是不可以通过 s[2] = 'a' 更改内容的,于是通过 slice 来进行就方便多了。(s[:n] + s[n:] == s 哟。)

天下大势

将字符串拆分为子字符串列表、将字符串列表合并为一个字符串,这些也是常见操作。Python 中实现这种机能的函数分为两组:

其实都是一样的作用,只是前者是内置字符串类的方法,后者是 string 模组的函数。

红色警戒 II

类似于 C++ 的 RAII 和 C# 的 using,Python 有 with 语句。

熟悉的应该一看就懂了吧~(虽然我不清楚三者是否可以等价,但从效果上说还是差不多的嘛~)

Google Talk Bot in Python

分类:技术

上班或者出门在外的时候有没有想过用家里闲着的电脑干点什么事呢?嘛~一切的可能性都得建立在你能连接得到自己的电脑之上才行。家里的电脑不比服务器,IP 并不固定,怎么办呢?解决方法挺多,比如花生壳,比如用 corn 发定时邮件……

对于我来说,写个会告诉你自己 IP 的 GTalk 机器人,想 SSH 连回家之前问一下是最方便的了。Lua 的 XMPP 库完全不懂怎么用,那么就先学现卖用 Python 写咯~

获取本机的公网 IP

由于使用了路由器,直接 ifconfig 是得不到公网 IP 的。那么可以直接在自己的网站上放一个 PHP 页:

<?php echo $_SERVER['REMOTE_ADDR']; ?>

或者利用现成的服务,比如 WhatIsMyIP.com 的自动化支持页面。有了这样一个可以获得访问者 IP 的页面地址,那么就可以用 Python 的 urllib 模块获得该页面的内容,从而得到自己的 IP 地址:

import urllib
ip = urllib.urlopen('http://www.whatismyip.com/automation/n09230945.asp').read()

简单的 GTalk 机器人

Google Talk 因为使用的是开放的 XMPP 协议,用各种语言实现机器人非常容易。(同样用 XMPP 协议的还有网易泡泡和人人桌面,而且据说微软的 Live Messager 最近也要支持 XMPP 协议了呢……腾讯啊~你不考虑来一发么?)比如接下来用的就是 xmpppy 库。

#! /usr/bin/env python
# vim: set fileencoding=utf-8

import xmpp
import urllib
import sys
from datetime import datetime

# 用来当机器人的用户名和密码
username = 'username@gmail.com'
password = 'password'

# 用来获取 IP 的 URL
ipurl = 'http://automation.whatismyip.com/n09230945.asp'

# 命令对应回复的处理
commands = {
    'ip':   lambda text: urllib.urlopen(ipurl).read(),
    'echo': lambda text: text,
}

def MessageCB(conn, mess):
    text = mess.getBody()
    user = mess.getFrom()

    if text is None: text = ''

    mail = user.getNode() + '@' + user.getDomain()
    print datetime.now().strftime('%H:%M:%S'), mail, text

    if ' ' in text: command, args = text.split(' ', 1)
    else:           command, args = text, ''

    if commands.has_key(command):   reply = commands[command](args)
    else:                           reply = unicode('主人已经被我干掉了', 'utf-8')

    conn.send( xmpp.Message(user, reply) )

def StepOn(conn):
    try:
        result = conn.Process(1)
    except KeyboardInterrupt: return 0
    return 1

def GoOn(conn):
    while StepOn(conn): pass

conn = xmpp.Client('gmail.com', debug=[])

if not conn.connect( server=('talk.google.com', 5223) ):
    print 'Unable to connect to server.'
    sys.exit(1)

if not conn.auth(xmpp.JID(username).getNode(), password, 'python'):
    print 'Unable to authorize.'
    sys.exit(1)

conn.RegisterHandler('message', MessageCB)
conn.sendInitPresence()
print 'Bot started.'
GoOn(conn)

上面的这段主要就是定义了两个命令 ip 和 echo,分别负责回答本机 IP 和学舌,其它情况下机器人都只会回复「主人已经被我干掉了」。

要扩展起来,在 commands 字典里添加条目即可 :)

初次写 Python 代码,不知道是不是写得 Pythonic……

p.s. 最近升级了 php 后网站就非常不稳定,暂时还不知道肿么办貌似是因为我某次把 crond 服务干掉了,于是长期没有 logrotate 的原因 =3=