Aug 5

这里有人给出了自己的解法,但是我不喜,所以有了本文。注意,本文中的 shell 代码均为 zsh。如果你在用 Windows,那建议还是不要玩了,那个对付这种事情太难用了。

可惜知道这个题晚了两天,没能进前X名 :-(

0. Fuck your brain

机器上没有 brainfuck 编译器,于是 Google「brainfuck online」,得到这个。贴进去运行即可。

1. Multiply

一个值是 42。另一个要观察数列。直接把数列贴到数列百科全书即可。然后乘起来。

2. Keyboard

这个也很容易,不是把 Dvorak 键盘当成 Qwerty,那就是把 Qwerty 当成 Dvorak 了。对照着 Dvorak 的键位输入下边那串字符串,得到一 C 源码。编译、运行之即可。

3. QR Code

扫码,得到:

[abcdefghijklmnopqrstuvwxyz] <=> [pvwdgazxubqfsnrhocitlkeymj]

是个字符映射关系。Python 有现成的函数来处理这个。也是有正反两种可能,都试试就可以了。

>>> T = str.maketrans('pvwdgazxubqfsnrhocitlkeymj', 'abcdefghijklmnopqrstuvwxyz')
>>> s = 'Wxgcg txgcg ui p ixgff, txgcg ui p epm. I gyhgwt mrl lig txg ixgff wrsspnd tr irfkg txui hcrvfgs, nre, hfgpig tcm liunz txg crt13 ra "ixgff" t
r gntgc ngyt fgkgf.'
>>> s.translate(T)
'Where there is a shell, there is a way. I expect you use the shell command to solve this problem, now, please try using the rot13 of "shell" to enter next level.'

按照提示执行命令:

rot13 <<< shell

Arch 上,rot13 命令位于 bsd-games 包。或者在 Vim 里把光标移动到「shell」单词上按g?aw也能得到结果。

题目开始有趣起来了~

4. cat

这个题目更有趣了。源码里一堆乱七八糟的数据。先把它们弄到一个单独的 Vim 缓冲区,然后找到所有的五字符回文字符串:

%!grep -oP '(.)(.)(.)\2\1'

不是所有回文都被接受。仔细观察示例可以发现,中间一定是个小写字母,左边一定是一字母一数字。但是过滤后还是有太多结果。限制左边的字母为大写字母之后可以得结果。删掉不符合条件的,然后把中间的字符连起来即可。

v/\v^([A-Z][0-9]|[0-9][A-Z])\l/d

5. variables

初看,提示莫名其妙。后来注意到图片链接到了有意思的地方。访问得到另一个数字「32722」。显然是要用这个数字放在 URL 上继续访问了。直接拿 shell 访问:

$ n=1024
$ while true; do n=$(curl -sS http://fun.coolshell.cn/n/$n); echo $n; done

访问上百次之后出现一句话,给出了下一关的地址。

6. tree

这关要求从一棵二叉树的中序和后序遍历中还原其最深的路径。不知道怎么做,直接 Google「reconstruct a binary tree from in-order and post-order」,看来有不少人都在做类似的东西啊。我看的是 LeetCode 上的这篇文章。有代码,但我懒得写程序把树画出来或者是找最深的了。反正这树也不大,懂得了方法,直接在 dia 里手工构建出来了。当然,我只构建了最深的那部分。SVG 导出图片

然后就是拿密码解那个字符串了。要注意的是,不要自己去解 base64,不然 openssl 报错的……

7. N Queens

八皇后问题的变种。我直接使用了 Rosetta Code 上的代码。当然要小改一下,直接输出结果而不是打印出图案:

main = mapM_ print $ queens 9

然后找到符合那个 SHA1 值的解就可以了:

$ ./queens | tr -d ',[]' | while read code; do [[ $(sha1sum <<<zWp8LGn01wxJ7$code | awk '{print $1}') == e48d316ed573d3273931e19f9ac9f9e6039a4242 ]] && echo $code; done

8. Excel Column

26 进制转十进制:

>>> def debase26(x):
...   return sum(26 ** i * (ord(d) - ord('A') + 1) for i, d in enumerate(x[::-1]))
...
>>> debase26('COOLSHELL') // base26('SHELL')
85165

结果得到的页面说要转回 26 进制。好吧:(可惜没能在一行内搞定)

>>> def base64(x):
...   L = []
...   while True:
...     x, d = divmod(x, 26)
...     if d == 0: break
...     L.append(d)
...   return ''.join(chr(x + ord('A') - 1) for x in L[::-1])
...
>>> base64(85165)
'DUYO'

9. Fraternal Organisation

这个我没能解出来 QAQ 这两个图片看起来有些莫名其妙。我没注意到图片的名字和鼠标放上去的小提示。最后是看前边那个链接里的答案才知道原来还有个「猪圈密码」-_-|||

PS: 最近博客访问和评论速度都挺慢的,请见谅。

Jul 18

缘起

在一群文件里搜索特定的文本,第一个想到的工具是经典的 grep。自从知道 ag——The silver searcher 之后,我就只用 grep 来过滤管道啦。

ag 的优势:

  • 命令短
  • 和 ack 以及 git grep 一样,默认会忽略掉你通常不想看的文本(二进制文件、被版本控制系统忽略掉的文件)
  • C 编写的,比 ack 更快!

作者在学在乎程序的执行效率。这也很重要,因为文件多啊,几十上百兆的源码找起来可费时了。

不过在我这里,ag 取代掉的既不是 grep 也不是 ack。因为我之前用的是 cgvg,和 ack 一样也是 Perl 写的,但不一样的是,它包含两个命令:一个(cg)用来搜索,另一个(vg)用来在编辑器里打开!

不知道为什么其它工具的作者都没有想到这一点。匹配的地方找到了,大部分情况都需要用编辑器打开看看,也许再改改吧?cgvg 免去了复制路径到编辑器里并跳转到特定位置这一烦琐的步骤。

所以换用 ag 之后,我自然也希望能够很便利地使用编辑器查看匹配的地方了。为此,我做了三个工具。好吧,其实有一个是在 cgvg 时代就已经有了的。

搜索

实践一下。假设我要寻找 Vim 源码中涉及到p_enc变量的地方。使用 agg 命令来搜索并使用翻页器 less 显示结果(截图时没有显示出 less 的存在):

agg result

agg 脚本更改了 ag 的显示样式,每一项开头都有一个序号,就和 cg 命令一样。同样地,agg 也把这个结果保存在主目录下的一个文件,以供 agv 命令使用。

注意,agg 并不能用来按文件名搜索。这种情况还是用 ag -g pattern

使用编辑器打开

agv 命令不给定参数,会显示上一次搜索的结果。如果给出一个编号,就会将结果在AGV_EDITOR环境变量指定的编辑器里打开,并跳转到对应的地方。和 vg 不同的是,agg/agv 为每一个终端维护了一个结果列表,这样就可以在不同的终端里搜索不同的内容而不会相互干扰了。

比如我们要去第 14 号结果所在的地方,只要执行agv 14,然后就到了:

agv result

我这里是在已有的 gVim 里打开的哦。因为我设置的AGV_EDITOR的值是vv $file:$line:$col。这是一句包含占位符的 shell 命令。$file$line$col分别是结果所在的文件、行号、列号。而vv,则是我写的另一个工具,用来在已经打开的 gVim 里打开文件,并且跳转到特定的地方

vv 需要 Python easygui 库,以及 gVim(或者 Vim)的 +clientserver 支持。vv 不仅支持上述格式的参数,作为一名 Pythonista 所编写的工具,它也支持从 Python 报错时打印的 Traceback 的行中提取文件名和行号。比如:

vv 'File "/usr/lib/python3.4/sre_parse.py", line 358, in _parse_sub'

这里,不一定要复制一整行。包含必要的信息(文件名和行号)就可以了。

当然,手动复制粘贴比较累。所以配合我的 zsh 全局别名:

alias -g XS='"$(xsel)"'

我只需要选中那一行,然后执行

vv XS

就可以了。zsh 会帮我把XS展开成我刚刚选择的文本。

ag.vim

ag.vim 是一个 ack.vim 的修改版,用于在 Vim 中调用 ag,就像 ack.vim 在 Vim 中调用 ack、grep.vim 在 Vim 中调用 grep 一样。

因为经常搜索在当前光标下的内容,我写了这么一条自定义命令:

command Agg exe 'Ag -Q ' . expand('<cword>')

仓库地址

https://github.com/lilydjwg/search-and-view

Jul 15

准备

首先检查 CPU 支持。需要 CPU 支持虚拟化的。

grep -E "(vmx|svm|0xc0f)" --color=always /proc/cpuinfo

没输出就没戏了。现在的 CPU 一般都支持的。

然后是内核支持。

zgrep CONFIG_KVM /proc/config.gz
zgrep CONFIG_VIRTIO /proc/config.gz

官方内核是支持的。

最后是用户态软件。Arch Linux 一向不怎么分包,安装 qemu 这个包就可以了。

哦对了,要安装 Arch 的话,还要准备它的安装镜像。

开始啦

一切就绪。

先创建虚拟机所用的磁盘文件。

qemu-img create -f qcow2 ArchVM.img 15G

这样就创建了一个 15G 容量的 qcow2 格式虚拟磁盘文件。之所以选用 qcow2,是因为它支持「母镜像」功能,对应于 Virtual Box 的差分存储。

然后就可以启动系统了。为了避免老是输入一长串命令,遵循 Gentoo Wiki 的建议,我们创建一个脚本:

#!/bin/sh
exec qemu-system-x86_64 -enable-kvm \
       -cpu host \
       -drive file=$HOME/ArchVM.img,if=virtio \
       -netdev user,id=vmnic,hostname=archvm,hostfwd=tcp:127.0.0.1:2222-:22 \
       -device virtio-net,netdev=vmnic \
       -m 256M \
       -curses \
       -name "Arch VM" \
       "$@"

注意到这里我已经加上了hostfwd参数,将虚拟机的 22 端口映射到 host 的 2222 端口上,方便以后通过 ssh 连接。

我这里指定了-curses参数,它将虚拟机的显示器直接使用 curses 库显示在当前终端上。当然能显示的只有显示器处于文本模式的时候,图形模式就只能知晓当前分辨率了。因为我是在服务器上使用,所以加上这个参数。当然你也可以使用 VNC 去连。

然后执行命令:

./startvm -boot once=d -cdrom path_to_file.iso

首先从光驱启动一次(once=d),重启之后恢复到默认的从硬盘启动。

系统启动啦~然后就会发现引导器 isolinux 把显示器切换到图形模式了……

终端无法显示图形模式的内容

不过还好。Arch 的引导界面我们知道。按Tab,然后输入<Space>nomodeset并回车。不过待会进系统里,KMS 之后一直是图形模式就什么也看不到了。

然后进入系统安装啦。注意硬盘设备是/dev/vda。当然也要注意安装并让 sshd 在开机时启动,虽然说有 curses 模式的「显示器」也可以用。

装好之后、重启之前还要注意一点,把/boot/grub/grub.cfg包含gfxload_video之类的地方都去掉,不然会进图形模式的。

装好后就 reboot 吧。如果一切顺利的话就能看到已经安装好的 Arch 登录提示符了。

好不容易装好了系统,当然要把它作为母镜像,所有后续的修改放子镜像上啦:

qemu-img create -f qcow2 -b ArchVM.img ArchTest.img

然后修改一下启动脚本。以后就可以用./startvm脚本启动这个虚拟机啦。

参考文章

Jul 3

muttils 是一系列用于 mutt 等终端邮件客户端的小工具,其中 viewhtmlmsg 脚本用于在浏览器中阅读 HTML 邮件:

macro pager \eh "<pipe-entry>viewhtmlmsg<enter>" 在网页浏览器中查看

但一直以来有个问题:需要等好几秒脚本才会返回,因为它要给浏览器足够的时间来读取 HTML 等文件,然后在退出前清理掉它们。如果是 Linux 系统并且安装了 inotifyx 包会好一些,它检测到有程序读取了 HTML 文件之后就立即退出了。

不过我希望更快一些。到后台去处理,到后台去等待。我要继续阅读下一封邮件。

所以就有了这么个小脚本:读取标准输入传过来的数据之后立即 fork,父进程退出,子进程去把数据交给 viewhtmlmsg 来处理:

#!/usr/bin/env python3

import sys
import os
import subprocess

def main():
  content = sys.stdin.buffer.read()
  if os.fork() > 0:
    return
  p = subprocess.Popen(
    'viewhtmlmsg',
    stdin = subprocess.PIPE,
  )
  p.communicate(content)
  p.wait()

if __name__ == '__main__':
  main()
Jun 17

需要 Python 3.4+,一个参数用来选择测试搜索服务还是 GAE 服务。测试 GAE 服务的话需要先修改开头的两个变量。从标准输入读取 IP 地址或者 IP 段(形如 192.168.0.0/16)列表,每行一个。可用 IP 输出到标准输出。实时测试结果输出到标准错误。50 线程并发。

#!/usr/bin/env python3

import sys
from ipaddress import IPv4Network
import http.client as client
from concurrent.futures import ThreadPoolExecutor
import argparse

# 先按自己的情况修改以下两行
APP_ID = 'your_id_here'
APP_PATH = '/fetch.py'

def check_ip_p(ip, func):
  if func(ip):
    print(ip, flush=True)

def check_for_gae(ip):
  return _check(APP_ID + '.appspot.com', APP_PATH, ip)

def check_for_search(ip):
  return _check('www.google.com', '/', ip)

def _check(host, path, ip):
  try:
    conn = client.HTTPSConnection(ip, timeout=2)
    conn.request('GET', path, headers = {
      'Host': host,
    })
    response = conn.getresponse()
    if response.status < 400:
      print('GOOD:', ip, file=sys.stderr)
    else:
      raise Exception('HTTP Error %s %s' % (
        response.status, response.reason))
    return True
  except KeyboardInterrupt:
    raise
  except Exception as e:
    print('BAD :', ip, e, file=sys.stderr)
    return False

def main():
  parser = argparse.ArgumentParser(description='Check Google IPs')
  parser.add_argument('service', choices=['search', 'gae'],
                      help='service to check')
  args = parser.parse_args()
  func = globals()['check_for_' + args.service]

  with ThreadPoolExecutor(max_workers=50) as executor:
    for l in sys.stdin:
      l = l.strip()
      if '/' in l:
        for ip in IPv4Network(l).hosts():
          executor.submit(check_ip_p, str(ip), func)
      else:
        executor.submit(check_ip_p, l, func)

if __name__ == '__main__':
  main()

脚本下载地址

Jun 3

这是个虽然简单但是很有意思的问题,以前我竟然未曾想过。后来看到「宇宙的心弦」上对这个问题的回答写得太模糊(什么叫「镜子里头脚的位置没变」?「位置没变」的定义是什么?),所以这里写一个尽可能精确描述的回答。首先,我们讨论最容易引起问题的那种情景,即人站立时正面照镜子。

首先定义几个概念。

左右。以你为原点,你的左手方向为左,右手方向为右。(你知道哪只手是左手吧?)

上下。站在地球表面,在空中静止释放一物体,由于重力,它会运动起来。其运动方向为下,反之为上。

前后。这个有点奇特。你面前站了一个人,背心对着你的时候,这是那人的面。面对着你的时候,你看到那人的面。我们的问题隐含了作为观察者的你,去看外界的像,而不是考察你自己,对吧?

让我们再定义一下坐标系。

右为 x 轴正方向,上为 y 轴正方向,由你(观察者)的后背指向你的胸前为 z 轴正方向。

作为观察者,这里有一个很明白的变换:你所认为「正」的像,其坐标要绕 y 轴旋转 180°,才能与你观察时使用的坐标系一致。让我解释得更清楚一些——

拿鼠标指针选中你,按一下Ctrl-D(如果你不是 Inkscape 用户,那就按一下Ctrl-C再按一下Ctrl-V)。现在有了你的一个像。但是你看不到它,因为它和你重合了。让我们把这个像向 z 轴正方向平衡一段距离,比如 2m,你再看看?哟,它怎么背对着你呀?不行,再原地转身 180°,这样才能看到正面不是?

忽略掉平移,让我们把这个变换记作 \(T_1\),有

$$ T_1 = \begin{bmatrix} \cos{\pi} & 0 & \sin{\pi} \\ 0 & 1 & 0 \\ -\sin{\pi} & 0 & \cos{\pi} \end{bmatrix} = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix} $$

我们再来考察一下镜子做了什么。

想像镜子前有一个坐标系的三根轴,就是我们刚刚定义的那个。其中 z 轴正方向指向镜面。于是乎,镜子里的 x 轴与外边的 x 轴是平行且方向一致的。y 轴也是这样。但是 z 轴的位置没有改变,方向却反了过来,箭头对箭头了。所以,这种放置法,使得像与物体的 z 轴反了,\(z\) 变成了 \(-z\)。还是忽略掉平移,让我们把这个变换记作 \(T_2\)

$$ T_2 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} $$

