Aug 22
本篇来自这个问题:python中创建父类对象的问题

也许可以认为这是Python设计的缺陷导致的,因为Python 3.0只需要用 `super().some_attr` 就行了。至于为什么需要两个参数,可以大概分析一下,如果有错,欢迎指正。

先介绍下背景知识:super是从Python 2.2开始随着 `new-style class` 一起引入的,也只能应用于所谓的 `new-style class` (即直接或间接继承于 `object` 的class),可以在一定程度上解决所谓`钻石继承`的问题。2.2起所有python内置class都是new-style class。

那么super是什么呢?实际上它是一个builtin的type,你调用 `super(Base, self)` 会返回一个 `__class__ = super` 的对象,这个对象可以作为一个代理,通过BFS的顺序(实际上列表已经保存在`Base.__mro__`里了)去访问第一个**直接定义**了目标属性的基类里的属性。`super(type, obj_or_subtype).attr` 基本上可以认为是  `find_in_mro_provide_by(obj_or_subtype, "attr")` ,有点晦涩。

**注意**,super()返回的不是“父类对象”,而是一个super类型的对象;实例化的时候,第一个参数是一个类型(type),第二个参数可以是type的instance,也可以是type的subclass。

介绍完基础知识,可以先说说为什么要有第二个参数。理由很简单 —— 我们知道 self 代表当前对象,每个对象的方法在定义的时候都需要显示地把self作为第一个参数,你本来应该写成这样
   
    some_class.some_method(obj, *args, **kwargs)

但是因为Python语法允许 `obj.some_metod(*args, **kwargs)` (本质上是个语法糖) ,所以你可以写得简单点(不必显式地给出方法的第一个参数)。而super对象则不同,它没有语法上的直接支持,所以在内部invoke some_method的时候必须指定某个对象,而这个对象的得你自己塞给它,也就是说把这个super对象绑定(bound)到第二个参数上。所以实际上并不是非要用self作为super的第二个参数,甚至super并不是必须在class内部才能调用:
    class A(object):
        def foo(self):
            print self.name

    class B(A):
        def __init__(self, name):
            self.name = name

    b1 = B('b1')
    super(B, b1).foo() #produces 'b1'


至于为什么要有第一个参数,如果你看了 `help(super)` 就会发现它居然提供了个单参数的版本:

    super(type) -> unbound super object

这个理解起来比较困难点。先说这个`unbound`,没绑定,就是说这个super对象没有实际绑定到某个对象上,它并不是可以直接用的。要怎么用呢?那就得注意到`help(super)`里面有一个 `__get__` 方法,也就是说,super类还是一个Descriptor类!哎,这个Descriptor类展开又是好大一段,简单地说就是它可以把自己和某个object的属性绑定,使得访问这个属性的时候,实际上是在调用它的`__get__/__set__`方法:
    class Descr(object):
        def __get__(self, obj, tp): #最后一句传进来的 obj=x, tp=X
            return 'get!'

    class X(object):
        t = Descr()

    x = X()
    print x.t #this produces 'get!'


回到unbound super, 为了使用它,就要通过 __get__ 方法把它再绑定到一个对象上。由于Descriptor的属性,特别适合这么用:
    class A(object):
        def foo(self):
            print 'foo'

    class B(A):
        def foo(self):
            self._super.foo()
    B._super = super(B) #这还不能直接写在B的定义里,多蛋疼啊。

    b = B()
    b.foo() #produces 'foo'


这是它的一个可能用法。我不确定是否还有其他更合适的用途,但是在这里实际上也并不是很好,尤其是遇到staticmethod的时候还会出错,再加上绕了这么大一个弯,实在不是很推荐使用。

据说unbound super的使用非常少,不知道现实意义有多少,也不知道当初为什么设计成这个样子,总之由于Python3.0已经改了(虽然仍然保留了unbound super),所以基本上还是可以认为这是设计的历史遗留问题。

参考文献(unbound super主要参考了这个系列):Things to Know About Python Super

[1] http://www.artima.com/weblogs/viewpost.jsp?thread=236275
[2] http://www.artima.com/weblogs/viewpost.jsp?thread=236278
[3] http://www.artima.com/weblogs/viewpost.jsp?thread=237121
Aug 16

mixo:又一个翻墙socks5代理 不指定

