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:
- Has its own parameter name, and;
- Available from the
**kwargs
Get 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:
- First to know
requests.request
How torequests.Session#request
In passing the parameter – willkwargs
Fully expanded passing in is the simplest case; - Check it again
requests.Session#request
Is removed from the parameter listmethod
andurl
Which 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