所以,最终作为观察者的你,看到的镜中的自己经历的变换是:

$$ T_1 T_2 = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} = \begin{bmatrix} -1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix} $$

即最终的像的坐标中的 \(x\) 变成了 \(-x\),即左右颠倒。

让我们再考虑另一种情况试试。如果把镜子放在头顶上,看过去会是什么感觉呢?

这时候,y 轴一头扎进了镜子,于是,我们的第三个变换 \(T_3\) 为:

$$ T_3 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix} $$

乘一下,结果是:

$$ T_1 T_3 = \begin{bmatrix} -1 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} $$

咦?这不是上下、左右、前后都颠倒了吗?找面位于天花板的镜子试试看。上下的确颠倒了不是么?再伸出左手试试,左右也和在面前的镜子里一样,也是颠倒的。可,前后感觉并没有颠倒啊?这是因为观察者和被观察对象位于同一垂直线上,感觉不一样了。仔细想想,天花板镜子里的像的前后确实与面前的镜子晨那个的前后是对着的,不是么?而我们定义后者没有颠倒,那么前者当然相对于后者是颠倒过了嘛。

个人拙见 =w=

Jun 1

听说 Ruby 支持分数字面量呢——

irb(main):001:0> 1/3r
=> (1/3)
irb(main):002:0> 1/3r+1/2r
=> (5/6)

