K.I.S.S. – 简单哲学 Linux, Freedom, Arch, Python, Gtk+, C … Keep It Simple, Stupid!

15十一/1111

那些在 Python 3 中闪亮的

大家好,又到了科普时间,咳咳。

距离 3 发布已经有一段时间了,主流发行版都已经带了 3 的软件包,甚至 Arch 等发行版还将其设为了默认的 版本。多数的库也已经带了 3 的支持(也有 Twisted、Django 等例外),是不是偶尔也想着要不要将自己的程序升级一下呢?

昨天稍微有时间研究了一下 Python 3,就将我在文档中找到的有趣新特性分享给大家。

默认返回迭代器(Iterator)

print 成为一个函数、默认不用地板除(Floor Divide)之类的我就不说了,想必地球人都知道有这么回事。

值得一提的是,原来需要使用 xrange 、 iteritems 等等函数和方法才能返回的迭代器现在成为了默认,替代了原来返回列表的函数。就连 map 、 filter 、 zip 等函数都返回迭代器了。

大家都知道相对于返回完整的列表,迭代器省去了一次生成所有元素的开销,并且在循环 break 的时候,就停止迭代,防止了额外的开销,所以一般情况下迭代器要比列表快得多。

如果你仍然需要完整列表,可以通过 list(some_iter) 构造,不过这种问题往往使用列表解析(List comprehension)就能够解决。

字符串分为 str 和 bytes

在 Python 2 中,字符串分为 ASCII 码表示('some text')和 Unicode 表示(u'Unicode 字符串'),默认为 ASCII 码。

不过在 Python 3 中,默认就是万能的 Unicode 码了,所以字符串前面不用加字母 u 也可以写 Unicode 了,当然这不是重点,重点是不会有各种 ASCII 和 Unicode 转换和混用带来的错误了。

另外, Python 3 中增加了一种 bytes 对象(b'\xb6\xfe\xbd\xf8\xd6\xc6\xca\xfd\xbe\xdd'),专门用来表示编码后的(二进制)数据,所以现在对字符串的编码就是从 str 到 bytes 的转换,反之亦然,两者不能混用,这样编码与否一目了然,免除了很多错误。

源文件编码默认为 UTF-8

Python 3 在字符编码方面有很多改进,其中之一就是默认的源文件编码从 ASCII 变为 UTF-8 ,也就是说以前在文件头加上的各种花样的 coding=utf-8 不再需要了!

# coding: UTF-8
# vim:fileencoding=UTF-8
# -*- coding=UTF-8 -*-
# vim: set fileencoding=UTF-8

标识符支持非 ASCII 字符

这个自行理解,易语言表示压力很大。

>>> 所有 = all
>>>
>>> class 男人:
...     @classmethod
...     def 包括(cls, Ta):
...         return isinstance(Ta, cls)
...
>>> def 一起玩(人们):
...     if 所有(男人.包括(Ta) for Ta in 人们):
...         print('他们是基友')
...     else:
...         print('他们是朋友')
...
>>> 小攻 = 男人()
>>> 小受 = 男人()
>>> 一起玩([小攻,小受])
他们是基友
>>>

新的字符串格式化语法

原来的 %s %d %你妹 语法已经不推荐,并且很快会被弃用,新的字符串格式化方法(2.6 版引入)为 str.format 或者内置函数 format 。比如:

>>> 三青年 = {'小红':'普通青年','小明':'文艺青年','小亮':'二逼青年'}
>>> '{小红}说我想吃罐头,{小明}说更上一层楼,{小亮}说阿伊呀伊呦。'.format(**三青年)
'普通青年说我想吃罐头,文艺青年说更上一层楼,二逼青年说阿伊呀伊呦。'
>>>

字典解析和集合解析

有了列表解析,当然也少不了字典解析:

>>> {k: v + '青年' for k, v in [('小明', '文艺'), ('小红', '普通'), ('小亮', '二逼')]}
{'小明': '文艺青年', '小红': '普通青年', '小亮': '二逼青年'}
>>>

还有集合解析:

>>> {小吃 for 小吃 in ('豆浆', '油条', '包纸')}
{'油条', '包纸', '豆浆'}
>>>

有序字典与 configparser

默认 Python 字典是无序的,不过新引入的 collections.OrderedDict 类提供了一种有序字典实现,并且被 configparser 默认使用,现在使用 configparser 类就可以得到有序的 ini 格式配置文件了!

