The preface

Python is known to support passing keyword arguments to functions. For example, Python’s built-in function Max accepts a keyword argument named key to determine how to compare two arguments

max({'v': 1}, {'v': 3}, {'v': 2}, key=lambda o: o['v'])  # return {'v': 3}
Copy the code

It’s also easy to customize a function that uses the keyword argument feature. For example, imitate the Common Lisp function 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]
Copy the code

Pass it parameters in the form of keyword arguments

string_equal("Hello, world!"."ello", start1=1, end1=4)  # returns True
Copy the code

Literature Review: There should be one; preferably only one; obvious way to do it. I can even pass parameters to string1 and string2 with the syntax for keyword arguments

string_equal(string1='Goodbye, world! ', string2='ello')  # return False
Copy the code

However, Python’s keyword arguments also have their drawbacks.

The shortage of the Python

The disadvantage of Python’s keyword arguments feature is that the same argument cannot be used at the same time:

  1. Has its own parameter name, and;
  2. Available from the**kwargsGet in,

Both forms exist in the parameter list.

For example, we all know that Python has a well-known third-party library called Requests, which provides the ability to make HTTP requests for developing crawlers. Its class Requests.Session instance method, Request, has an irresistible 16-parameter List refactoring with a Long Parameter List. (You can check out the request documentation.)

For ease of use, the authors of requests are thoughtful enough to provide requests. Request, which requires only one simple function call

requests.request('GET'.'http://example.com')
Copy the code

The requests. Request function supports the same argument list as the requests.Session#request (let me borrow Ruby’s approach to instance methods), all by declaring the **kwargs variable in the argument list. And pass the same syntax to the latter in the body of the function. (You can check out the source code of the request function.)

The drawback is that the argument list of the request.request function is missing a lot of information. To see what arguments a user can pass to kwargs, you must:

  1. First to knowrequests.requestHow torequests.Session#requestIn passing the parameter – willkwargsFully expanded passing in is the simplest case;
  2. Check it againrequests.Session#requestIs removed from the parameter listmethodandurlWhich parameters are left in the section.

If you want to use the names of the parameters themselves (e.g., params, data, JSON, etc.) in the argument list of requests. Request, then calling requests

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

Sure enough, human nature is a reread machine.

For an elegant solution, see Common Lisp next door.

The advantages of Common Lisp

Common Lisp first appeared in 1984, a full seven years before Python’s 1991 debut. However, Python’s keyword argument feature is known to be borrowed from Modula-3, not from Original-of-things Lisp. The keyword argument feature in Common Lisp differs from Python in a number of ways. For example, according to the official Python manual, **kwargs only has extra keyword arguments

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

In Common Lisp, the equivalent of **kwargs is &rest args, which must be placed before the keyword parameter (to the left), and according to A Specifier for A Rest Parameter in CLHS, Args contains all the unprocessed arguments — as well as any keyword arguments that follow

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

(foobar :k1 1 :k2 3)  ;; Return value ((:K1 1 :K2 3) 1 3)
Copy the code

If I had another function with a similar argument list to foobar, I could just as easily pass all the arguments to it

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

(foobaz 1 :k1 2 :k2 3)  ;; Return value (1 (:K1 2 :K2 3) 2 3)
Copy the code

Even if foobaz supports more keyword arguments than foobar, it can be handled easily, because Common Lisp supports passing a special keyword argument to the called function :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)  ;; (1 (: allow-other-keys T :K1 2 :K2 3: my-key 4) 2 3)
Copy the code

Back to the HTTP client example. Drakma, a third-party library I use in Common Lisp to make HTTP requests, exports an http-request function that is used in similar ways to requesting. Request

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

If I wanted to encapsulate a function http-get that makes GET requests easily based on it, I could write it like this

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

If I wanted to directly expose some of the http-request supported keyword arguments in the parameter list of HTTP-get, I could do so

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

Further, if I wanted to support parsing responses with content-type application/ JSON in HTTP-get, I could have written this

(ql:quickload 'jonathan)
(ql:quickload 'str)
(defun http-get (uri &rest args &key content (decode-json t))
  ;; Http-request does not support the parameter decode-json, but it can still pass the entire args to it.
  (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))))
Copy the code

Dio Common Lisp does what we can’t do easily.

digression

Literature Review: There should be one– and preferably only one– obvious way to do it., But Python has a lot of different ways of defining the parameters of a function. Even during the writing of this article, I learned that Python’s argument list can be made positional-only by writing /.

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


foo1(a=1, b=2)
foo2(a=1, b=2)  # will throw an exception because a can only be passed by position.
Copy the code

Read the original