Python Library: pipe


 C#/.NET 有个很迷人的特征 —— LINQ。别管因为什么,我们试着在 Python 里面 "发明" 一个类似的东西吧。

首先,我们可能要抛弃 "from x in datas..." 这样的想法,改用 LINQ method 方式。先写个伪码做目标。

range(10).sum().out()
以前写过一篇博客《Python Expression》,提到用重载运算符的方式来实现 Expression。"." 有特殊含义,还是改用其他的吧。比如 "|",这个符号在 Unix-Shell 里面通常用作数据管道,用来将数据依次传递处理,正好符合我们的需求。

range(10) | sum() | out()
好,开始简单一点,先写 sum。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Sum(object):
    def __ror__(self, iterator):
        return sum(iterator)

print range(10) | Sum()
执行:
$ ./main.py

45
很好,我们达成了第一步目标,由于和内置函数 sum 冲突,我们改用 "Sum" 大写。不过我们需要写很多很多类似的函数,用 "class + __ror__" 太麻烦也太啰嗦了。嗯,用一个装饰器来处理吧,这样就不用写那么多重复代码了,而且还可以用真正的函数来代替类。

装饰器的名字就用 Pipe 吧,符合我们上面说的思想。

class Pipe(object):
    def __init__(self, function):
        self.function = function
       
    def __ror__(self, iterator):
        return self.function(iterator)

@Pipe
def Sum(iterator):
    return sum(iterator)

@Pipe
def out(x):
    print "result =", x

range(10) | Sum | out
这回好看多了,基本实现了原定目标,那个伪码变成现实了。(关于装饰器,请参考《Python Essential: 9. Decorators (1)》)

别着急,事情还没完。函数不会总像 sum、out 这么简单,比如 "append(x)" 这样附带参数的函数怎么办呢?首先,这类函数肯定要先执行的,为了照顾 "|" 还得 "override __ror__" 一下。继续在 Pipe 上折腾吧,调用 "append(x)" 无非是 "Pipe.__call__(x)",然后 "return" 一个新的 Pipe 对象,不就有 "__ror__" 了。

class Pipe(object):
    def __init__(self, function):
        self.function = function
       
    def __ror__(self, iterator):
        return self.function(iterator)
       
    def __call__(self, *args, **kwargs):
        return Pipe(lambda iterator: self.function(iterator, *args, **kwargs))

@Pipe
def Sum(iterator):
    return sum(iterator)

@Pipe
def out(x):
    print "result =", x

@Pipe
def append(iterator, x):
    iterator.append(x)
    return iterator

range(10) | append(100) | Sum | out
执行:
$ ./main.py

result = 145
由于 "append(iterator, x)" 附带额外的参数,与 __ror__ 里面的调用 "self.function(iterator)" 不符,因此我们用一个 lambda 生成一个新的包装函数。托闭包的福,可以持有其调用参数 x。

到此,我们总算完成了一个很 Happy 的功能。当然,剩下来的工作就是写好多好多的函数了⋯⋯ 郁闷不?别着急,接下来有请我们今天的主角:

$ sudo easy_install pipe
看看它的介绍:

Help on module pipe:

NAME
    pipe - Infix programming toolkit

FILE
    /home/mandark/doc/code/python/pipe/pipe.py

DESCRIPTION
    Module enablig a sh like infix syntax (using pipes).
   
    = Introduction =
    As an exemple, here is the solution for the 2nd Euler Project exercise :
   
    "Find the sum of all the even-valued terms in Fibonacci
     which do not exceed four million."
   
    Given fib a generator of fibonacci numbers :
   
    euler2 = fib() | where(lambda x: x % 2 == 0)
                   | take_while(lambda x: x < 4000000)
                   | add
   
   
    = Vocabulary =
     * a Pipe: a Pipe is a pipeable function, somthing that you can pipe to,
               In the code [1, 2, 3] | add add is a Pipe
     * a Pipe function: A standard function returning a Pipe so it can be used like
               a normal Pipe but called like in : [1, 2, 3] | concat("#")
   
   
    = Syntax =
    The basic symtax is to use a Pipe like in a shell :
    >>> [1, 2, 3] | add
    6
   
    A Pipe can be a function call, for exemple the Pipe function where :
    >>> [1, 2, 3] | where(lambda x: x % 2 == 0) #doctest: +ELLIPSIS
    <generator object <genexpr> at ...>
   
    A Pipe as a function is nothing more than a function returning
    a specialized Pipe.
   
   
    = Constructing your own =
    You can construct your pipes using Pipe classe initialized with lambdas like :
   
    stdout = Pipe(lambda x: sys.stdout.write(str(x)))
    select = Pipe(lambda iterable, pred: (pred(x) for x in iterable))
   
    Or using decorators :
    @Pipe
    def stdout(x):
        sys.stdout.write(str(x))
   
    = Existing Pipes in this module =
   
    stdout
        Outputs anything to the standard output
        >>> "42" | stdout
        42
   
    lineout
        Outputs anything to the standard output followed by a line break
        >>> 42 | lineout
        42
   
    as_list
        Outputs an iterable as a list
        >>> (0, 1, 2) | as_list
        [0, 1, 2]
   
    as_tuple
        Outputs an iterable as a tuple
        >>> [1, 2, 3] | as_tuple
        (1, 2, 3)
   
    concat()
        Aggregates strings using given separator, or ", " by default
        >>> [1, 2, 3, 4] | concat
        1, 2, 3, 4
        >>> [1, 2, 3, 4] | concat("#")
        1#2#3#4
   
    average
        Returns the average of the given iterable
        >>> [1, 2, 3, 4, 5, 6] | average
   &n

相关内容

    暂无相关文章

评论关闭