而 configparser 模块现在完全支持使用类字典的方法进行读写了!你妹,我之前的工作全白做了!

ABC

抽象基类(Abstract Base Classes),就是像 C++ 里面虚类一样的东西。作为其子类,只有将所有抽象方法都实现,才能实例化。

抽象基类是对 Duck Typing 的补充,由于引入了 @abstractmethod , @abstractstaticmethod , @abstractclassmethod , @abstractproperty 四个修饰符,强制抽象方法必须实现,所以可以一定程度上避免错误,用起来感觉比 Duck Typing 安心一些。

结局

以上就是我把 Python 3.0 到 3.2 的 What's new 看了一遍的成果,总体来说 Python 3 本身变得更加规范,更加灵活,如果你的程序不依赖于 Python 2 特有的库的话,来试试 Python 3 很不错!

结局?结局?结局就是小亮和小红幸福地生活在了一起,小明自己吃豆浆油条包纸。

(完)

标签: 11 评论
23九/100

使用 dbus-python 建立单实例进程

D-Bus 是 Linux 系统上广泛应用的消息总线,是 Linux 桌面程序常用的消息通信机制之一,熟悉 Linux 编程的同学可能对它已经很熟悉了,不过对于我来说,看了好长时间文档才算有点收获,赶紧记录下来。

大家知道 # 得是个单实例(Single Instance)程序(不知道的同学赶紧知道),之前这种单实例的特性是由网上抄来的一段代码,用 Socket 的唯一地址实现的。功能虽然实现了,不过想要进一步扩展就比较难了,好吧,相比于 D-Bus,Socket 我更是一窍不通。

为什么要扩展呢?大家知道,作为一个下载器,是要支持浏览器滴,可是怎么支持浏览器呢?当然是浏览器来调用下载器的命令行。这样就要求,下载器是单实例程序的同时,运行其他实例的时候,不仅要提示用户已经有一个实例运行,还要从命令行接收参数,传输给之前存在的实例,这就需要进程间通信了。

下面就是一段简单的用 D-Bus 实现的单实例类,继承这个类的类都将获得单实例特性。目前它还没有通信相关的代码,将在后面加入。

import 
import .service
import .mainloop.glib

class SingleInstanceAppMixin:
    "Single Instance Application"

    def __init__(self, bus_name):
        .mainloop.glib.DBusGMainLoop(set_as_default = True)
        self.bus = .SessionBus()
        try:
            self.bus_name = .service.BusName(bus_name,
                    self.bus, allow_replacement = False, replace_existing = True, do_not_queue = True)
        except .exceptions.NameExistsException:
            print "Another instance is already running."
            self.on_instance_exists()

    def on_instance_exists(self):
        """
        This method is called when an instance of the program already
        exists. It may be overwritten by subclasses.
        """
        import sys
        sys.exit(0)

代码很简单,主要原理就是利用 D-Bus 中 BusName 的唯一性,其中 BusName 的三个 Bool 型常量的设置分别表示不允许被替换、尝试替换同名 Bus、存在同名时不加入队列,这样就可以保证存在同名 Bus 的时候出现异常了。

需要注意的是 self.bus_name 将其返回结果加上引用,免得执行完函数之后销毁掉了,在调试的时候在这里卡了半天,用 vimdiff 才看出来问题所在……

8五/1019

神秘软件 Yaner 截图

我就发图,我不说话。

yaner-main.png
yaner-normal.png
yaner-bt.png
yaner-metalink.png

8五/102

简单实现 Python 有序字典(Ordered Dict)

的 Dict 类型很好用,不过有一点可惜就是它的 keys() 是乱序的,想要用它来保存有序的 key-value 对(比如配置文件)就比较困难,碰巧我的毕设就要用到这样一个类型来存放配置文件,怎么办呢?
搜索了一下,Python 2.7 / 3.1 才有 Ordered Dict 的支持,我总不能去 Python 源码里面拽吧……
还有些方法比较陈旧,继承自 UserDict,不支持 iter*() 系列方法,虽然不碍事,不过心里还是不舒服。
不过找来找去,还是让我找到了相对比较简洁,功能有符合要求的代码,见下:

from UserDict import DictMixin

class odict(DictMixin):

    def __init__(self):
        self._keys = []
        self._data = {}

    def __setitem__(self, key, value):
        if key not in self._data:
            self._keys.append(key)
        self._data[key] = value

    def __getitem__(self, key):
        return self._data[key]

    def __delitem__(self, key):
        del self._data[key]
        self._keys.remove(key)

    def keys(self):
        return list(self._keys)

    def copy(self):
        copyDict = odict()
        copyDict._data = self._data.copy()
        copyDict._keys = self._keys[:]
        return copyDict