可是在 Python 里只能这样子:

>>> from fractions import Fraction as R
>>> R(1, 3)
Fraction(1, 3)
>>> R(1, 3) + R(1, 2)
Fraction(5, 6)

学习《用 `accio` 代替 `import`:深入理解自举和 Python 语法》改改 Python 解释器,让它也支持分数字面量,应该会很有趣呢 =w=

去翻了一下 Grammar/Grammar 文件,有些失望。那里只有语法定义,没有词法定义呢。以为词法定义在另一个地方,所以找了找,无果。只有 C 代码。想到复数的 j 字尾,直接拿 ag 搜带引号的 "j"'j',最终确认它在 Parser/tokenizer.c 里。也就是说,Python 的词法分析是自己实现的。

在那个四千多行的tok_get函数里,有一部分代码在尝试解析一个数,也就是语法里的 NUMBER。照着解析复数的办法,把 d 后缀和 r 后缀也加进去:

diff -r bf65e7db066d Parser/tokenizer.c
--- a/Parser/tokenizer.c    Mon Apr 14 22:27:27 2014 -0400
+++ b/Parser/tokenizer.c    Fri May 30 20:12:07 2014 +0800
@@ -1528,6 +1528,10 @@
                 goto fraction;
             if (c == 'j' || c == 'J')
                 goto imaginary;
