跳转至

如何将 Python 写成文言文

绪论

俗话说得好:

“用 Python 写程序就像是用白话写作文。”

既然如此,为何不试一试将 Python 写成文言文呢?对于一个像 Python 这样具有丰富语法糖的解释型语言来说,将其写成文言文是一件非常容易且有趣的事情。也正如某人所说:

“(文学意义上的)文言文何尝不就是一个语法糖的合集呢?”

本篇博客的意义,便是介绍如何利用各种语法糖,将 Python 写成文言文。当然,如果你想在生产环境中用这种写法写 Python 程序,理论上也不是不行,但是你真的见过有人在自己的文章里写一大堆文言文吗?

写法示范

为了更好地运用语法糖,本博客涉及到的所有 Python 版本均为 Python 3.10。

1. 使用 filter() 函数取代 if-elif-else 判断链

现代文:

def calc_const(score: int | float, rating: float) -> float | None:
    if rating == 0:
        const = None
    elif score >= 1000e4:
        const = rating - 2
    elif score >= 980e4:
        const = rating - 1 - (score - 980e4) / 20e4
    elif score >= 950e4:
        const = rating - (score - 950e4) / 30e4
    else:
        const = None

    return const

文言文:

def calc_const(score: int | float, rating: float) -> float | None:
    const_pattern: list[tuple[bool, float | None]] = [
        (rating == 0, None),  # if rating == 0: const = None
        (score >= 1000e4, rating - 2),  # elif score >= 1000e4: const = rating - 2
        (score >= 980e4, rating - 1 - (score - 980e4) / 20e4),
        (score >= 0, rating - (score - 950e4) / 30e4),
        (True, None),  # else: const = None
    ]

    return if_pattern(const_pattern)


def if_pattern(pattern: list[tuple[bool, _T]]) -> _T:
    return filter(lambda x: x[0], pattern).__next__()[1]

乍一看,文言文版本似乎比现代文版本更长一些,其实不然,如果我们将 if_pattern() 函数合并到 calc_const() 函数中,那么文言文版本就会更短一些。并且由于延长一个判断项,现代文版本需要增加两行,而文言文版本只需要一行,所以当判断链很长时,文言文版本会更短一些。

这个文言文版本的唯一缺点是,当进入 calc_const() 函数时,需要计算 const_pattern 里所有布尔表达式的值,因此在性能上可能会稍稍逊色于现代文版本。

2. 使用 dict.get() 方法取代 match-case 模式匹配

现代文:

def return_relation_between_vn(relation: str) -> str:
    match relation:
        case 'seq':
            return ' 续作 '
        case 'preq':
            return ' 前作 '
        case 'set':
            return '同一设定'
        case 'alt':
            return '替代版本'
        case 'char':
            return '角色客串'
        case 'side':
            return '支线故事'
        case 'par':
            return '主线剧情'
        case 'ser':
            return '同一系列'
        case 'fan':
            return 'FanDisc'
        case 'orig':
            return ' 原作 '
        case _:
            return '未知'

文言文:

def return_relation_between_vn(relation: str) -> str:
    return {
        'seq': ' 续作 ',  # Sequel
        'preq': ' 前作 ',  # Prequel
        'set': '同一设定',  # Same setting
        'alt': '替代版本',  # Alternative version
        'char': '角色客串',  # Shares characters
        'side': '支线故事',  # Side story
        'par': '主线剧情',  # Parent story
        'ser': '同一系列',  # Same series
        'fan': 'FanDisc',  # Fandisc
        'orig': ' 原作 ',  # Original game
    }.get(relation, '未知')

由于 PEP 8 不允许我们将多个声明写在同一行上(PEP 8: E701 multiple statements on one line (colon)),故现代文版本将明显比文言文版本占据更多的行数。

在 Python 版本低于 3.10 的情况下,由于无法使用 match-case 模式匹配(PEP 634: Structural Pattern Matching),这将导致只能降级使用 if-elif-else 判断链进行链式判断。

同理,你也可以使用 dict.get() 方法执行一些函数,例如:

def extract(path: str) -> None:
    return {
        '.zip': pyzipper.ZipFile,
        '.7z': py7zr.SevenZipFile,
        '.rar': rarfile.RarFile,
    }.get(path.split('.')[-1], None).extractall(path)

甚至使用 lambda 表达式作为函数:

Numeric = Union[int, float, complex, np.number]