felix021 @ 2013-8-16 17:23 [IT » 网络] 评论(0) , 引用(0) , 阅读(14929) | Via 本站原创
因为不满ssh tunnel的使用效果,所以2012年12月某天(大概是17号)心血来潮写了这个小东西,由于 [socks5协议]( http://www.openssh.com/txt/rfc1928.txt ) 本身很简单、加上gevent/greenlet使得异步开发跟同步似的,所以200行就搞定了。但是性能上问题很大——主要是加密有问题。尽管加密就是最简单的xor,但是因为python不适合处理大量的小对象,所以当时写了一个python扩展,性能上就没问题了,但是又多了一项麻烦的依赖。后来发现已经有更成熟的shadowsocks,于是就弃坑了,也一直没有发布。

今天[2013.08.16]心血来潮,用ctypes来实现同样的功能,似乎也挺合适的;不过跟shadowsocks比起来有两个地方做得不好,一是没有更“高级”的加密方式(他家用了M2Crypto,代码看起来很复杂),另一个是shadowsocks在本地先回应socks5请求,只把必要的host:port信息发送给server,减少了一个来回,而我原先的实现则是在server端实现完整的socks5(现在把step1搬到client了,因为改动很小)。

总之好歹也是个凑合能用的东西了,发布出来晾着吧,也许哪天有人就用上了呢。

项目地址: https://github.com/felix021/mixo
Aug 16

使用ctypes来扩展Python 不指定

felix021 @ 2013-8-16 08:58 [IT » Python] 评论(1) , 引用(0) , 阅读(22046) | Via 本站原创
为了扩展Python,我们可以[用C/C++编写模块](http://docs.python.org/2/extending/ ),但是这要求对Python的底层有足够的了解,包括Python对象模型、常用模块、引用计数等,门槛较高,且不方便利用现有的C库。而 [ctypes](http://docs.python.org/2/library/ctypes.html ) 则另辟蹊径,通过封装`dlopen/dlsym`之类的函数,并提供对C中数据结构的包装/解包,让Python能够加载动态库、导出其中的函数直接加以利用。

快速上手
-------

最简单的,我们可以从 libc 开始:
    felix021@ubserver:~/code$ python
    Python 2.7.3 (default, Jul  5 2013, 08:39:51)
    [GCC 4.6.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import ctypes
    >>> libc = ctypes.CDLL("libc.so.6")
    >>> libc.printf("hello, %s! %d + %d = %d\n", "world", 1, 2, 3)
    hello, world! 1 + 2 = 3
    25


由于ctypes库能够直接处理 None, integers, longs, byte strings 和 unicode strings 类型的转换,因此在这里不需要任何额外的操作就能完成任务。(注意,最后一行的25是printf的返回值,标识输出了25个字符)

自己动手
-------

[这里](http://wolfprojects.altervista.org/articles/dll-in-c-for-python/)有一个最简单的例子——实现一个 `int sum(int a, int b)`:

    //file foo.c
    #ifdef __WIN32
    #define DLLEXPORT __declspec(dllexport)
    #else
    #define DLLEXPORT
    #endif
   
    DLLEXPORT int sum(int a, int b)
    {
        return a + b;
    }


编译之得到 foo.so:

    $ gcc -fPIC -shared -o foo.so foo.c

然后在Python里头:

    >>> foo = ctypes.CDLL("./foo.so")
    >>> foo.sum(5, 3)
    8


真是超级简单。

丰衣足食
-------

下面来个稍微复杂点的例子:反转一个字符串。尽管ctypes可以直接处理string的转换,但是你不能直接修改string里的内容,所以这里需要变通一下。可能的解决方案有两个:

1. 在foo.c里面malloc一点空间然后返回。这样可以不修改原string,但是却要考虑释放该空间的问题,不太好处理。

2. 让Python分配好空间,将原字符串拷贝进去,再让foo里面的函数来修改它。ctypes为这种方案提供了`create_string_buffer`这个函数,正适合。

那就让我们用方案2来实现它:

    //file foo.c
    DLLEXPORT int reverse(char *str)
    {
        int i, j, t;
        for (i = 0, j = strlen(str) - 1; i < j; i++, j--)
            t = str[i], str[i] = str[j], str[j] = t;
        return 0;
    }


然后在Python里头:

    >>> s = ctypes.create_string_buffer("hello world!")
    >>> foo.reverse(s)
    0
    >>> print s.value
    !dlrow olleh


这样就OK啦。

补充说明
-------

以上的操作都是在Linux下完成的,在Windows下基本上一致,除了windows不能直接load libc.so.6,而是应该找 msvcrt :

    >>> libc = ctypes.cdll.msvcrt

或者直接导入文件

    >>> libc = ctypes.CDLL("msvcr90.dll")


小结
-------

前面演示了ctypes库的简单使用,已经能够完成许多任务了;但是它可以完成更多的任务,包括操作更复杂的例如结构体、数组等C数据结构,具体的这里不细说了,可以参考详细的[官方文档](http://docs.python.org/2/library/ctypes.html)

好了,就到这里。
分页: 1/1 第一页 1 最后页 [ 显示模式: 摘要 | 列表 ]