+            if (c == 'd' || c == 'D')
+                goto decimal;
+            if (c == 'r' || c == 'R')
+                goto rational;
             if (c == 'x' || c == 'X') {

                 /* Hex */
@@ -1621,6 +1625,12 @@
                     /* Imaginary part */
         imaginary:
                     c = tok_nextc(tok);
+                else if (c == 'd' || c == 'D')
+        decimal:
+                    c = tok_nextc(tok);
+                else if (c == 'r' || c == 'R')
+        rational:
+                    c = tok_nextc(tok);
             }
         }
         tok_backup(tok, c);

d 后缀是我给十进制数——就是会计里会用到的精确的十进制小数——准备的。

然后可以编译出来试试。这个 configure 命令是从 Arch 官方编译脚本里找的。

./configure --enable-shared --with-threads --with-computed-gotos --enable-ipv6 --with-valgrind --with-system-expat --with-dbmliborder=gdbm:ndbm --with-system-ffi --with-system-libmpdec --without-ensurepip
make

因为我不执行安装步骤,而又用了共享库,所以要这样子执行:

LD_LIBRARY_PATH=. ./python

试试看:

>>> 4d
ValueError: could not convert string to float: 4d

有效果,不报语法错了呢。

现在报ValueError,因为我还没告诉 Python 如何解析我新加的字面量表示呢。解析代码位于Python/ast.cparsenumber函数。最终的补丁如下:

