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

多返回值阵营九宫格

时间:2023-03-26 11:58:53 Python

在粘贴业务代码时通常只有一个返回值,无论是函数、方法还是宏。例如C语言中检查一个字符是否为阿拉伯数字的isdigit函数,只会返回yes(1)或no(0)#include#includeintmain(intargc,char*argv[]){charc='a';printf("isdigit('%c')是%d\n",c,isdigit(c));return0;}但是有时候如果一个函数或者方法,或者宏如果能返回多个值会更方便。例如,Python中的dict类型有一个实例方法get,用于检索dict实例中给定键对应的值。但是如果字典中存在值为None的key,那么get的返回值无法准确判断该key是否存在——除非你给它一个非None的默认值#-*-coding:utf8-*-deftest(d,key):print("d.get('{0}')is{1}\t'{0}'indis{2}".format(key,d.get(key),keyind))if__name__=='__main__':d={'foo':'bar','baz':None,}test(d,'foo')test(d,'baz')如此发展多年的编程语言,怎么可能做不到一次调用,多次返回值这种简单的事情。事实上,返回多个值的方法有很多种,我对其中的一些进行了分类。正统的,最常用的实现。以其内置函数truncate为例,其第一个返回值为第一个参数除以第二个参数的商,第二个返回值为对应的余数CL-USER>(truncate103)31ifCallingtruncate没有修饰,和其他只返回值的函数一样,只会得到一个返回值CL-USER>(let((q(truncate103)))(formatt"q=~D~%"q))q=3除非使用multiple-value-bind来捕获一个函数产生的所有返回值CL-USER>(multiple-value-bind(qr)(truncate103)(formatt"q=~方案的优点D~8Tr=~D~%"qr))q=3r=1CL就是很灵活。即使一个函数从返回单个值变成返回多个值,也不会导致原来调用该函数的所有地方都被修改——它对修改关闭,对扩展开放(错误)。Go的多返回值Go,踩着C语言的肩膀,函数也可以返回多值。io/ioutil包的官方文档中有很多示例。例如,如果使用ReadAll方法从string派生的流中读取所有内容,它会返回两个值packagemainimport("fmt""io/ioutil""log""strings")funcmain(){s:="你好,世界!"reader:=strings.NewReader(s)bytes,err:=ioutil.ReadAll(reader)iferr!=nil{log.Fatal(err)}fmt.Printf("bytesis%s",bytes)}这种方式,Go取代了C语言用返回值表示成功与否,再通过指针传递读取数据的风格。由于这种模式在有用的Go程序中随处可见,Gophers使用自定义键盘(错误地)与之前的多值绑定不同,如果函数或方法返回多个值,调用者必须捕获每个值,否则编译失败?trycattry_read_all_ignore_err.gopackagemainimport("fmt""io/ioutil""strings")funcmain(){s:="Hello,world!"reader:=strings.NewReader(s)bytes:=ioutil.ReadAll(reader)fmt.Printf("bytesis%s",bytes)}?trygobuildtry_read_all_ignore_err.go#命令行参数./try_read_all_ignore_err.go:12:8:assignmentmismatch:要求1个变量但是ioutil.ReadAll返回2个值也是合理的。毕竟,多返回值机制主要是用来向调用者传达错误原因——既然可能有错误,就必须进行检查。Python和Rust的解构就像CL的truncate函数一样。Python中的函数divmod也可以同时返回两个数相除的商和余数,一看也是返回多个值#-*-coding:utf8-*-if__name__=='__main__':q,r=divmod(10,3)print('q={}\tr={}'.format(q,r))但本质上,这是因为Python支持解构,divmod返回一个由以下内容组成的元组商和余数。这种做法和CL的真、深、多返回值的区别在于,如果你只想要divmod的第一个值,那么等号左边也要写成对应的结构#-*-编码:utf8-*-if__name__=='__main__':q,_=divmod(10,3)print('q={}'.format(q))可以在支持的语言中模拟多个返回值解构,如Rustfndivmod(a:u32,b:u32)->(u32,u32){(a/b,a%b)}fnmain(){let(q,r)=divmod(10,3);println!("q={}\tr={}",q,r);}这里Prolog归一化为Prolog,画风有点不一样。首先,Prolog既没有函数,也没有方法,也没有宏。在Prolog中,像length/2和member/2这样的东西被称为仿函数。它们在Prolog中是对列表的,就像CL的length和member是对列表的,Python的len函数和in运算符是对列表的,JavaScript的数组的length属性和indexOf方法……第二,Prolog不会“返回”“调用”functor的result”,它只是判断输入查询是否为真,并给出使查询为真的变量值。在第一个查询中,length/2的第二个参数是变量L,因此Prolog给出的L的值为4,使该查询为真;第二个查询没有变量,Prolog简单给出查询是否成立;在第三个查询中,Prolog给出了可以使查询有效的四个变量X的值。由于Prolog会给出查询中每个变量的值,因此您可以使用此功能来模拟多个返回值。例如,Prolog可以一次给出两个数的和、差和积。sumquotient的麻烦在于,即使你只想得到两个数的和,你也必须使用占位符来填充最后三个参数:jjcc(10,3,S,_,_,_)。作弊指针和全局变量虽然开头说了C语言的函数不能返回多个值,但是如果像上面的Prolog那样允许修改参数,C语言也可以做到。谁让它成为可能?指针的强大功能呢?例如stat(2)函数会将一个文件的信息填充到参数指向的结构体的内存中#include#includeintmain(intargc,char*argv[]){char*path="./try_stat.c";结构统计缓冲区;统计(路径,&buf);printf("inode的%s个数是%llu\n",path,buf.st_ino);return0;}查看man2stat可以知道structstat类型的内容很多,显然也是一种多返回值。同样的方法在Go中也可以使用,比如Scan方法用于将从数据库中读取的行的数据写入到目标数据结构中。最后,只要能被调用者感知,那么全局变量可能就是一个通用的多重返回值机制。例如C语言中的strtol函数,当任何数不能转换时,都会返回0并设置errno,所以检查errno是必须的步骤#include#include#includevoidtry_conversion(constchar*str){longnum=strtol(str,NULL,10);如果(errno==EINVAL||errno==ERANGE){字符消息[64];snprintf(消息,sizeof(消息),"strtol(\"%s\")",str);错误(消息);返回;}printf("strtol(\"%s\")是%ld\n",str,num);}intmain(intargc,char*argv[]){try_conversion("233");try_conversion("0");try_conversion("口齿不清");return0;}由于errno是一个全局变量,使用strtol完全有可能让读者忘记检查。相比之下,Go的strconv包中的函数将转换过程中的错误作为第二个参数返回给调用者,使用起来更安全。后记按照本文《代码写得不好,不要总觉得是自己抽象得不好》,代码长什么样完全由产品经理决定。但是产品经理怎么可能关心你用的技术是如何实现多重回报值的。综上所述,这个特性是没用的(错误的)。全文结束。