<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>年华转瞬 &#187; Python开发</title>
	<atom:link href="http://blog.xiaket.org/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.xiaket.org</link>
	<description>xiaket 的网志</description>
	<lastBuildDate>Sat, 21 Aug 2010 02:31:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>自定义python类方法实现加减比较操作</title>
		<link>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/</link>
		<comments>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/#comments</comments>
		<pubDate>Sat, 19 Jun 2010 03:34:04 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[Django开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=429</guid>
		<description><![CDATA[最近在写一个组内项目, 需要把一群一群的类似字典的对象实现类似相加的逻辑. 为了让代码更好看逻辑性更强, 偶玩了下python的自定义类方法.

首先更详细地介绍下背景: 我们需要在Django中记录一个模型变化的历史, 我们有需求会将这个模型的某个实例的现在的状态和它的若干个变化历史相加. 为此, 我们用Django的序列化方法把这个实例导出成一个json字符串, 然后用这个字符串实例化一个类似字典的类. 变化历史的各种相加相减操作就是通过自定义这个类似字典的类的特殊的类方法而实现的.

既然这个类是用一个json字符串来实例化的, 我们简单地将这个类的名字定为Json, 其初始化方法类似:

<span class="readmore"><a href="http://blog.xiaket.org/2010/06/19/python-class-methods-customize/" title="自定义python类方法实现加减比较操作">阅读全文——共3611字</a></span>]]></description>
			<content:encoded><![CDATA[<p>最近在写一个组内项目, 需要把一群一群的类似字典的对象实现类似相加的逻辑. 为了让代码更好看逻辑性更强, 偶玩了下python的自定义类方法.</p>
<p>首先更详细地介绍下背景: 我们需要在Django中记录一个模型变化的历史, 我们有需求会将这个模型的某个实例的现在的状态和它的若干个变化历史相加. 为此, 我们用Django的序列化方法把这个实例导出成一个json字符串, 然后用这个字符串实例化一个类似字典的类. 变化历史的各种相加相减操作就是通过自定义这个类似字典的类的特殊的类方法而实现的.</p>
<p>既然这个类是用一个json字符串来实例化的, 我们简单地将这个类的名字定为Json, 其初始化方法类似:</p>
<pre class="brush: python;">
class Json(object):
    &quot;&quot;&quot;
    This class is used to hold a model instance's information.
    &quot;&quot;&quot;
    def __init__(self, _json_string):
        &quot;&quot;&quot;
        This class is initialized with a json formatted string.

        The json formatted string is provided using django's serialization
        tool, which accept a QuerySet or a list as an object. So here we have
        to retrieve the first(and the only) object in the list.
        &quot;&quot;&quot;
        self.init_string = _json_string
        _dictionary = json.loads(_json_string)[0]
        self.model = _dictionary['model']
        self.object_id = self.pk = _dictionary['pk']
        self.fields = _dictionary['fields']
</pre>
<p>看起来很长一段代码, 实际上只是定义了若干基本类属性而已. 这些属性包括初始化字符串, 被序列化的实例的模型和主键, 以及一个存放原实例的属性的self.fields字典. 如果对这些名字不太熟悉可以自行去围观下django的序列化工具的json输出, <a href="http://docs.djangoproject.com/en/dev/topics/serialization/#topics-serialization">这个页面</a>上有例子.</p>
<p>Json这个类只是存放了类的序列化的内容, 为了记录历史, 我们需要将两个这样的类相减以得出变化. 在定义相减这个操作之前, 我们需要定义一个用来存放历史变化的类:</p>
<pre class="brush: python;">
class Jsondiff(object):
    &quot;&quot;&quot;
    This object is used to save substraction results from two Json objects.
    &quot;&quot;&quot;
    def __init__(self, _diffdict):
        &quot;&quot;&quot;
        Input format specification
        --------------------

        the input json string represents a dictionary of differences. Like:
            {
                'fieldname': (newvalue, oldvalue),
            }
        It also contains a special field named '_model', containing the name of
        the model and the id of the object.
        A realworld example is provided below:
            {
                u'_object': (u'gsdb.server', 1),
                u'tag': (u'12535', u'12537'),
            }
        &quot;&quot;&quot;
        self.fields = _diffdict
        self.model = self.fields['_object'][0]
        self.object_id = self.fields['_object'][1]
        del self.fields['_object']
</pre>
<p>同样的, 看上去很长的代码只是干了很有限的几件事情: 从类初始化参数中拿到实例的模型和主键, 作为类属性保存起来, 并从初始化参数中删除这个用于存放元信息的特殊键值, 这样我们再后面进行各种操作时就不需要再讨论这个特殊的键值了.</p>
<p>OK, 两个类已经定义完了, 我们开始定义一些特殊的类方法. 首先, 我们需要定义一个方法能够比较两个Json实例的区别以得到一个Jsondiff实例. 我们需要实现下面的代码:</p>
<pre class="brush: python;">
jsondiff0 = json1 - json2
</pre>
<p>为此, 我们需要自定义Json的__sub__方法:</p>
<pre class="brush: python;">
    def __sub__(self, _json):
        &quot;&quot;&quot;
        This method would substract two Json objects and give a Jsondiff
        object.

        The object represented by _json is the old object, 'new - old' would
        make more sense than 'old - new'.
        &quot;&quot;&quot;
        _diffdict = {
            '_object': (self.model, self.object_id),
        }
        for _field in self.fields:
            if self.fields[_field] != _json.fields[_field]:
                _diffdict[_field] = (self.fields[_field], _json.fields[_field])
        return Jsondiff(_diffdict)
</pre>
<p>这段代码很容易理解. 你不是要比较两个Json对象吗? 既然所有相关的值都在self.fields里面, 那我们就一个个地进行比较, 如果有不同的地方就按照一个预设的格式记录下来. 我们这儿将这些记录放到一个字典里面, 处理完每个键后就直接将这个字典作为初始化Jsondiff类的参数实例化一个Jsondiff对象, 然后就可以直接返回这个对象了. 到这儿, 我们已经实现了前面写的json对象相减的代码. 下一步, 既然已经写了减法, 不写加法就不合理了:</p>
<pre class="brush: python;">
# Since we can do:
jsondiff0 = json1 - json2
# we wanna have this, too:
json1 = json2 + jsondiff0
</pre>
<p>好吧, 我们现在来定义加法:</p>
<pre class="brush: python;">
    def __add__(self, _jsondiff):
        &quot;&quot;&quot;
        This method would add a Json to a Jsondiff object.
        &quot;&quot;&quot;
        _sum = copy.deepcopy(self)
        for _field in _jsondiff.fields:
            _new, _old = _jsondiff.fields[_field]
            if _sum.fields[_field] != _old:
                raise RuntimeError
            else:
                _sum.fields[_field] = _new
        return _sum
</pre>
<p>此处, 为了避免执行加法后改变已有实例的属性, 我们用deepcopy将原来的Json对象复制了一份. 然后从Jsondiff里面读键值, 对于每个键值看看diff里的内容和当前的实例的属性有没有冲突, 以确定我们要进行的操作是不是合法的. 如果一切都没问题, 返回一个Json对象.</p>
<p>为了让这一切更和谐, 我们还定义了一个Jsondiff的__add__方法, 这样我们哪天把前后顺序弄反了也不会有多大的事情:</p>
<pre class="brush: python;">
    def __add__(self, _json):
        &quot;&quot;&quot;
        This method would add a Jsondiff to a Json object.
        &quot;&quot;&quot;
        return _json + self
</pre>
<p>有了这段代码, 我们在进行Json对象和Jsondiff对象的加法时可以肆无忌惮地将Jsondiff放在前面或者后面了. 而如果没有这个对象, 我们只能老老实实地将Jsondiff放在后面.</p>
<p>后来在使用中, 我发现我经常会需要比较两个Json对象的创建时间来比较哪个更新一点. 为了简化这些操作, 我给Json对象添加了一个date属性, 记录原模型的修改时间. 这样我每次只需要比较Json对象的date属性即可. 既然已经有了这个属性, 我们可以更进一步的定义__cmp__方法来使Json对象的比较更美观更和谐:</p>
<pre class="brush: python;">
    def __cmp__(self, _json):
        &quot;&quot;&quot;
        This method would compare two Json object's date.
        &quot;&quot;&quot;
        return cmp(self.date, _json.date)
</pre>
<p>定义了这个方法后, 我们能做类型下面的操作:</p>
<pre class="brush: python;">
&gt;&gt;&gt; json1 &gt; json2
True
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/06/19/python-class-methods-customize/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>python实现本地邮件发送</title>
		<link>http://blog.xiaket.org/2010/05/20/python-send-local-mail/</link>
		<comments>http://blog.xiaket.org/2010/05/20/python-send-local-mail/#comments</comments>
		<pubDate>Thu, 20 May 2010 13:48:00 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Linux相关]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[slackware]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=409</guid>
		<description><![CDATA[之前一直忍受Thunderbird的荼毒, 换上KMail后一身轻, 感觉很开心. 于是有了将本地报警和通知转成邮件的冲动. 作为第一步, 就是要用KMail收取本地的邮件. 今天就整了整这个.

要用KMail收取本地邮件, 你需要一个本地邮件服务器(更具体的说是MTA). 这个我用的是exim, 因为Slackware默认的sendmail比我的岁数都大, 而且实际使用中也发现有些小问题. exim的安装和配置比较容易, 我基本上就只是改了默认端口而已.

接下来是要写一个程序能够发送本地邮件. 这个功能是smtplib这个库实现的. 基本的框架是这样的(继续无耻抄袭python官方文档):

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/20/python-send-local-mail/" title="python实现本地邮件发送">阅读全文——共1681字</a></span>]]></description>
			<content:encoded><![CDATA[<p>之前一直忍受Thunderbird的荼毒, 换上KMail后一身轻, 感觉很开心. 于是有了将本地报警和通知转成邮件的冲动. 作为第一步, 就是要用KMail收取本地的邮件. 今天就整了整这个.</p>
<p>要用KMail收取本地邮件, 你需要一个本地邮件服务器(更具体的说是MTA). 这个我用的是exim, 因为Slackware默认的sendmail比我的岁数都大, 而且实际使用中也发现有些小问题. exim的安装和配置比较容易, 我基本上就只是改了默认端口而已.</p>
<p>接下来是要写一个程序能够发送本地邮件. 这个功能是smtplib这个库实现的. 基本的框架是这样的(继续无耻抄袭python官方文档):</p>
<pre class="brush: python;">
import sys
import smtplib

msg = sys.argv[1]
fromaddr = &quot;&lt;xiaket@bolt&gt;&quot;
toaddrs = &quot;&lt;xiaket@bolt&gt;&quot;
server = smtplib.SMTP('localhost')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, msg)
server.quit()
</pre>
<p>这段代码里的bolt是我的主机名.</p>
<p>一切正常的话, 执行这个脚本应该能够给自己的用户发一封邮件, 你可以从debug信息里面看到发送过程中你的python脚本和服务器的通讯. 邮件发送完成后, 你可以在本地邮箱(例如: /var/mail/xiaket)里找到它的内容. KMail的配置也很简单, 给出到本地邮箱的路径即可.</p>
<p>接下来要做的事情就是完善我们的python脚本了. 我们要实现的目标是, 既能够当作本地命令执行又能够作为python模块被导入. 于是大概的框架应该是这样的:</p>
<pre class="brush: python;">
import smtplib
import sys

def send(message):
    fromaddr = &quot;&lt;xiaket@bolt&gt;&quot;
    toaddrs = &quot;&lt;xiaket@bolt&gt;&quot;

    # I used a bizzare port to receive mail.
    server = smtplib.SMTP('localhost', '9025')
    server.sendmail(fromaddr, toaddrs, message)
    server.quit()

if __name__ == &quot;__main__&quot;:
    if len(sys.argv) != 2:
        print &quot;Usage: mailer message&quot;
        exit(1)
    send(sys.argv[1])
</pre>
<p>不过现在还有一个问题是这样发出来的邮件没有标题, 编码和From/To信息. 找了找文档, 发现应该用email这个库, 用类似字典键值的方式给MIMEText这个类的实例添加属性, 最后用一个as_string转成字符串传给sendmail即可.</p>
<pre class="brush: python;">
from email.mime.text import MIMEText

mail = MIMEText(content, 'plain', 'utf-8')
mail['Subject'] = subject
mail['From'] = &quot;%s &lt;xiaket@localhost&gt;&quot; % category
mail['To'] = 'me &lt;xiaket@localhost&gt;'
server.sendmail(fromaddr, toaddrs, mail.as_string())
</pre>
<p>这样, <a href="http://blog.xiaket.org/wp-content/uploads/2010/05/mailer.py" target="_blank">代码</a>基本就完成了, 把这段代码命名为mailer.py, 放到我自己定义的PYTHONPATH里, 然后做一个软链接放到PATH下, 测试完成以后就大功告成了~ 使用起来还是挺方便的, 在命令行下:</p>
<pre class="brush: bash;">
mailer 测试类别 测试主题 测试内容
</pre>
<p>在python里面调用函数:</p>
<pre class="brush: python;">
&gt;&gt;&gt; from mailer import send
&gt;&gt;&gt; send(category=&quot;测试类别&quot;, subject=&quot;测试主题&quot;, content=&quot;测试内容&quot;)
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/20/python-send-local-mail/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>用python的单元测试工具写网易微博API的测试样例</title>
		<link>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/</link>
		<comments>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/#comments</comments>
		<pubDate>Sun, 16 May 2010 14:59:45 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[decorator]]></category>
		<category><![CDATA[网易微博]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=403</guid>
		<description><![CDATA[虽然之前在Django项目中也不完整地用过单元测试工具, 但是今天是第一次用python的单元测试工具完整写出一个项目的测试样例, 于是小记录下~

需求: 测试网易微博API的python实现.

源代码可见: http://code.google.com/p/163microblog/source/browse/trunk/tests.py

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/" title="用python的单元测试工具写网易微博API的测试样例">阅读全文——共2639字</a></span>]]></description>
			<content:encoded><![CDATA[<p>虽然之前在Django项目中也不完整地用过单元测试工具, 但是今天是第一次用python的单元测试工具完整写出一个项目的测试样例, 于是小记录下~</p>
<p>需求: 测试网易微博API的python实现.</p>
<p>源代码可见: <a href="http://code.google.com/p/163microblog/source/browse/trunk/tests.py" target="_blank">http://code.google.com/p/163microblog/source/browse/trunk/tests.py</a></p>
<p>话说对于偶这种初级民工, 写代码喜欢不需要动脑子地先搭个架子出来然后往里面填空, 不过偶连这个架子也是从python文档里面抄出来的:</p>
<pre class="brush: python;">
import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def testlogin(self):
        self.session = Session(username=&quot;xiaket@163.com&quot;, password=&quot;notmypass&quot;)
        self.assertEqual(self.session.screen_name, &quot;xiaket&quot;)

if __name__ == &quot;__main__&quot;:
    unittest.main()
</pre>
<p>这个东西貌似是能够跑, 但是有个比较不爽的地方是密码必须放到测试样例里面去, 这个让偶很不爽. 于是网上搜了搜, 看到一个继承unittest.TestCase这个基类的__init__方法的办法:</p>
<pre class="brush: python;">
import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def __init__(self, testname, username=None, password=None):
        super(SessionTests, self).__init__(testname)
        self.username = username
        self.password = password

    def testlogin(self):
        session = Session(username=self.username, password=self.password)
        self.assertEqual(session.screen_name, &quot;xiaket&quot;)
</pre>
<p>这样你在另外一个文件里面就能够对这个类进行测试了:</p>
<pre class="brush: python;">
import unittest

from t163.tests import SessionTests

if __name__ == &quot;__main__&quot;:
    username = &quot;xiaket@163.com&quot;
    password = &quot;notmypass&quot;
    suite = unittest.TestSuite()
    suite.addTest(SessionTests(&quot;testlogin&quot;, username, password))
    unittest.TextTestRunner().run(suite)
</pre>
<p>架子搭好了, 剩下的就是苦力活, 往里面填测试样例了. 这个过程比较纠结比较繁复, 而且伴随着测试样例的逐渐完成, 我还fix了一群以前代码中的bug. 另外, 由于现在网易微博还比较不成熟, 我还不得不为一群server的错误擦屁股, 写了几个装饰器来修正API的行为. 例如, 有个微博的API能够给出follow某用户的用户的信息.当给出的用户的screen_name参数不正确时, 我们理应期待服务器返回错误代码. 但是服务器比较出人意料地返回了follow当前登录用户的用户的信息. 这导致所有查询都不是完全可靠的, 除非你在每次查询前确定你查询所使用的screen_name是有效的. 于是, 我写了这样一个装饰器加到已有函数的前面:</p>
<pre class="brush: python;">
def check_screen_name(func):
    &quot;&quot;&quot;
    This decorator would check the screen_name in the parameter of the original
    function.

    It is to be noted that the screen must be the first argument if we are
    using a positional parameter.
    &quot;&quot;&quot;
    def morewrapped(func):
        def wrapped(kls, *args, **kwargs):
            if 'screen_name' in kwargs:
                _screen_name = kwargs['screen_name']
            elif len(args):
                _screen_name = args[0]
            else:
                _screen_name = None
            if _screen_name:
                # If the screen_name is set, we shall check if it is a valid
                # screen_name. We do this by visiting the homepage of this
                # screen_name:
                _url = &quot;/%s&quot; % _screen_name
                _message = &quot;Specified user does not exist.&quot;
                _err_dict = {
                    404: (UserNotFound, _message),
                }
                kls.request(_url, errors=_err_dict)
            return func(kls, *args, **kwargs)
        return wrapped
    return morewrapped(func)
</pre>
<p>我们通过能否访问这个用户的主页来判断这个用户是否存在. 如果这个主页不能够被正常访问, 我们就抛出一个404, 这样就能相信followers这个API的结果了. 另外, 使用装饰器的好处在于, 哪天服务器端的程序更新了, 我们的代码不用做太大的修改就可以继续使用.</p>
<p>好吧, 话题扯回来, 总结下写了这么长时间的测试样例, 有啥经验:</p>
<ul>
<li>将测试代码分类, 这样什么时候改动了一个地方, 不需要测试所有的样例.</li>
<li>这个项目主要测试的内容是异常的处理, 于是可以先写好所有的会抛异常的代码, 然后一边测试一边添加assert语句.</li>
<li>考虑问题要全面, 当然这个比较废话了, 每个人都希望如此.</li>
<li>如果测试需要网络交互, 或者速度较慢的话, 用&#8221;"&#8221;将测过的不影响逻辑的代码注释起来, 这样能有效地提高速度.</li>
<li>注意添加注释, 这样你能够更有效地重用你自己的代码.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/16/python-unittest-on-t163-api/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>研究网易微博API &#8211; 2</title>
		<link>http://blog.xiaket.org/2010/05/02/t-163-com-api-part/</link>
		<comments>http://blog.xiaket.org/2010/05/02/t-163-com-api-part/#comments</comments>
		<pubDate>Sun, 02 May 2010 15:09:22 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[网易微博]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=394</guid>
		<description><![CDATA[前几天经freestyler介绍, 从Tux那儿拿到了一份网易微博API的文档. 然后用python实现了一遍, 具体的代码现在还没正式发布, 只是放在的google code上: http://code.google.com/p/163microblog/. 关于网易微博API, 首先现在还在紧张开发中, 所以现在放出来的实现都是非官方的. 其次, 网易微博API很多地方参考了Twitter的API, 再次, 很多时候像我上一篇文章所讲的, 直接分析HTTP通讯也能给出一份可能不完全的API来&#8230;

<span class="readmore"><a href="http://blog.xiaket.org/2010/05/02/t-163-com-api-part/" title="研究网易微博API &#8211; 2">阅读全文——共3154字</a></span>]]></description>
			<content:encoded><![CDATA[<p>前几天经<a href="http://t.163.com/emacs" target="_blank">freestyler</a>介绍, 从<a href="http://t.163.com/Tux" target="_blank">Tux</a>那儿拿到了一份网易微博API的文档. 然后用python实现了一遍, 具体的代码现在还没正式发布, 只是放在的google code上: <a href="http://code.google.com/p/163microblog/" target="_blank">http://code.google.com/p/163microblog/</a>. 关于网易微博API, 首先现在还在紧张开发中, 所以现在放出来的实现都是非官方的. 其次, 网易微博API很多地方参考了Twitter的API, 再次, 很多时候像我上一篇文章所讲的, 直接分析HTTP通讯也能给出一份可能不完全的API来&#8230;</p>
<p>今天早上无聊用python和已经写好的python的API实现写了一个客户端起来. 代码可参考: <a href="http://163microblog.googlecode.com/files/163microblog-cli.tar.gz" target="_blank">http://163microblog.googlecode.com/files/163microblog-cli.tar.gz</a></p>
<p>下面简单记录下开发过程.</p>
<h3>设计</h3>
<p>偶想做的东西是一个命令行下能够访问微博的工具, 能够看见自己的timeline, 能够发推就可以了. 不需要复杂的功能. 这种东西不需要什么前期设计, 从头开始往下写就是了&#8230;</p>
<h3>登录和loop</h3>
<p>用raw_input拿到用户名和密码, 偶写的API部分, 主类的实例化完成就已经完成了登录的过程.</p>
<pre class="brush: python;">
    username = raw_input('Please enter your username: ')
    password = raw_input('Please enter your password: ')
    usersession = Session(username, password)
</pre>
<p>返回usersession后, 你可以随意地调用网易微博的API了, 具体每个API是干嘛的, 偶希望偶写的注释已经比较详细了&#8230;</p>
<p>接下来, 偶们写一个死循环来接收用户输入和响应.</p>
<pre class="brush: python;">
    while True:
        cmd = raw_input('Please enter your command: ')
        if cmd == 'home':
            home(usersession)
        elif cmd == 'post':
            text = raw_input('Please enter your status: ')
            post(usersession, text)
        elif cmd == 'quit' or cmd == 'exit':
            print &quot;    Bye~&quot;
            break
        else:
            help()
</pre>
<p>一开头就提示用户输入命令, 然后等, 用户输了命令就执行, 然后继续等&#8230;</p>
<h3>用户的timeline</h3>
<p>这个在网页上就是显示你的首页啦. 先放代码吧:</p>
<pre class="brush: python;">
def home(session):
    &quot;&quot;&quot;
    Display one's timeline.
    &quot;&quot;&quot;
    statuses = session.home_timeline(count=8)
    for status in statuses:
        print &quot;%(text)s - %(user)s - %(time)s\n&quot; % {
            'text': parse_text(status.text),
            'user': colored(status.user.name, 'green'),
            'time': colored(parse_time(status.created_at), 'red'),
        }
</pre>
<p>用session.home_timeline()拿了8条微博, 因为默认的30条太多了&#8230; 接下来就是要让这个东西能够比较漂亮地显示出来. 我们要显示的内容应该包括推的内容, 发推时间和作者. 为了让这些东西能够区分开来, 偶想到了用ASCII颜色. 顺手一搜, 找到一个pypi里的包能够干这个事情, 链接是: <a href="http://pypi.python.org/pypi/termcolor" target="_blank">termcolor</a>. 偶把这个拿过来, 放在目录下就直接用了. 反正这个软件偶没改动, 而且也不打算改动, 它发布的协议和偶发布的网易微博API的协议都是一样的. 这个东西用起来还是挺省心的, 像上面代码中那样用一个colored()就可以了.</p>
<p>接下来解释下代码中status和status.user是什么东西. 这两个是偶定义两个类, 一个是Status, 一个是T163UserBase, 具体定义在微博API的models.py文件中. 定义这两个类是为了更方便地处理网易微博返回的json文件的格式. Status这个类对应你在网页上看到的一条推, 而T163UserBase对应一个用户的基本信息. 这个类通过继承能够用来放登录用户本人的个人信息, 也能够用来放关注者和被关注者的信息. 一条推肯定有一个作者, 这个作者就是一个用户, 因此每条推实例化的时候都会和一个用户的实例关联起来, 这就是为啥status这个Status的实例有一个T163UserBase的实例.</p>
<p>了解了这个, 后面的打印就没什么好讲的了. 写个循环, 依次将推的内容, 推的作者, 发推的时间打印出来就行了. 好吧, 这儿还稍复杂了点, 推里面的一些符号, 例如大于号(&gt;, &amp;gt;)和小于号(&lt;, &amp;lt;)被转义了, 偶要把这个转回去, 所以写了一个parsed_text函数(好吧, 实际上是抄的&#8230;). 另外, 偶的Status实例里的时间是UTC的, 偶要按照用户的时区把它转回来. 想了一个很山寨的办法写了个parse_time函数来干这件事.</p>
<h3>发推</h3>
<p>发推是用update这个函数来实现的, 代码如下:</p>
<pre class="brush: python;">
def post(session, text):
    &quot;&quot;&quot;
    Post a new status to t.163.com
    &quot;&quot;&quot;
    status = session.update(text)
    if isinstance(status, Status):
        print &quot;done~ Refreshing...&quot;
        home(session)
    else:
        print &quot;Something went wrong...&quot;
</pre>
<p>按照API的说明, 用session.update()函数后, 服务器端应该返回发出的推的相关信息, 正好用来实例化一个Status. 所以偶的python实现也是将服务器返回的json文件解析后丢给Status. 因此, 如果这儿的status是一个Status实例, 那么偶们的推就成功地发出去了. 偶在这儿写了一个自动刷新. 调用刚才的home函数来显示更新后的用户的timeline.</p>
<h3>登录优化</h3>
<p>之前的登录很傻, 每次都需要用户输入用户名和密码, 偶优化了下这部分, 如果发现一个貌似cookie的东西, 就尝试用这个cookie来登录, 如果成功, 就不找用户要密码了. 这样更人性化一点~</p>
<pre class="brush: python;">
from os.path import isfile

def main():
    username = raw_input('Please enter your username:')
    if isfile(&quot;%s.txt&quot; % username):
        try:
            usersession = Session(username)
        except RuntimeError:
            password = raw_input('Please enter your password:')
            usersession = Session(username, password)
    else:
        password = raw_input('Please enter your password:')
        usersession = Session(username, password)
</pre>
<p>为了实现这一点, 偶还修改了API部分的相关代码, 去掉了Session类实例化部分对password的要求. 不过判断登录是否成功的部分代码还很山寨, 还需要修改.</p>
<p>当然偶们也可以写得更人性化一点, 如果发现一个疑似cookie的东西就先试试, 这样和web的用户体验就很相似了, 不过偶懒&#8230; 反正偶打字还算快, 不管了&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/05/02/t-163-com-api-part/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>研究网易微博API &#8211; 1</title>
		<link>http://blog.xiaket.org/2010/04/26/t-163-com-api-part1/</link>
		<comments>http://blog.xiaket.org/2010/04/26/t-163-com-api-part1/#comments</comments>
		<pubDate>Sun, 25 Apr 2010 16:24:45 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[网易微博]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=391</guid>
		<description><![CDATA[有朋友要我帮他写一个偶们家微博的客户端, 今天晚上研究了下登录过程, 主要用到了urllib2和cookielib这两个库. 第一次在python里面用cookie, 之前都是用curl搞定的, 于是记录下~

背景

登录地址: http://t.163.com/session

<span class="readmore"><a href="http://blog.xiaket.org/2010/04/26/t-163-com-api-part1/" title="研究网易微博API &#8211; 1">阅读全文——共2261字</a></span>]]></description>
			<content:encoded><![CDATA[<p>有朋友要我帮他写一个偶们家微博的客户端, 今天晚上研究了下登录过程, 主要用到了urllib2和cookielib这两个库. 第一次在python里面用cookie, 之前都是用curl搞定的, 于是记录下~</p>
<h3>背景</h3>
<p>登录地址: http://t.163.com/session</p>
<p>相关工具: firefox + httpfox</p>
<h3>账号校验</h3>
<p>首先在Firefox里面登录, 拿到这个过程的HTTP通讯记录. 为了避免已有的cookie对登录过程的影响, 我们用Firefox提供的隐私浏览模式来模拟一个完全没有cookie的用户的登录过程. 根据通讯记录来看, 登录过程首先校验了用户账号, 往http://t.163.com/account/passport/check这个url POST了用户名, 服务器返回一个json, 里面只有一个字段, status. 如果用户账号存在则值为0, 否则为1.</p>
<pre class="brush: python;">
import urllib2
from urllib import urlencode
url = &quot;http://t.163.com/account/passport/check&quot;
para = urlencode({'userName': 'xiaket@163.com'})
f = urllib2.urlopen(url, para)
print f.read()   # {&quot;status&quot;: &quot;0&quot;}
para = urlencode({'userName': 'xiaket@163.cm'}) # no such account.
f = urllib2.urlopen(url, para)
print f.read()   # {&quot;status&quot;: &quot;1&quot;}
</pre>
<p>根据返回的内容来看, 这个步骤没有设置任何cookie, 我们可以放心地继续了.</p>
<h3>输入用户名和密码, 设置cookie</h3>
<p>继续看通讯记录, 如果上一步没问题应该会往https://reg.163.com/logins.jsp这个url发送登录用户名, 密码和相关选项. 这个步骤会设置cookie, 所以我们需要用cookielib创建的opener来打开这个url.</p>
<pre class="brush: python;">
url = 'https://reg.163.com/logins.jsp'
data = {'username': 'xiaket@163.com', 'password': 'notmypass', 'savelogin': '1', 'url': 'http://t.163.com/session/first', 'product': 't', 'type': '1'}
para = urlencode(data)
cj = cookielib.MozillaCookieJar()
cj.save('E:\\t163.txt')
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
f = opener.open(url, para)
cj.save('E:\\t163.txt')
</pre>
<p>此时, 我们应该能够在t163.txt这个文件中看到一些cookie的内容了~</p>
<h3>第一次重定向</h3>
<p>如果成功登录, 上一步返回的页面里面是一个重定向的网页. 这个页面里面的链接用HTML代码(&#8216;&#开头的小于256的十进制数&#8217;)转义了所有的字符. 一时没找到合适的库函数, 自己山寨了一段转换的代码:</p>
<pre class="brush: python;">
from urllib import unquote
lines = f.read().split('\r\n')
for line in lines:
    if line.find('&lt;a href=') != -1:
        linkline = line
start = linkline.index(&quot;ref='&quot;) + 5
end = linkline.index(&quot;'&gt;&lt;&quot;, start)
reallink = ''
link = linkline[start:end]
for numstr in link.split('&amp;#'):
    if numstr == '':
        continue
    else:
        hexstr = hex(int(numstr))
        reallink += ('%' + hexstr.replace('0x', ''))
reallink = urllib.unquote(reallink)
</pre>
<p>此时就能拿到一个以http://reg.youdao.com/crossdomain.jsp?username=开头的url. 我们下一步就要用这个url了.</p>
<h3>第二次重定向</h3>
<p>我们继续访问上面给出的reallink这个url:</p>
<pre class="brush: python;">
f = opener.open(reallink)
cj.save('E:\\t163.txt')
</pre>
<p>此时, 我们会发现这个文件里有多了youdao.com写的cookie.</p>
<p>这次访问返回的页面是用js重定向到http://t.163.com/session/first?username=xiaket这个url. 我们于是拿已有的opener继续打开这个页面:</p>
<pre class="brush: python;">
nurl = &quot;http://t.163.com/session/first?username=xiaket&quot;
f = opener.open(nurl)
lines = f.readlines()
for line in lines:
    print line.decode('utf8')
</pre>
<p>此时已经能够正常给出微博首页的url了. 晚了, 下次继续&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/04/26/t-163-com-api-part1/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Bloody Monday2第一集观后</title>
		<link>http://blog.xiaket.org/2010/01/28/bloody-monday2-ep01/</link>
		<comments>http://blog.xiaket.org/2010/01/28/bloody-monday2-ep01/#comments</comments>
		<pubDate>Thu, 28 Jan 2010 11:52:24 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[电视]]></category>
		<category><![CDATA[Python开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=289</guid>
		<description><![CDATA[Bloody Monday是南京大学小百合bbs上的VarVar童鞋推荐我看的一部日剧. 看这个倒也是因为这个日剧里面有谈黑客和入侵相关的内容. 而且让我这个Python粉丝很高兴的是里面的超级黑客猎鹰所使用的脚本语言有Python. 前两天在VeryCD上看到这个剧的第二部出了, 于是兴冲冲地下到, 一直在公司看到晚上十二点才回家. 相对于国内将魔兽争霸作为黑客工具的电视剧, 这个剧可是专业太多了. 里面的所有入侵据我的知识, 在理论上都是可能的. 而且很多小细节都很靠谱, 例如剧情说要入侵KGB, 那么屏幕上闪过的域名肯定有KBG相关的信息, 下面这个截屏比较能说明这部剧在专业细节上没什么说不过去的东西.

<span class="readmore"><a href="http://blog.xiaket.org/2010/01/28/bloody-monday2-ep01/" title="Bloody Monday2第一集观后">阅读全文——共586字</a></span>]]></description>
			<content:encoded><![CDATA[<p>Bloody Monday是<a href="http://bbs.nju.edu.cn/">南京大学小百合bbs</a>上的<a href="http://bbs.nju.edu.cn/blogcon?userid=VarVar&#038;file=1225095249">VarVar童鞋推荐</a>我看的一部日剧. 看这个倒也是因为这个日剧里面有谈黑客和入侵相关的内容. 而且让我这个Python粉丝很高兴的是里面的超级黑客猎鹰所使用的脚本语言有Python. 前两天在VeryCD上看到这个剧的第二部出了, 于是兴冲冲地下到, 一直在公司看到晚上十二点才回家. 相对于国内将魔兽争霸作为黑客工具的电视剧, 这个剧可是专业太多了. 里面的所有入侵据我的知识, 在理论上都是可能的. 而且很多小细节都很靠谱, 例如剧情说要入侵KGB, 那么屏幕上闪过的域名肯定有KBG相关的信息, 下面这个截屏比较能说明这部剧在专业细节上没什么说不过去的东西.</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/01/Bloody-Monday.png" width="600px" alt="Bloody Monday截屏" /></p>
<p>也许由于第一季我是一气看完的, 前两天看完手头的S2E01完全没有当时看的感觉. 而且这个片子里面的很多剧情上让我很不爽, 例如很生硬地让第一部里面表演很好的两个pp女演员挂掉, 又如为了衬托男主角猎鹰的黑客能力而故意引出来的大黄蜂也出现得有点莫名. 另外, 这一集里面有些小细节也开始不靠谱了, 例如弄一个nslookup就能入侵一个超市的安保系统. 不过就我刚看完的第一集来说, 剧情还算过得去. 男主角在这部剧里面也依旧有上佳表演. 这一集最后的那个hack虽然不算太难猜, 不过也还是够强大~</p>
<p>好吧, 也没什么其他的感想了, 贴几张图占空间吧~</p>
<p>这张是太明显的吹捧, 不喜欢&#8230;</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/01/bm1.png" width="600px" alt="Bloody Monday 1" /></p>
<p>口水下这张里面的苹果键盘&#8230;</p>
<p><img src="http://blog.xiaket.org/wp-content/uploads/2010/01/bm2.png" width="600px" alt="Bloody Monday 2" /></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/01/28/bloody-monday2-ep01/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Django中的两个命令行输出添加ASCII颜色的函数</title>
		<link>http://blog.xiaket.org/2010/01/03/python-ascii-color-functions/</link>
		<comments>http://blog.xiaket.org/2010/01/03/python-ascii-color-functions/#comments</comments>
		<pubDate>Sat, 02 Jan 2010 16:09:50 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Python开发]]></category>
		<category><![CDATA[Django开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=234</guid>
		<description><![CDATA[代码在django/utils/termcolors.py中, 相关应用的例子在下面:



        colorize('hello', fg='red', bg='blue', opts=('blink',))

<span class="readmore"><a href="http://blog.xiaket.org/2010/01/03/python-ascii-color-functions/" title="Django中的两个命令行输出添加ASCII颜色的函数">阅读全文——共712字</a></span>]]></description>
			<content:encoded><![CDATA[<p>代码在django/utils/termcolors.py中, 相关应用的例子在下面:</p>
<pre class="brush: python;">
        colorize('hello', fg='red', bg='blue', opts=('blink',))
        print colorize('first line', fg='red', opts=('noreset',))
        print 'this should be red too'
        print colorize('and so should this')
        print 'this should not be red'
</pre>
<p>支持常见的ASCII效果:</p>
<pre>
        'bold'
        'underscore'
        'blink'
        'reverse'
        'conceal'
        'noreset'
</pre>
<p>这个文件里面的另一个make_style函数更赞~</p>
<pre class="brush: python;">
def make_style(opts=(), **kwargs):
    &quot;&quot;&quot;
    Returns a function with default parameters for colorize()

    Example:
        bold_red = make_style(opts=('bold',), fg='red')
        print bold_red('hello')
        KEYWORD = make_style(fg='yellow')
        COMMENT = make_style(fg='blue', opts=('bold',))
    &quot;&quot;&quot;
    return lambda text: colorize(text, opts, **kwargs)
</pre>
<p>这两个东东原理都很简单, 代码也是很易懂, 不再多解释了~ 以后项目中有需要直接输出彩色文字的需求直接调用这两个函数就行了~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/01/03/python-ascii-color-functions/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>一天idea散记</title>
		<link>http://blog.xiaket.org/2009/12/31/todays-ideas/</link>
		<comments>http://blog.xiaket.org/2009/12/31/todays-ideas/#comments</comments>
		<pubDate>Wed, 30 Dec 2009 17:04:56 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[其它]]></category>
		<category><![CDATA[django-admin]]></category>
		<category><![CDATA[django-orm]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=226</guid>
		<description><![CDATA[Django相关

问: 一个models.py文件里面定义了一群模型(不知道多少个), 我想要得到一个字典, 键为模型名, 值为模型的类.

答: 这是个比较丑的办法, 暂时想不到更漂亮的了:

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/31/todays-ideas/" title="一天idea散记">阅读全文——共1549字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>Django相关</h3>
<p>问: 一个models.py文件里面定义了一群模型(不知道多少个), 我想要得到一个字典, 键为模型名, 值为模型的类.</p>
<p>答: 这是个比较丑的办法, 暂时想不到更漂亮的了:</p>
<pre class="brush: python;">
from django.db import models
import msgcenter.models
class_dict = {}
for name in dir(msgcenter.models):
    try:
        status = eval(&quot;issubclass(msgcenter.models.%s, models.Model)&quot; % name)
        class_dict[name] = eval(&quot;msgcenter.models.%s&quot; % name)
    except:
        pass
</pre>
<p>问: 一个模型里面有很多的field, 怎么拿到这个field的列表?</p>
<p>答: 这个是一个内置的属性:</p>
<pre class="brush: python;">
&gt;&gt;&gt; from msgcenter.models import Project
&gt;&gt;&gt; Project._meta.object_name
'Project'
&gt;&gt;&gt; Project._meta.get_all_field_names()
['admins',
 'fullname',
 'id',
 'popo_acount',
 'shortname']
</pre>
<p>不过这个似乎还包含了一些外键之类的东西, 有时间再细细研究下.</p>
<p>问: 我定义了一个模型, 里面有个field用来记录作者的ip, 普通视图函数还好说, 但是怎么样能够在admin界面下也能正确保存这个ip信息呢?</p>
<p>答: 几种办法. 最丑的: 改urls.py, 让我们匹配的规则在admin的规则的前面, 然后自己写视图函数, 外观做得很像很像admin界面&#8230;. 最剑走偏锋的: 用中间件, 对于到某个特殊的url的POST, 将POST的内容改掉, 添加用户的ip. 最正统的: 改admin.py里面的save_model方法&#8230;</p>
<p>第二种方法是今天和LD讨论出来的, 很有意思, 中间件这样用虽然很非主流, 但也不失为一个极端情况下的投机取巧之道. 用中间件你可以任意地修改request/response对象, 一切尽在掌握~</p>
<h3>工作相关</h3>
<p>遇到一个诡异的问题, 某个服务用户的crontab里运行的脚本ls输出和通过sudo来以这个服务用户的身份运行ls的输出结果不一样, 时间格式不同, 后来强制指定&#8211;time-style搞定&#8230;</p>
<p>今天开始写脚本处理<a href="http://mirrors.163.com/">开源镜像服务器</a>上Web服务器的日志. 今天主要解决的问题是一个很大的日志文件如何得到每个小时的访问次数. grep -c肯定是可以的, 但是效率很差. 目前想到的方法是利用日志里面的时间戳, 在python里用file对象的seek方法来处理, 而不是逐行处理, 不过这样效率到底能提高多少, 目前脚本没写出来我还没底, 目标是一个2G的文件一秒能搞定, 这样比逐行处理效率要高不少了. 直接将文件cat到/dev/null都会要几十秒~</p>
<h3>其他</h3>
<p>晚上在Windows平台下一个Debian虚拟机里面配mysqld, 怎么都起不来, 报错是无法绑定端口. 但是显然即使我起了两个mysqld也不会这么费事地排错&#8230;  后来改掉my.cnf里面的bind-address, 将127.0.0.1改为0.0.0.0就正常了&#8230;  都是VM惹的祸&#8230;   </p>
<p>新闻里广汉高铁上有人吸烟导致列车停运. 论坛上一群人骂铁道部胡乱找借口掩盖问题, 另有一群人辩护, 说, 有烟的话列车本来就应该停下来处理, 否则万一火灾就挂了. 这件事我倒有另一个看法, 目前广汉高铁的安检要求太低. 打火机是不能带上飞机的. 既然高铁的速度和飞机也差不了太多, 那么高铁上用打火机我想也应该禁止. 每秒一百米的速度, 如果着火, 绝对响起了通往天国的倒计时了&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/31/todays-ideas/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django中的中间件</title>
		<link>http://blog.xiaket.org/2009/12/27/django-middleware/</link>
		<comments>http://blog.xiaket.org/2009/12/27/django-middleware/#comments</comments>
		<pubDate>Sun, 27 Dec 2009 09:00:31 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[WSGI]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=214</guid>
		<description><![CDATA[中间件的调用

如果你看了我写的前一篇讨论Django处理HTTP请求的文章, 我在文中基本没提到中间件. 好吧, 我故意遗漏了这些, 因为中间件相对比较独立. 而且每个中间件之间也相对比较独立. 顺口说句, 松耦合就这个好处&#8230;

言归正传, 中间件的调用是在WSGIHandler被调用的开头进行的:

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/27/django-middleware/" title="Django中的中间件">阅读全文——共4376字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>中间件的调用</h3>
<p>如果你看了我写的前一篇<a href="/2009/12/25/django-httpquest-procession/">讨论Django处理HTTP请求的文章</a>, 我在文中基本没提到中间件. 好吧, 我故意遗漏了这些, 因为中间件相对比较独立. 而且每个中间件之间也相对比较独立. 顺口说句, 松耦合就这个好处&#8230;</p>
<p>言归正传, 中间件的调用是在WSGIHandler被调用的开头进行的:</p>
<pre class="brush: python;">
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        from django.conf import settings

        # Set up middleware if needed. We couldn't do this earlier, because
        # settings weren't available.
        if self._request_middleware is None:
            self.initLock.acquire()
            # Check that middleware is still uninitialised.
            if self._request_middleware is None:
                self.load_middleware()
            self.initLock.release()
</pre>
</p>
<p>显然, 关键的语句是倒数第二行里面的load_middleware方法, 我们继续跟进去看:</p>
<pre class="brush: python;">
# django/core/handlers/base.py
class BaseHandler(object):
    def load_middleware(self):
        &quot;&quot;&quot; some code omitted. &quot;&quot;&quot;
        self._view_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        request_middleware = []
        for middleware_path in settings.MIDDLEWARE_CLASSES:
            &quot;&quot;&quot; Exception handling is removed from these code. &quot;&quot;&quot;
            dot = middleware_path.rindex('.')
            mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
            mod = import_module(mw_module)
            mw_class = getattr(mod, mw_classname)
            mw_instance = mw_class()

            if hasattr(mw_instance, 'process_request'):
                request_middleware.append(mw_instance.process_request)
            if hasattr(mw_instance, 'process_view'):
                self._view_middleware.append(mw_instance.process_view)
            if hasattr(mw_instance, 'process_response'):
                self._response_middleware.insert(0, mw_instance.process_response)
            if hasattr(mw_instance, 'process_exception'):
                self._exception_middleware.insert(0, mw_instance.process_exception)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._request_middleware = request_middleware
</pre>
<p>整个这段代码的核心意思是对于每个settings.MIDDLEWARE_CLASSES里指定的中间件, 我们探测里面有什么方法. 如果有process_request, process_view, process_response, process_exception这四个方法, 我们将其添加到BaseHandler的四个列表里去. 按照Django处理HTTP请求的顺序, 前两个列表都是在django.core.handlers.base.BaseHandler模块的get_response方法中应用的, 第三个process_response方法是在WSGIHandler的结尾处应用的, 最后一个方法是在视图函数处理HTTP请求出现异常时才被应用的.</p>
<p>用django-admin.py startproject创建的新项目里, 默认添加了下面这些中间件:</p>
<pre class="brush: python;">
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)
</pre>
<p>csrf这篇文章就不管了, 我们接下来分析剩余三个中间件到底执行了哪些操作. 我们主要分析process_request和process_response这两个字典.</p>
<h3>process_request</h3>
<p>CommonMiddleware里面没啥新鲜的东西, 主要做了三件事情:</p>
<ul>
<li>禁止某些User-Agent的访问(我瞬间很邪恶地想起可以禁止IE的访问&#8230;).</li>
<li>URL重写, 在URL后面添加/和在前面添加www, 这个是可以配置的.</li>
<li>ETag处理.</li>
</ul>
<p>这个不细分析了, 有兴趣的童鞋可以自行围观代码(django/middleware/common.py), 我们接下来看SessionMiddleware. 这个中间件的process_request方法里面只有几行代码:</p>
<pre class="brush: python;">
class SessionMiddleware(object):
    def process_request(self, request):
        engine = import_module(settings.SESSION_ENGINE)
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
        request.session = engine.SessionStore(session_key)
</pre>
<p>这一段详细的理解可以参考<a href="http://docs.djangoproject.com/en/dev/topics/http/sessions/#topics-http-sessions">Django文档中有关会话的部分</a>. 这部分在我手头的项目中应用不多, 于是很不厚道地也不准备细看了.</p>
<p>AuthenticationMiddleware中的代码依然简洁:</p>
<pre class="brush: python;">
# django/contrib.auth.middleware.py
class AuthenticationMiddleware(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), &quot;The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'.&quot;
        request.__class__.user = LazyUser()
        return None
</pre>
<p>除掉判断会话中间件是否已被载入的那段, 关键的语句是第5行, 这句设置了request这个WSGIRequest实例的类属性user, 将其设置为LazyUser, 而这个东西的定义为:</p>
<pre class="brush: python;">
class LazyUser(object):
    def __get__(self, request, obj_type=None):
        if not hasattr(request, '_cached_user'):
            from django.contrib.auth import get_user
            request._cached_user = get_user(request)
        return request._cached_user
</pre>
<p>这段代码使用了描述器(descriptor), 和上面那段代码的作用和起来所实现的功能是, 如果用request.user来请求user这个对象, 实际上请求的是_cached_user这个对象. 如果这个对象为空, 那么用get_user方法来获得当前请求的user:</p>
<pre class="brush: python;">
# django/contrib/auth/__init__.py
def get_user(request):
    from django.contrib.auth.models import AnonymousUser
    try:
        user_id = request.session[SESSION_KEY]
        backend_path = request.session[BACKEND_SESSION_KEY]
        backend = load_backend(backend_path)
        user = backend.get_user(user_id) or AnonymousUser()
    except KeyError:
        user = AnonymousUser()
    return user
</pre>
<p>这个函数的逻辑是, 从会话信息里面查找到当前用户信息, 查不到的话就将当前用户设置为匿名用户(未注册用户). 至此, 这个纠结的AuthenticationMiddleware处理request的部分就告一段落了.</p>
<h3>process_response</h3>
<p>CommonMiddleware中的这个方法是在处理ETag; SessionMiddleware的这个方法只是用来处理会话信息的保存, 谢天谢地. 而AuthenticationMiddleware更是很厚道地没有定义这个方法. 好吧, 那就这样结束吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/27/django-middleware/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django处理HTTP请求的过程</title>
		<link>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/</link>
		<comments>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/#comments</comments>
		<pubDate>Fri, 25 Dec 2009 08:07:28 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[REQUEST]]></category>
		<category><![CDATA[WSGI]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=204</guid>
		<description><![CDATA[WSGIHandler

HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:



<span class="readmore"><a href="http://blog.xiaket.org/2009/12/25/django-httpquest-procession/" title="Django处理HTTP请求的过程">阅读全文——共4456字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>WSGIHandler</h3>
<p>HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:</p>
<pre class="brush: python;">
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
</pre>
<p>这个句柄将处理HTTP请求, 我们跟进去看这个WSGI句柄的初始化. WSGIHandler这个类没有__init__方法, 因此使用的是base.BaseHandler的初始化方法:</p>
<pre class="brush: python;">
# django/core/handlers/base.py
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    response_fixes = [
        http.fix_location_header,
        http.conditional_content_removal,
        http.fix_IE_for_attach,
        http.fix_IE_for_vary,
    ]

    def __init__(self):
        self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
</pre>
<p>好吧, 这个里面貌似也没干啥&#8230; 我们继续看WSGIHandler的__call__方法. 这个方法会被调用上面的wsgi脚本中的语句调用.</p>
<p>根据WSGI的文档:</p>
<pre>
The Web framework/app is represented to the server as a Python callable. It can be
a class, an object, or a function. The arguments to __init__, __call__, or the function
must be as above: an environ object and a start_response callable.
</pre>
<p>参考看下WSGIHandler的__call__方法, 很符合这个要求, 也是两个外部参数.</p>
<pre class="brush: python;">
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        &quot;&quot;&quot; some code omitted here. &quot;&quot;&quot;
        signals.request_started.send(sender=self.__class__)
        try:
            try:
                request = self.request_class(environ)
            except UnicodeDecodeError:
                response = http.HttpResponseBadRequest()
            else:
                response = self.get_response(request)

            &quot;&quot;&quot; some more code omitted. &quot;&quot;&quot;
        finally:
            signals.request_finished.send(sender=self.__class__)

        try:
            status_text = STATUS_CODE_TEXT[response.status_code]
        except KeyError:
            status_text = 'UNKNOWN STATUS CODE'
        status = '%s %s' % (response.status_code, status_text)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', str(c.output(header=''))))
        start_response(status, response_headers)
        return response
</pre>
<p>这个方法一开始还做了些和中间件相关的事情, 这儿就省略了. 我们从信号这一段开始, 在处理完中间件相关的逻辑后, 此处放出一个request_started的信号, 我们可以利用这个信号来处理一些对每个HTTP请求都要处理的逻辑. 接下来我们开始处理请求. 这儿用了一个try-except-else语句块. 首先我们尝试将environ作为参数来生成一个WSGIRequest类, 如果这一步出了问题, 我们返回400, 否则我们用get_response(request)来获得一个HttpRequest对象. 接下来Django放出了一个request_finished信号, 然后返回这个HTTP请求. 我们继续看处理请求的这部分.</p>
<h3>WSGIRequest</h3>
<p>前面的代码中, 我们已经在WSGIHandler中将其request_class属性设为一个WSGIRequest的实例, 并用一个装有环境变量的字典environ初始化了一个WSGIRequest. 我们接下来看Django是怎么处理这个WSGIRequest的.</p>
<p>首先看这个类的初始化. 相关代码如下:</p>
<pre class="brush: python;">
class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = force_unicode(environ.get('PATH_INFO', u'/'))
        if not path_info or path_info == script_name:
            &quot;&quot;&quot; some comments omitted here. &quot;&quot;&quot;
            path_info = u'/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s%s' % (script_name, path_info)
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        self._post_parse_error = False
</pre>
<p>这段初始化基本上也是比较平淡的, 主要做了两件事, 一个是设置了request.META这个字典, 另外设置了request.method. 如果这一段成功了, 那么我们将用get_response来处理这个WSGIRequest.</p>
<h3>get_response</h3>
<p>这个函数的定义在django/core/handlers/base.py中, 是BaseHandler的一个方法, 代码如下:</p>
<pre class="brush: python;">
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    def get_response(self, request):
        &quot;Returns an HttpResponse object for the given HttpRequest&quot;
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
        # Get urlconf from request object, if available.  Otherwise use default.
        urlconf = getattr(request, &quot;urlconf&quot;, settings.ROOT_URLCONF)
        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
        try:
            callback, callback_args, callback_kwargs = resolver.resolve(
                    request.path_info)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            # Actually, this line was in a try-except thing...   by xiaket.
            response = callback(request, *callback_args, **callback_kwargs)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            return response
        except http.Http404, e:
            if settings.DEBUG:
                from django.views import debug
                return debug.technical_404_response(request, e)
            else:
                try:
                    callback, param_dict = resolver.resolve404()
                    return callback(request, **param_dict)
                except:
                    try:
                        return self.handle_uncaught_exception(request, resolver, sys.exc_info())
                    finally:
                        receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
</pre>
<p>原来的代码很长, 仔细分析, 除掉了一些我不太关心的异常处理后就变成了现在这个样子. 这个函数接受一个HttpRequest实例, 返回一个HttpRequest实例. 函数一开始就来匹配URL. 第7行这句很强大, 因为它提供了很多可能性. 例如, 你可以通过中间件来对于request中的IP做判断, 然后通过设置urlconf来决定使用什么样的URL匹配方式. 当然, 这个时候已经经过了中间件, request.user已经设置好了, 你也可以根据用户的不同来提供不同的urlconf. 从settings.ROOT_URLCONF中拿到了URL配置后, 我们尝试匹配URL, 然后在第11行拿第10行匹配得到的适当的view函数来处理这个URL. 到视图函数以后就没有太多好说了. 中间那层我们略过, 返回了response之后也没啥了, 到这儿来就交给WSGI了. Django处理HTTP请求的逻辑也就结束了.</p>
<p>挖个坑, 实际上Django有很多HTTP请求的细节是在中间件里面完成的, 这篇文章完全没有提及这一点, 我有时间再仔细分析下中间件的作用, 为强大的中间件们正名~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