代码来自 ActiveState ,PSF 许可。

标签: 2 评论
27五/090

在自己的 GTK+ 程序中加入 RGBA 透明支持

上次介绍了怎么在 Arch Linux 下启用 RGBA 透明窗口 ,然而,目前支持 的程序还很少,看起来有的透明,有的不透明,很不爽吧?
另外,+ 默认是不启用 RGBA 支持的,我们自己编写程序的时候,怎么加入 RGBA,达到这种酷炫的效果呢?
细心的同学可能已经发现,上次我发的图中最顶端那个程序就是我自己写的,而且已经实现了半透明效果。
加入 RGBA 效果的方法并不难,仅仅是几条语句而已,看看下面的 patch:

From fb88cb790a08e928c5e6656f8334264c5ae9f93a Mon Sep 17 00:00:00 2001
From: Kevin Lange
Date: Wed, 5 Mar 2008 09:26:53 -0500
Subject: [PATCH] Added RGBA colormap support to the GUI

---
 ccm/Window.py |    5 +++++
 1 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/ccm/Window.py b/ccm/Window.py
index 09306da..84f1d76 100644
--- a/ccm/Window.py
+++ b/ccm/Window.py
@@ -42,6 +42,10 @@ class MainWin(gtk.Window):

     def __init__(self, Context, pluginPage=None, categoryName=None):
         gtk.Window.__init__(self)
+        self.gtk_screen = self.get_screen()
+        colormap = self.gtk_screen.get_rgba_colormap()
+        if colormap:
+            gtk.widget_set_default_colormap(colormap)
         self.ShowingPlugin = None
         self.Context = Context
         self.connect("destroy", self.Quit)
@@ -99,6 +103,7 @@ class MainWin(gtk.Window):
             self.ToggleCategory(None, categoryName)

     def Quit(self, *args):
+        gtk.widget_pop_colormap()
         gtk.main_quit()

     def ResetMainWidgets(self):
--
1.5.2.5

呵呵,这是 CCSM 的 RGBA 补丁,看到了吧,其实关键的也就那么几行而已,在初始化的时候试着载入 RGBA 颜色表,在退出的时候对颜色表进行出栈,其他语言也是这个道理。
有了这几行代码,是不是想把桌面上的所有 GTK+ 程序 hack 掉?
呵呵,赶快在自己的程序里加入 RGBA 支持吧!

16五/093

Python 版 Linux 下的迅雷

Linux 下该不该有迅雷,这个问题一直存在分歧,在此也不予讨论。不过,迅雷抗死链的作用是巨大的,这点是不容置疑的,很多人确实用得着。
有需求就有市场,于是乎,Ubuntu 中文论坛的一位放出了 furl 这个小程序,不但可以解析迅雷的 :// 协议,还可以返回迅雷候选地址, xiooli 大侠更是做出了 Shell 脚本,自动调用 进行下载。
可惜的是,furl 是 32 位闭源程序,所依赖的 lib32-libopenssl2 在 Arch Linux 下面安装不了……
突然想起,前些日子,可可熊大侠不是写过一个 pythunder 么?干嘛不用这个下载呢?于是就有了下面的程序……

#!/usr/bin/env #pyaria2.py

import os, sys, urllib

def usage():    print """Usage:  tharia2.py [OPTIONS] URL

OPTIONS: As same as options of aria2c"""

def get_url_list(url, listpath):    if not os.path.exists(listpath):        print "Getting URL list, please wait..."        f = urllib.urlopen("http://cocobear.info/demo/pythunder/?url=%s" % url)        lst = open(listpath, "w+")        lst.writelines(f.readlines())        f.close        lst.seek(0)    else:        print "Found existing url list: ", listpath        lst = open(listpath)

    url_list = [line[:-1] for line in lst]    lst.close()    print "Recieved %d url(s)." % len(url_list)    return " ".join(url_list)

