当前位置: 首页 > 后端技术 > Python

在这方面,Python还是略逊于Lisp

时间:2023-03-26 14:50:27 Python

前言众所周知,Python支持向函数传递关键字参数。例如Python内置函数max接受一个名为key的关键字参数来决定如何获取比较两个参数的依据max({'v':1},{'v':3},{'v':2},key=lambdao:o['v'])#返回值为{'v':3}自定义一个使用关键字参数特性的函数当然不是问题。比如模仿CommonLisp中的函数string-equaldefstring_equal(string1,string2,*,start1=None,end1=None,start2=None,end2=None):ifnotstart1:start1=0ifnotend1:end1=len(string1)-1ifnotstart2:start2=0ifnotend2:end2=len(string2)-1returnstring1[start1:end1+1]==string2[start2:end2+1]再次以关键字的形式arguments给它传参数string_equal("Hello,world!","ello",start1=1,end1=4)#返回值为True,遵循Python之禅应该有一个——最好只有一个——显而易见的方法。概念,我什至可以使用关键字参数语法给string1和string2传递参数也有他们的缺点。Python的关键字参数特性的缺点是相同的参数不能:有自己的参数名,并且;可以从**kwargs获取,这两种形式存在于参数列表中。比如我们都知道Python有一个知名的第三方库requests,它提供了为开发爬虫发起HTTP请求的功能。其类requests.Session的实例方法request,参数列表多达16个,让人忍不住用LongParameterList来重构。(可以移步到request方法的文档中去观察)为了方便使用,requests的作者贴心的提供了requests.request,这样只需要一个简单的函数调用即可。requests.request('GET','http://example.com')requests.request函数支持和requests.Session#request一样的参数列表(允许我借用Ruby写实例方法的方式),都是在参数列表中声明了**kwargs变量,与在函数体中使用相同的语法向后者传递参数。(可以移步request函数源码观察)这样的缺陷就是requests.request函数的参数列表丢失了很多信息。如果你想知道用户可以向kwargs传入哪些参数,你必须首先知道requests.request是如何向requests.Session#request传递参数的——完全展开并传入kwargs是最简单的情况;然后检查requests.Session#request的参数列表排除了哪些参数留在了method和url的部分。如果你想在requests.request的参数列表中使用参数本身的名字(比如params、data、json等),那么调用requests.Session#request就变得很麻烦,不得不写成session.Session()assession:returnsession.request(method=method,url=url,params=params,data=data,json=data,**kwargs)的形式——果然,人的本质就是中继器。优雅的解决方案,请参考隔壁的CommonLisp。CommonLisp的优点CommonLisp最早出现于1984年,比1991年的Python早了七年。但据悉,Python的关键字参数特性是从Modula-3借来的,而不是万物起源的Lisp。CommonLisp中的关键字参数功能在很多方面都不同于Python。例如,根据Python官方手册,**kwargs中只有额外的关键字参数。to**kwargs是&restargs,必须放在关键字参数之前(也就是左边),根据CLHS中的《A specifier for a rest parameter》,args包含所有未处理的参数——还包括关键字参数之后(defunfoobar(&restargs&keyk1k2)(listargsk1k2))(foobar:k11:k23);;返回值是((:K11:K23)13)如果我有另一个函数具有与foobar类似的参数列表,因此您也可以轻松地将所有参数传递给它(defunfoobaz(a&restargs&keyk1k2)(declare(ignorablek1k2))(consa(apply#'foobarargs)))(foobaz1:k12:k23);;返回(1(:K12:K23)23)尽管foobaz支持的关键字参数比foobar可以轻松处理,因为CommonLisp支持将特殊关键字参数传递给被调用函数:allow-other-keys(defunfoobaz(a&restargs&keyk1k2my-key)(declare(ignorablek1k2))(formatt"my-keyis~S~%"my-key)(consa(apply#'foobar:allow-other-keystargs)))(foobaz1:k12:k23:my-key4);;打印my-key为4,然后返回(1(:ALLOW-OTHER-KEYST:K12:K23:MY-KEY4)23)回到HTTP客户端示例。在CommonLisp中,我一般使用第三方库drakma来发起HTTP请求。它导出了一个http-request函数,它在用法上类似于requests.request(drakma:http-request"http://example.com":method:get)如果我想封装一个函数http-get使一个基于它方便的GET请求,我可以这样写(defunhttp-get(uri&restargs)(apply#'drakma:http-requesturi:method:getargs))http-get的参数列表中http-request支持的参数,我可以这样写(defunhttp-get(uri&restargs&keycontent)(declare(ignorablecontent))(apply#'drakma:http-requesturi:method:getargs))更进一步,如果我想在http-get中支持解析Content-Type为application/json的响应结果,也可以写成(ql:quickload'jonathan)(ql:quickload'str)(defunhttp-get(uri&restargs&keycontent(decode-jsont));;http-request不支持decode-json参数,但是仍然可以传整个args给它。(声明(可忽略的内容))(多值绑定(字节代码标头)(应用#'drakma:http-requesturi:allow-other-keyst:method:getargs)(声明(可忽略的代码))(让((content-type(cdr(assoc:content-typeheaders)))(text(flexi-streams:octets-to-stringbytes)))(if(anddecode-json(str:starts-with-p"application/json"content-type))(jonathan:parsetext)text))))正如DioCommonLisp所期望的那样,它很容易做到我们做不到的事情。题外话曾几何时,Python程序员会谈论应该有一种——最好只有一种——显而易见的方法来做到这一点。在Python的禅宗中。但实际上,Python只是定义一个函数的参数。有各种各样的写法。甚至在写这篇文章的过程中,我了解到在原来的Python参数列表中,可以写/来让左边的参数都是positional-only参数。deffoo1(a,b):passdeffoo2(a,/,b):passfoo1(a=1,b=2)foo2(a=1,b=2)#会抛出异常,因为a只能被position来传参考。阅读原文