Python 如何傳遞運算表示式

z正小歪發表於2018-04-02

首先要說明的一下,所描述的是 Python 中的 運算表示式 的部分,不是 Python 表示式的部分。

關於什麼是 Python 中的運算表示式,可以參考 Python 文件 10.3.1. Mapping Operators to Functions 部分,所需要傳遞的就是這部分運算表示式。

一個簡單的問題

題目如下:

給定一個實數列表和區間,找出區間部分。

這個問題中有 2 個變數,一個是實數列表,一個區間。其中區間包含幾種情況:

  • 左開右開
  • 左開右閉
  • 左閉右開
  • 左開右開

由於區間存在多種情況,無法通過一種固定的形式去描述這個區間。

假設左邊界是 a,右邊界是 b,列表中某個變數是 x,那麼轉換成區間關係就是:

  • (a, b):a < x < b
  • (a, b]:a < x <= b
  • [a, b):a <= x < b
  • [a, b]:a <= x <=b

那麼如何使用一種優雅的方式獲取這種運算關係,就是要解決的一個問題。

典型的應用

傳遞運算表示式在 Python 中最典型的應用在 ORM 上。

Python 呼叫關係型資料庫基本上都是通過 Database API 來實現的,查詢資料依賴於 SQL,ORM 最大方便之一就是能生成查詢所用的 SQL。

非關係型資料庫中有的 query 語句也支援條件查詢,比如 AWS 的 Dynamodb。那麼如何通過 ORM 來生成 query 語句也是一直重要的地方。

在 peewee 文件的 Query operators 中可以看到這個 ORM 支援常用的操作符來表示欄位和欄位之間的關係。

文件中還用通過函式來表達關係,他們實質上是一樣的,但是這個不在討論範圍之類

# Find the user whose username is "charlie".
User.select().where(User.username == 'charlie')

# Find the users whose username is in [charlie, huey, mickey]
User.select().where(User.username << ['charlie', 'huey', 'mickey'])
複製程式碼

從上面程式碼中可以看出用 == 來表示相等,用 << 表示 IN。

解決方案

中心思想非常簡單:儲存還原操作符與引數

Python 所支援的操作符都可以通過重寫魔法方法來重新實現邏輯,所以在魔法方法中已經可以拿到操作符和引數。

一元操作符和二元操作符都是如此。

所以,最開始那個問題可以分為兩個步驟來完成。

第一步,儲存操作符和引數,可以採用一個類重寫相關操作符完成。

class Expression:
    def __eq__(self, other):
        return Operator('==', other)

    def __lt__(self, other):
        return Operator('<', other)

    def __le__(self, other):
        return Operator('<=', other)

    def __gt__(self, other):
        return Operator('>', other)

    def __ge__(self, other):
        return Operator('>=', other)
複製程式碼

第二步,還原操作符和引數。在 Operator 類中完成從操作符轉化為函式的過程。

import operator

class Operator:
    def __init__(self, operator_, rhs):
        self._operator = operator_
        self._rhs = rhs 
        self._operator_map = {
            '==': operator.eq,
            '<': operator.lt,
            '<=': operator.le,
            '>': operator.gt,
            '>=': operator.ge
        }

    @property
    def value(self):
        return self._rhs 

    @property
    def operator(self):
        return self._operator_map[self._operator]
複製程式碼

一個 Operator 的例項就是一個運算表示式,可以自己定義操作符和函式的關係,來完成一些特殊的操作。

所以,有了 Expression 和 Operator,就能很優雅地解出最開始問題的答案

def pick_range(data, left_exp, right_exp):
    lvalue = left_exp.value
    rvalue = right_exp.value
    
    loperator = left_exp.operator
    roperator = right_exp.operator
    
    return [item for item in data if loperator(item, lvalue) and roperator(item, rvalue)]
複製程式碼

最後來幾個測試用例

>>> exp = Expression()
>>> data = [1, 3, 4, 5, 6, 8, 9]
>>> pick_range(data, 1 < exp, exp < 6)
[3, 4, 5]
>>> pick_range(data, 1 <= exp, exp < 6)
[1, 3, 4, 5]
>>> pick_range(data, 1 < exp, exp <= 6)
[3, 4, 5, 6]
>>> pick_range(data, 1 <= exp, exp <= 6)
[1, 3, 4, 5, 6]
>>>
複製程式碼

總結

關於傳遞運算表示式,知道的人會覺得簡單,不知道的人一時間摸不著頭腦。

Python 強大神祕,簡約的邏輯中總是有複雜的背後支援,深入 Python 才能明白 Python 之美。

相關文章