关于后端:这方面Python还是比Lisp略逊一筹

35次阅读

共计 3893 个字符,预计需要花费 10 分钟才能阅读完成。

序言

家喻户晓,Python 反对向函数传递关键字参数。比方 Python 的内置函数 max 就承受名为 key 的关键字参数,以决定如何获取比拟两个参数时的根据

max({'v': 1}, {'v': 3}, {'v': 2}, key=lambda o: o['v'])  # 返回值为{'v': 3}

自定义一个使用了关键字参数个性的函数当然也不在话下。例如模拟一下 Common Lisp 中的函数string-equal

def string_equal(string1, string2, *, start1=None, end1=None, start2=None, end2=None):
    if not start1:
        start1 = 0
    if not end1:
        end1 = len(string1) - 1
    if not start2:
        start2 = 0
    if not end2:
        end2 = len(string2) - 1
    return string1[start1:end1 + 1] == string2[start2:end2 + 1]

再以关键字参数的模式向它传参

string_equal("Hello, world!", "ello", start1=1, end1=4)  # 返回值为 True

秉承 Python 之禅中的 There should be one-- and preferably only one --obvious way to do it. 理念, 我甚至能够花里胡哨地、用关键字参数的语法向 string1string2传参

string_equal(string1='Goodbye, world!', string2='ello')  # 返回值为 False

但瑜不掩瑕,Python 的关键字参数也有其有余。

Python 的有余

Python 的关键字参数个性的毛病在于,同一个参数无奈同时以:

  1. 具备本身的参数名,以及;
  2. 能够从 **kwargs 中获得,

两种状态存在于参数列表中。

举个例子,咱们都晓得 Python 有一个出名的第三方库叫做 requests,提供了 用于开发爬虫牢底坐穿的 发动 HTTP 申请的性能。它的类 requests.Session 的实例办法 request 有着让人忍不住使用 Long Parameter List 对其重构的、长达 16 个参数的参数列表。(你能够移步 request 办法的文档观摩)

为了便于应用,requests 的作者贴心地提供了requests.request,这样只须要一次简略的函数调用即可

requests.request('GET', 'http://example.com')

requests.request函数反对与 requests.Session#request(请容许我借用 Ruby 对于实例办法的写法)雷同的参数列表,这一切都是通过在参数列表中申明**kwargs 变量,并在函数体中用雷同的语法向后者传参来实现的。(你能够移步 request 函数的源代码观摩)

这样的缺点在于,requests.request函数的参数列表失落了大量的信息。要想晓得使用者能往 kwargs 中传入什么参数,必须:

  1. 先晓得 requests.request 是如何往 requests.Session#request 中传参的——将 kwargs 齐全开展传入是最简略的状况;
  2. 再查看 requests.Session#request 的参数列表中排除掉 methodurl的局部剩下哪些参数。

如果想在 requests.request 的参数列表中应用参数本身的名字(例如 paramsdatajson 等),那么调用 requests.Session#request 则变得繁琐起来,不得不写成

    with sessions.Session() as session:
        return session.request(method=method, url=url, params=params, data=data, json=data, **kwargs)

的模式——果然人类的实质是复读机。

一个优雅的解决方案,能够参考隔壁的 Common Lisp。

Common Lisp 的优越性

Common Lisp 第一次面世是在 1984 年,比 Python 的 1991 年要足足早了 7 年。但据悉,Python 的关键字参数个性借鉴自 Modula-3,而不是 万物起源的 Lisp。Common Lisp 中的关键字参数个性与 Python 有诸多不同。例如,依据 Python 官网手册中的说法,**kwargs 中只有多进去的关键字参数

If the form“**identifier”is present, it is initialized to a new ordered mapping receiving any excess keyword arguments

而在 Common Lisp 中,与 **kwargs 对应的是 &rest args,它必须搁置在关键字参数之前(即右边),并且依据 CLHS 中《A specifier for a rest parameter》的说法,args 中含有所有未经解决的参数——也蕴含了位于其后的关键字参数

(defun foobar (&rest args &key k1 k2)
  (list args k1 k2))

(foobar :k1 1 :k2 3)  ;; 返回值为((:K1 1 :K2 3) 1 3)

如果我还有另一个函数与 foobar 有着类似的参数列表,那么也能够轻松将所有参数传递给它

(defun foobaz (a &rest args &key k1 k2)
  (declare (ignorable k1 k2))
  (cons a
        (apply #'foobar args)))

(foobaz 1 :k1 2 :k2 3)  ;; 返回值为(1 (:K1 2 :K2 3) 2 3)

甚至于,即便在 foobaz 中反对的关键字参数比 foobar 要多,也能轻松地解决,因为 Common Lisp 反对向被调用的函数传入一个非凡的关键字参数 :allow-other-keys 即可

(defun foobaz (a &rest args &key k1 k2 my-key)
  (declare (ignorable k1 k2))
  (format t "my-key is ~S~%" my-key)
  (cons a
        (apply #'foobar :allow-other-keys t args)))

(foobaz 1 :k1 2 :k2 3 :my-key 4)  ;; 打印 my-key is 4,并返回(1 (:ALLOW-OTHER-KEYS T :K1 2 :K2 3 :MY-KEY 4) 2 3)

回到 HTTP 客户端的例子。在 Common Lisp 中我个别用 drakma 这个第三方库来发动 HTTP 申请,它导出了一个 http-request 函数,用法与 requests.request 差不多

(drakma:http-request "http://example.com" :method :get)

如果我想要基于它来封装一个便捷地收回 GET 申请的函数 http-get 的话,能够这样写

(defun http-get (uri &rest args)
  (apply #'drakma:http-request uri :method :get args))

如果我心愿在 http-get 的参数列表中间接暴露出一部分 http-request 反对的关键字参数的话,能够这样写

(defun http-get (uri &rest args &key content)
  (declare (ignorable content))
  (apply #'drakma:http-request uri :method :get args))

更进一步,如果我想在 http-get 中反对解析 Content-Typeapplication/json的响应后果的话,还能够这样写

(ql:quickload 'jonathan)
(ql:quickload 'str)
(defun http-get (uri &rest args &key content (decode-json t))
  ;; http-request 并不反对 decode-json 这个参数,但仍然能够将整个 args 传给它。(declare (ignorable content))
  (multiple-value-bind (bytes code headers)
      (apply #'drakma:http-request uri
             :allow-other-keys t
             :method :get
             args)
    (declare (ignorable code))
    (let ((content-type (cdr (assoc :content-type headers)))
          (text (flexi-streams:octets-to-string bytes)))
      (if (and decode-json
               (str:starts-with-p "application/json" content-type))
          (jonathan:parse text)
          text))))

不愧是Dio Common Lisp,轻易就做到了咱们做不到的事件。

题外话

曾几何时,Python 程序员还会津津有味于 Python 之禅中的 There should be one-- and preferably only one --obvious way to do it.,但其实 Python 光是在定义一个函数的参数方面就有形形色色的写法了。甚至在写这篇文章的过程中,我才晓得原来 Python 的参数列表中能够通过写上/ 来使其左侧的参数都成为 positional-only 的参数。

def foo1(a, b): pass
def foo2(a, /, b): pass


foo1(a=1, b=2)
foo2(a=1, b=2)  # 会抛出异样,因为 a 只能按地位来传参。

浏览原文

正文完
 0