diff -r bf65e7db066d Python/ast.c
--- a/Python/ast.c  Mon Apr 14 22:27:27 2014 -0400
+++ b/Python/ast.c  Fri May 30 20:12:07 2014 +0800
@@ -3650,12 +3650,29 @@
     long x;
     double dx;
     Py_complex compl;
-    int imflag;
+    char typeflag;
+    PyObject *mod, *type, *ret;

     assert(s != NULL);
     errno = 0;
     end = s + strlen(s) - 1;
-    imflag = *end == 'j' || *end == 'J';
+    switch(*end){
+        case 'j':
+        case 'J':
+            typeflag = 'j';
+            break;
+        case 'd':
+        case 'D':
+            typeflag = 'd';
+            break;
+        case 'r':
+        case 'R':
+            typeflag = 'r';
+            break;
+        default:
+            typeflag = 'i';
+    }
+
     if (s[0] == '0') {
         x = (long) PyOS_strtoul(s, (char **)&end, 0);
         if (x < 0 && errno == 0) {
@@ -3670,13 +3687,43 @@
         return PyLong_FromLong(x);
     }
     /* XXX Huge floats may silently fail */
-    if (imflag) {
+    if (typeflag == 'j') {
         compl.real = 0.;
         compl.imag = PyOS_string_to_double(s, (char **)&end, NULL);
         if (compl.imag == -1.0 && PyErr_Occurred())
             return NULL;
         return PyComplex_FromCComplex(compl);
     }
+    else if (typeflag == 'd') {
+      mod = PyImport_ImportModule("decimal");
+      if (mod == NULL)
+          return NULL;
+
+      type = PyObject_GetAttrString(mod, "Decimal");
+      if (type == NULL) {
+          Py_DECREF(mod);
+          return NULL;
+      }
+      ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1);
+      Py_DECREF(type);
+      Py_DECREF(mod);
+      return ret;
+    }
+    else if (typeflag == 'r') {
+      mod = PyImport_ImportModule("fractions");
+      if (mod == NULL)
+          return NULL;
+
+      type = PyObject_GetAttrString(mod, "Fraction");
+      if (type == NULL) {
+          Py_DECREF(mod);
+          return NULL;
+      }
+      ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1);
+      Py_DECREF(type);
+      Py_DECREF(mod);
+      return ret;
+    }
     else
     {
         dx = PyOS_string_to_double(s, NULL, NULL);

因为只是玩玩,所以不太认真,没仔细做错误处理;因为decimalfractions模块是从外部文件导入的,所以可能被覆盖掉,从而导致报错,并且这错误是无法通过异常处理捕获的。

不出问题的话,再次make之后,就可以开始玩了。不过在此之前,再多做几个补丁,让 Python 把分数和十进制数显示得简洁好看一点:

diff -r bf65e7db066d Lib/decimal.py
--- a/Lib/decimal.py    Mon Apr 14 22:27:27 2014 -0400
+++ b/Lib/decimal.py    Fri May 30 20:12:07 2014 +0800
@@ -1015,7 +1015,7 @@
     def __repr__(self):
         """Represents the number as an instance of Decimal."""
         # Invariant:  eval(repr(d)) == d
-        return "Decimal('%s')" % str(self)
+        return str(self) + 'd'

     def __str__(self, eng=False, context=None):
         """Return string representation of the number in scientific notation.
diff -r bf65e7db066d Lib/fractions.py
--- a/Lib/fractions.py  Mon Apr 14 22:27:27 2014 -0400
+++ b/Lib/fractions.py  Fri May 30 20:12:07 2014 +0800
@@ -280,7 +280,7 @@

     def __repr__(self):
         """repr(self)"""
-        return ('Fraction(%s, %s)' % (self._numerator, self._denominator))
+        return str(self) + 'r'

     def __str__(self):
         """str(self)"""
diff -r bf65e7db066d Modules/_decimal/_decimal.c
--- a/Modules/_decimal/_decimal.c   Mon Apr 14 22:27:27 2014 -0400
+++ b/Modules/_decimal/_decimal.c   Fri May 30 20:12:07 2014 +0800
@@ -3092,18 +3092,10 @@
 static PyObject *
 dec_repr(PyObject *dec)
 {
-    PyObject *res, *context;
-    char *cp;
-
-    CURRENT_CONTEXT(context);
-    cp = mpd_to_sci(MPD(dec), CtxCaps(context));
-    if (cp == NULL) {
-        PyErr_NoMemory();
-        return NULL;
-    }
-
-    res = PyUnicode_FromFormat("Decimal('%s')", cp);
-    mpd_free(cp);
+    PyObject *res, *str;
+    str = dec_str(dec);
+    res = PyUnicode_FromFormat("%Ud", str);
+    Py_DECREF(str);
     return res;
 }

下面是最终成果啦:

>>> 0.1 + 0.2 == 0.3
False
>>> 0.1d + 0.2d == 0.3d
True
>>> 1/3r + 1/2r
5/6r
>>> 0.4/1.2r
0.33333333333333337
>>> 0.4r/1.2r
1/3r

可以看到,与复数类似,分数字面量其实包含了一次除法。所以如果分子写浮点数的话,最终结果是会被转成浮点数的呢。这个和 Ruby 的行为是一样的 =w=

May 27

这东西挺好用的,可惜我只寻到一多年以前的 Python 2 版本的,作者是 AutumnCat,不认识。但注释里提到的修改者 bones7456 是鼎鼎大名的骨头兄,现其博客已经长草……

一直以来,我都是通过子进程调用来使用的,因为我写的代码是 Python 3 版,比如这个寻找文本里的 IP 地址并标记的 ipmarkup 脚本。配合 Python 3.2 加入的 functools.lru_cache,效率还不错的样子。但近期有大量 IP 需要查询,才感到每个 IP 都开个子进程的方式实在太慢。遂将其修改为 Python 3 版,并加入了些 Python 后来才流行的 idiom。

脚本还是扔到 winterpy 仓库里了。GPLv2 授权的。

2014年8月2日更新:增加了在线更新的功能,从此不需要 Wine 就能更新数据库啦 :-) 更新方法来自微菜。更新命令如下:

python3 -m QQWry update
May 15

udev 规则其实挺简单的,但第一次配置也颇费了一番工夫。

事情的起因是这样子的。我的手机,还有 Kindle Paperwhite,都能接电脑上提供一 USB 网络设备,可以用来 ssh 啊 rsync 啊什么的。但是呢,每次接好之后还要执行条命令设置 IP 地址,还要用 sudo、输入密码,很是麻烦。

我用来配置 IP 地址的命令是:

ifconfig usb0 192.168.42.1 # 手机
ifconfig usb0 192.168.15.1 # Kindle

查阅 udev(7) man 文档之后,对 udev 规则有了大概了解,知道大约要写成以下形式:

ACTION=="add", SUBSYSTEM=="net", XXX, RUN+="xxx"

需要一个属性来确定添加的设备是目标设备。插入设备,使用udevadm命令来检查设备的各种属性:

udevadm info --attribute-walk /sys/class/net/usb0

本来准备用 MAC 地址的,但后来才发现我这 Android 手机每次的 MAC 地址都不一样。想到 adb 用的序列号,于是我决定用ATTRS{serial}=="BX90345MWH"。然后轮到 Kindle 了。结果一看,竟然没有序列号属性了……但是它的 MAC 地址不会变,所以用 MAC 地址了。

写好规则之后可以先测试一下:

udevadm test /sys/class/net/usb0

配置正确的话会看到一行以run:开头的行里写着自己定义的命令。

没问题就让 udevd 重新加载规则文件:

sudo udevadm control --reload-rules

到这里似乎就该结束了。可事与愿违,测试都没问题了,但 IP 地址就是没出现。查阅各处文档,也没做错什么呀。后来才注意到测试时上边有一行输出:

run: '/usr/lib/systemd/systemd-sysctl --prefix=/proc/sys/net/ipv4/conf/usb0 --prefix=/proc/sys/net/ipv4/neigh/usb0 --prefix=/proc/sys/net/ipv6/conf/usb0 --prefix=/proc/sys/net/ipv6/neigh/usb0'

它使用的是绝对路径!想起 systemd 的命令必须是绝对路径,我尝试改成绝对路径,果然可以了:

ACTION=="add", SUBSYSTEM=="net", ATTRS{serial}=="BX90345MWH", RUN+="/bin/ifconfig %k 192.168.42.1"
ACTION=="add", SUBSYSTEM=="net", ATTR{address}=="ee:49:00:00:00:00" RUN+="/bin/ifconfig %k 192.168.15.1"
Apr 25

再次成功 root Kindle 之后,突发奇想:把自己的 ID 和二维码显示在其上应该会很有趣。生成二维码用 zint 搞定,添加上 ID 啦阴影啦边缘半透明啦用 GIMP 搞定。可是,我想把自带的 20 张屏保图片全部加上这个小图呢?我当然可以用 ImageMagick 批量添加,但是定位呢?对于不同图片,我要把这二维码放到不同的地方呢!于是终于拿 GTK 2 写了这么个很久以前就想写的程序——imagestamp

功能很简单,用法也很简单,make之后直接执行:

./imagestamp -p my_qr_code.png *.png

然后在弹出的窗口里不断地在适当的位置点击即可 :-)

下边是图片之一。由于对 Kindle 的像素密码估算不足所以那个二维码小了一点。

修改之后的 Kindle 屏保图片