def download(url):    for prefix in (r"http://", r"https://", r"ftp://"):        if url.startswith(prefix):            break    else:        print "Invalid URL: %s" % url        exit()

    listdir = os.path.expanduser("~/.tharia2/list/")    listfile = os.path.split(url)[-1] + ".list"    if not os.path.exists(listdir):        os.makedirs(listdir)    listpath = os.path.join(listdir, listfile)

    url_list = get_url_list(url, listpath)    cmd = " ".join(("aria2c -c", " ".join(sys.argv[1:-1]), url_list))    print "Executing command: %s" % cmd    if not os.system(cmd):        os.remove(listpath)

if __name__ == "__main__":    if len(sys.argv) > 1:        download(sys.argv[-1])    else:        usage()

很简单的一个脚本,呵呵,参数和 aria2 是一样的,区别只在于对于 url 的处理(暂时 url 只能放在命令行的最后)。
比如下载 http://www.dmato.com/DownloadFile/FishDesk2009Beta4.exe,就运行:

python tharia2.py http://www.dmato.com/DownloadFile/FishDesk2009Beta4.exe

默认 aria2 可以支持 5 线程,如果你想改为 10 线程,那么:

python tharia2.py -s 10 http://www.dmato.com/DownloadFile/FishDesk2009Beta4.exe

指定下载目录,用 -d:

python tharia2.py -d "/home/iven" -s 10 http://www.dmato.com/DownloadFile/FishDesk2009Beta4.exe

更多用法详见:

aria2c --help

目前的主要问题是,可可熊大侠的网站相应速度太慢了,过半分钟才会返回候选列表,汗……不知道是不是我的网速问题,大家可以试一下。另外,就是没有解迅雷的 thunder:// 协议了,还有快车什么的,这个貌似不难,有时间研究一下。
现在还没有开源版本的迅雷候选地址搜索工具,主要大家怕流传太广,遭到迅雷封锁。但是还是好想看看代码是怎么写的啊……
最后,项目的地址:http://github.com/iven/tharia2/

13五/094

Python – 你可能不知道的

最近决定系统学习 ,于是把去年 ChinaUnix 的赠书《 核心编程》掏出来,使劲啃……
通过这几天的学习,总算是看完了前六章,发现许多以前在《Dive Into Python》里面没有提到,或者很少提到,或者很容易忘掉的特性,在此记录一下~呵呵,感谢 CU 的赞助,感谢 CCAV,感谢 GFW,感谢 宋吉广 的人品~
* 首先,交换 x, y 的值:

x, y = y, x

* 跟 C 不同,连续的判断:

if x > y > z:

* 跨平台,平台自适应的换行符(\n、\r\n……):

os.linesep

* `foo`的作用和 repr(foo) 的作用是一样的(虽然``已经不推荐使用)
* str()、tuple()、list()等是工厂函数,用来生产对象,而不是简单的强制转换
* id() 函数用来查看对象的唯一标识符
* int 型会在必要时自动转换成 long 型
* 字符串和 tuple 一样,是不能改变的,例如下面的代码执行会出错:

str[0] = 'a'

* Python 中存在复数这个类型
* // 可以用来做整除(地板除):

>>>1.0 // 2.00.0

* 幂运算符 **
* 对于 list,extend() 比 + 快
* 可以用 string.Template() 实现 Shell 的 ${变量} 的功能
* enumerate() 函数可以用来产生序列的序号
* tuple 可以用来作 dict 的 key
* copy.deepcopy() 可以用来做深拷贝

Python 还有很多不知道的啊,继续学习……

标签: 4 评论
2五/090

使用 PyGTK 和 Cairo 编写一个简单的时钟

呵呵,其实说这个是个自定义控件也不为过。
先介绍一下 Cairo:Cairo 是一套提供设备独立的矢量图形 API 的二维图形库,支持很多不同的后端,如果可能的话,还可以使用硬件加速绘图。Cairo 是用 C 语言编写的,但是提供了很多其他语言的绑定,Factor, Haskell, Lua, Perl, , Ruby, Scheme, Smalltalk 等等。Cairo 基于 LGPL 和 MPL 双许可,这意味着你可以用它开发闭源软件。Cairo 是自由软件。
关于 Cairo 的更多详情,可以参照维基百科
Cairo 的使用方法很简单,跟中学时学习的 LOGO 语言差不多,这里用 + 编写一个时钟出来。
首先,建立一个窗口。GTK+ 提供了一个专门用来画图的控件 DrawingArea,我们继承它,建立一个类 CairoDrwa,并且把一个对象加到窗口上去。虽然 GTK+ 本身提供了不少绘图功能,不过这里只说 Cairo。

