Pipe——Python 的中缀语法库

赖勇浩(http://laiyonghao.com)

注:本文基本上是翻译这篇文章(http://dev-tricks.net/pipe-infix-syntax-for-python)。

通过 Pipe 模块,就能够使用 Python 用上中缀语法。

首先来看一下传统前缀语法的代码:

sum(select(where(take_while(fib(), lambda x: x < 1000000) lambda x: x % 2), lambda x: x * x))

很难读?再来看看中缀语法代码:

fib() | take_while(lambda x: x < 1000000) \
      | where(lambda x: x % 2) \
      | select(lambda x: x * x) \
      | sum()

好读多了吧?

虽然 Pipe 基类的代码很少,但很强大,能够让你很容易写出 pipeable 函数哦。而且这个模块本身就带了超过 30 个已经写好的函数,比如 ‘where’, ‘group_by’, ‘sort’, ‘take_while’ …

如果想一下 Pipe,需要先安装,在命令行执行:

pip install -U pipe

然后等着安装完成就行了。现在可以打开一个交互式 Shell,来试一下:

Python 2.6.6 (r266:84292, Dec 26 2010, 22:31:48) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pipe import *
>>> [1, 2, 3, 4, 5] | add
15
>>> [5, 4, 3, 2, 1] | sort
[1, 2, 3, 4, 5]

很简单吧?如果有什么问题,可以随时 help(pipe) 一下,就可以看到完备的帮助了。

接下来我们展示一下组合两个或更多的 pipeable 函数:

>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | concat
'1, 3, 5'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | concat
'3, 5'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | select(lambda x: x * x) | concat
'9, 25'
>>> [1, 2, 3, 4, 5] | where(lambda x: x % 2) | tail(2) | select(lambda x: x * x) | add
34

因为 Pipe 是惰性求值的,所以我们完成可以弄一个无穷生成器而不用担心内存用完,比如:

>>> def fib():
...    a, b = 0, 1
...    while True:
...        yield a
...        a, b = b, a + b

现在让我们用 fib() 函数来完成一个 http://projecteuler.net 的第 2 题:

Find the sum of all the even-valued terms in Fibonacci which do not exceed four million.

>>> euler2 = fib() | where(lambda x: x % 2 == 0) | take_while(lambda x: x < 4000000) | add
>>> assert euler2 == 4613732

怎么样?可读性强吧?漂亮不?

最后,我们来学习一下如何利用 @Pipe decorator 创建一个新的 pipeable 函数:

假定要创建一个函数 yield 它的输入的前 x 个元素

假定要创建一个函数能够用以 (1, 2, 3, 4, 5) | take(2) 语句来获取前两个元素

那么最初的实现可能是这样的:

def take(iterable, qte):
    for item in iterable:
        if qte > 0:
            qte -= 1
            yield item
        else:
            return

现在,你只要把 @Pipe 盖在这个函数上头,这货就是 pipeable 函数了!

====================

鸣谢:

感谢 @yinhm 在 Twitter 上分享《Pipe: Infix syntax for Python》一文,让我知道还有这等神器。

感谢 @kyhpudding 在 Twitter 上分享他的 solo 模块,一个比 pipe 更奇幻的模块,希望我能理解清楚,能够跟大家介绍之。

### 实现中缀表达式计算的方法 为了在 Python 中实现中缀表达式的计算,可以采用两种主要方法:一种是先将中缀表达式转换成后缀表达式再进行计算;另一种则是直接通过栈结构处理中缀表达式。 #### 方法一:转换为后缀表达式后再求值 这种方法涉及两部分工作: 1. **从中缀到后缀的转换** 使用两个栈 s1 和 s2 来完成这一过程。s1 用于保存运算符,而 s2 则用来构建最终的后缀表达式[^4]。当遍历给定的中缀字符串时,对于每一个字符: - 若当前字符是一个操作数,则直接加入到 s2; - 遇见左括号 '(' 应立即将其推入 s1; - 当遇到右括号 ')' ,则持续从 s1 弹出元素直到遇见对应的左括号为止,并把这些弹出来的符号依次放入 s2; - 对于其他任何运算符而言,如果它的优先级高于或等于 s1 的顶部元素(除了'('),那么就把它压入 s1;反之,则应不断把 s1 栈顶较低级别的运算符移至 s2,直至满足条件或将整个 s1 清空之后才允许新来的运算符进入 s1。 完整遍历结束后,还需确保所有剩余在 s1 内部的操作符都被转移到 s2 上面去形成完整的逆波兰表示法形式的结果。 2. **基于后缀表达式的评估** 创建一个新的辅助栈 tempStack 。按照顺序读取由上述步骤得到的 RPN 表达式的每一项: - 如果该项是个数值,则简单地将其压入 tempStack ; - 倘若是二元运算符,则分别取出位于 tempStack 最上面的两项作为该运算的对象执行相应的数学运算并将所得结果重新放回 tempStack; - 经过一轮这样的迭代以后,tempStack 将只含有唯一的一个元素即为我们所期望获得的答案。 ```python def infix_to_postfix(expression): precedence = {'+':1, '-':1, '*':2, '/':2} operators_stack = [] output_queue = [] tokens = expression.split() for token in tokens: if token.isnumeric(): output_queue.append(token) elif token == "(": operators_stack.append(token) elif token == ")": top_token = operators_stack.pop() while top_token != "(": output_queue.append(top_token) top_token = operators_stack.pop() else: while (operators_stack and operators_stack[-1] != "(" and precedence[token] <= precedence.get(operators_stack[-1], 0)): output_queue.append(operators_stack.pop()) operators_stack.append(token) while operators_stack: output_queue.append(operators_stack.pop()) return " ".join(output_queue) def evaluate_postfix(postfix_expr): stack = [] for char in postfix_expr.split(): try: value = int(char) stack.append(value) except ValueError: b = stack.pop() a = stack.pop() switcher={ "+":a+b, "-":a-b, "*":a*b, "/":a/b } result=switcher[char] stack.append(result) return stack[0] expression = "( 1 + 2 ) * ( 6 - 3 ) * 5 - 10" postfix_expression = infix_to_postfix(expression) result = evaluate_postfix(postfix_expression) print(f"The evaluation of '{expression}' is {result}") ``` #### 方法二:直接解析中缀表达式 此方式不需要显式创建RPN版本而是利用单个栈来跟踪待处理的操作符以及它们之间的相对位置关系从而即时得出答案[^3]。这种方式下同样需要注意保持正确的运算次序并且妥善管理好各种类型的括号配对情况。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值