def calc(a: Numeric, b: Numeric, op: str) -> Optional[Numeric]:
    return {
        '+': lambda: a + b,
        '-': lambda: a - b,
        '*': lambda: a * b,
        '/': lambda: a / b,
        '%': lambda: a % b,
        '**': lambda: a ** b,
        '//': lambda: a // b,
    }.get(op, lambda: None)()

3. 使用 ... 方法取代 pass 语句

现代文:

try:
    a = 1 / 0
except ZeroDivisionError:
    pass

文言文:

try:
    a = 1 / 0
except ZeroDivisionError:
    ...

首先,这两段代码的效果是一模一样的。

那么,... 到底是个什么东西呢?可以试一试 type(...),你会发现它返回的是 <class 'ellipsis'>。这个东西的具体用途可以见 Built-in Constants - EllipsisWhat does the Ellipsis object do?

虽然看上去没有什么用,但其实 ... 相当于一个语气词,也是某键盘手的著名名言。你可以将其放到某些特定的 except 语句块中,表示你对抛出的这个异常十分无语。例如:

try:
    result = await get_user_best35(user_id)
except (
    socket.timeout,
    aiohttp.client_exceptions.ClientConnectionError,
    aiohttp.WSServerHandshakeError,
):
    ...

4. 将 if-else 块改为 if-else 三目运算符(伪)

现代文:

if a > 0:
    b = 1
else:
    b = -1

文言文:

b = 1 if a > 0 else -1

这个很多人应该都知道,就不赘述了。

这和三目运算符有点相似:

b = a > 0 ? 1 : -1

5. 利用 if-else 三目运算符处理序列

现代文:

msg = 'the fox jumped over the lazy dog'
new_msg = ''

for char in msg:
    if char == ' ':
        new_msg += ' '
    else:
        # 凯撒密码
        new_msg += chr((ord(char) - ord('a') + 1) % 26 + ord('a'))

文言文:

msg = 'the quick brown fox jumps over the lazy dog'
new_msg = ''.join(' ' if char == ' ' else chr((ord(char) - ord('a') + 1) % 26 + ord('a')) for char in msg)

为了叙述方便,我们把文言文里出现的 chr((ord(char) - ord('a') + 1) % 26 + ord('a')) 这个表达式称为 caesar(char),即:

msg = 'the quick brown fox jumps over the lazy dog'
new_msg = ''.join(' ' if char == ' ' else caesar(char) for char in msg)

需要注意的是,这里需要把 ' ' if char == ' ' else caesar(char) 看作一个整体,而不是把 caesar(char) for char in msg 看作一个整体。也就是说,这个复杂的表达式最终是一个生成器(由 for-in 语句制造),而不是一个表达式(if-else 三目运算符)。

另外,你也可以利用 map() 函数和 lambda 表达式写成以下的形式:

new_msg = ''.join(map(lambda char: ' ' if char == ' ' else caesar(char), msg))

6. 利用 inline 循环移除列表中的重复元素

现代文:

ls = [1, 1, 1, 2, 2, 3]
while 1 in ls:
    ls.remove(1)

文言文:

ls = [1, 1, 1, 2, 2, 3]
ls = [item for item in ls if item != 1]

有没有感兴趣的同学来一波性能分析?

7. 使用 locals() 取代 API 请求参数列表

现代文:

async def user_best(user, usercode, songname, songid, difficulty, withrecent, withsonginfo) -> dict:

    user_best_params = dict(
        user=user,
        usercode=usercode,
        songname=songname,
        songid=songid,
        difficulty=difficulty,
        withrecent=withrecent,
        withsonginfo=withsonginfo,
    )

    return await _request(Endpoint.V1.User.best, user_best_params)

文言文:

async def user_best(user, usercode, songname, songid, difficulty, withrecent, withsonginfo) -> dict: return await _request(Endpoint.V1.User.best, locals())

这样的好处不言而喻——不用每次写 API 函数都要复读一遍参数列表。甚至非常简洁——可以一行写完这个函数。

但是缺点也很明显:在调用 locals() 函数之前,该函数内部不能定义任何变量,否则这个新变量将加入 API 的参数列表。

题外话

若考虑传递给 API 的参数可能为 None 的情况,那么必须在 _request() 函数中清理这些 None,如下:

clear_params = {k: v for k, v in params.items() if v is not None}

甚至可以将其包装为一个单独的 set_params() 函数:

def set_params(**kwargs): return {k: v for k, v in kwargs.items() if v is not None}

无论是文言文版本还是现代文版本,这都是不可避免的。

评论