import gtk, glib, math, time
class CairoDraw(gtk.DrawingArea):
   """Drawing with Cairo"""
   def __init__(self):
       super(self.__class__,self).__init__()

class MainWindow(gtk.Window):
   """Main window of the test program"""
   def __init__(self):
       super(self.__class__, self).__init__(gtk.WINDOW_TOPLEVEL)
       self.set_title("Cairo test")
       self.connect("delete-event", gtk.main_quit)

       cairo_test= CairoDraw()
       self.add(cairo_test)

   def main(self):
       self.show_all()
       gtk.main()

if __name__ == "__main__":
   window = MainWindow()
   window.main()

然后,连接事件。把这个类的 expose-event 连接到一个回调函数 on_expose 上去,expose-event 是一个“曝光”事件,在控件被显示的时候会触发此事件。

class CairoDraw(gtk.DrawingArea):
   """Drawing with Cairo"""
   def __init__(self):
       super(self.__class__,self).__init__()
       self.connect("expose-event", self.on_expose)

下面来写 on_expose 函数。首先我们要取得我们的 Cairo Context(可以理解为“画板”),这个画板是从 DrawingArea 中的 GdkWindow 中取得的。

    def on_expose(self, widget, event):
       context = widget.window.cairo_create()
       return False

我们需要设定 DrawingArea 的刷新区域。这样以后每次触发 expose-event 的时候,都会重绘这个区域。

    def on_expose(self, widget, event):
       context = widget.window.cairo_create()

       context.rectangle (event.area.x, event.area.y,
               event.area.width, event.area.height)
       context.clip()

       return False

我们把绘图的部分写在 draw 这个函数中,然后让控件每秒钟重绘一次。这样每秒时间改变的时候,图形就会刷新,指针就会动了。

    def on_expose(self, widget, event):
       context = widget.window.cairo_create()

       context.rectangle (event.area.x, event.area.y,
               event.area.width, event.area.height)
       context.clip()

       self.draw(context)

       glib.timeout_add(1000, self.queue_draw)
       return False

下面来写 draw 这个函数。
首先来画表盘,也就是一个圆。我们用 arc 函数来画一个弧线,这个弧线的角度从 0 到 2 * pi,也就是一个圆了。注意,这时候我们还没有上色,所以画板依然是空的。使用 set_source_rgb 来设定画笔颜色,fill_preserve 和 stroke 分别用于填充和描边。

clock1.png

    def draw(self, context):
       rect = self.get_allocation()
       x = rect.x + rect.width / 2
       y = rect.x + rect.height / 2
       radius = min(rect.width / 2, rect.height / 2) - 5

       context.arc(x, y, radius, 0, 2 * math.pi)
       context.set_source_rgb(1, 1, 1)
       context.fill_preserve()
       context.set_source_rgb(0, 0, 0)
       context.stroke()

接下来画刻度。很简单的算法,值得注意的是 save 和 restore 两个函数,这是一种类似堆栈的保存 Context 状态的方法,这样对线的长度修改之后,很容易就可以恢复过来。

clock2.png

        for i in xrange(12):
           context.save()

           if i % 3 == 0:
               inset = .2 * radius
           else:
               inset = .1 * radius
               line_width = context.get_line_width()
               context.set_line_width(.5 * line_width)

           context.move_to(
                   x + (radius - inset) * math.cos(i * math.pi / 6),
                   y + (radius - inset) * math.sin(i * math.pi / 6))
           context.line_to(
                   x + radius * math.cos(i * math.pi / 6),
                   y + radius * math.sin(i * math.pi / 6))
           context.stroke()
           context.restore()

最后,对指针进行绘制。和表盘差不多的算法。

clock3.png

        tm_hour, tm_min, tm_sec = time.localtime()[3:6]
       handlist = (
               (tm_hour, .3, 12, 1.5),
               (tm_min, .2, 60, 1),
               (tm_sec, .1, 60, .5),
               )
       for (hand, inset, num, width) in handlist:
           context.save()

           inset *= radius
           line_width = context.get_line_width()
           context.set_line_width(width * line_width)
           context.move_to(
                   x + (radius - inset) * math.sin(2 * hand * math.pi / num),
                   y - (radius - inset) * math.cos(2 * hand * math.pi / num))
           context.line_to(x, y)
           context.stroke()

           context.restore()

这样,一个简单的时钟程序就做好了。源代码在这里
参考:Writing a Widget Using Cairo and PyGTK 2.8
PS:晕,写完了才发现这篇文章还有下半部分……