头图

Preface

As we all know, Python supports passing keyword arguments to functions. For example, Python's built-in function max accepts key to determine how to obtain the basis for comparing two parameters

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

It is certainly not a problem to customize a function that uses the feature of keyword parameters. For example, imitate the 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]

Then pass parameters to it in the form of keyword parameters

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

uphold the Zen of Python in There should be one-- and preferably only one --obvious way to do it. idea, I can even bells and whistles, the syntax to use keyword arguments string1 and string2 mass participation

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

But Yu doesn't conceal flaws, and Python's keyword parameters also have their shortcomings.

Python's shortcomings

The disadvantage of Python's keyword parameter feature is that the same parameter cannot be used at the same time:

  1. Has its own parameter name, and;
  2. Can be obtained from **kwargs ,

Two forms exist in the parameter list.

For example, we all know that Python has a well-known third-party library called requests, which provides for the development initiate HTTP requests. The instance method request its class requests.Session has a parameter list of up to 16 parameters that people can't help but use Long Parameter List to reconstruct it. (You can move to the request method document observe)

For ease of use, the author of requests intimately provides requests.request , so that only a simple function call is required

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

requests.request function supports the requests.Session#request (please allow me to borrow Ruby's writing for instance methods). All of this is done by declaring **kwargs variables in the parameter list and passing parameters to the latter with the same syntax in the function body. Achieved. (You can move to request function source code observe)

The disadvantage of this is that requests.request function loses a lot of information. To know kwargs in 0611545904b6bf, you must:

  1. To know requests.request how to requests.Session#request in parameter passing - will kwargs fully expanded is passed in the simplest case;
  2. Look requests.Session#request the parameter list of method to exclude 0611545904b71f and url which parameters are left.

If you want to requests.request use their own parameter name in the parameter list (for example params , data , json , etc.), then calls requests.Session#request becomes cumbersome up, it had to be written

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

The form of human beings is indeed a repeater.

For an elegant solution, you can refer to Common Lisp next door.

The advantages of Common Lisp

Common Lisp first appeared in 1984, 7 years before Python's 1991. But it is understood features of Python keyword arguments borrowed from Modula-3, rather than all origins Lisp. The keyword parameter characteristics in Common Lisp are quite different from those in Python. For example, according to the official Python manual, **kwargs are only extra keyword parameters in 0611545904b7a1

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

In Common Lisp, **kwargs corresponds to &rest args , which must be placed before the keyword parameter (ie on the left), and according to CLHS "A specifier for a rest parameter" , args contains all unprocessed The parameter-also contains the keyword parameter after it

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

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

If I have another function with foobar , then I can easily pass all the parameters 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)  ;; 返回值为(1 (:K1 2 :K2 3) 2 3)

Even if foobaz are more keyword parameters supported foobar , it can be handled easily, because Common Lisp supports passing a special keyword parameter :allow-other-keys to the called function.

(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)

Back to the HTTP client example. In Common Lisp, I generally use drakma this third-party libraries to initiate HTTP requests, it derived a http-request function, usage and requests.request almost

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

If I want to encapsulate a function http-get that makes GET requests conveniently based on it, I can write it like this

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

If I want to directly expose part of the keyword parameters supported by http-get in the parameter list of http-request

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

Furthermore, if I want to http-get support parsing Content-Type is application/json response to the results, it can also be written

(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))))

As Dio Common Lisp, it easily did what we couldn't do.

Digression

Once upon a time, Python programmers would still talk about There should be one-- and preferably only one --obvious way to do it. in the Zen of Python, but in fact, Python has a variety of ways to write the parameters of a function. Even in the process of writing this article, I learned that in the original Python parameter list, you can write / to make the parameters on the left side become positional-only parameters.

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


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

read the original text


用户bPGfS
169 声望3.7k 粉丝