♚
1、如何动态生成变量名?
M同学的问题如下:
打扰一下大家,请教个问题,已知list=['A','B','C','D'],如何才能得到以list中元素命名的新列表A=[],B=[],C=[],D=[]呢?
简单理解,这个问题的意思是,将字符串内容作为其它对象的变量名。
list 中的元素是字符串,此处的 ‘A’-‘D’ 是常量 ,而在要求的结果中,A-D 是变量。
如果强行直接将常量当做变量使用,它会报错:
>>>'A'=[]
...SyntaxError:can'tassigntoliteral
报错中的literal 指的是字面量 ,这是计算机科学中常见的一个概念,用于表达源代码中的固定值。例如,整数、浮点数、字符串等基本类型,就是字面量。
字面量指的就是一个量本身,可以理解为一种原子性的实体,当然不能再被赋值了。
所以,取出的字符串内容,并不能直接用作变量名,需要另想办法。
有初学者可能会想,list[0]=[]行不行?当然不行,因为没有出现A。那A=list[0],接着A=[]呢?那也不行,因为这里的A是你凭空定义出来的,而不是从已有条件中生成的。
我觉得这个题目很有意思,值得玩味。因为,如果能解决这个问题,那就意味着可以不作预先定义,而是动态地生成变量名,这不仅能减少给变量取名的麻烦,还实现了自动编码!
可以设想一下未来,人工智能在编写代码的时候,如果能根据已知条件,动态生成变量名,那编写代码的过程不就顺利多了么?(据说,现在已经有人工智能可以编写代码了,不知它在取变量名时,是用的什么方法?)
2、办法总是有的
#J同学的解答
>>>list1=['A','B','C','D']
>>>foriinlist1:
>>>globals()[i]=[]
>>>A
[]
这个方法通过修改全局命名空间,巧妙地“定义”出了新的变量。globals()方法取出来的是一个字典,字符串‘A’是其中一个键值(key),而这个键值恰恰是全局命名空间中的一个变量,这就实现了从常量到变量的转化。
在数据结构层面上,空列表[]作为一个值(value)跟它的字符串键值绑定在一起,而在运用层面上,它作为变量内容而跟变量名绑定在一起。
看到这个回答的时候,我就突然想起来一篇文章,讲的正是动态地进行变量赋值的问题啊!我似乎只关注了globals()与locals()用法的区别,却没有真正地掌握它们的原初用途。
J同学说,他正是看了那篇文章,才学得了这个方法。这就有意思了,我分享了一个自己囫囵吞枣的知识,然后它被J同学吸收掌握,最后反馈回来解决了我的难题。
我真切地感受到了知识分享的魅力:知识在流动中获得生命,在碰撞中锃亮色泽。
同时,我也真切地明白了一个互助的学习团体的好处:利人者也利己,互助者共同进步。
3、动态执行代码的方法
Q同学提供了一个不同的答案:
#Q同学的解答
>>>list1=['A','B','C','D']
>>>foriinlist1:
>>>exec(f'{i}=[]')
>>>A
[]
他的写法用到了Python3.6才引入的f-strings特性,事实上,在较低版本中,也是可以实现的,只需要保证exec()方法接收的参数是包含了变量i的字符串即可,例如这样写:
#以下代码可替换上例的第4行
exec(i+'=[]')
#或者:
exec('{}=[]'.format(i))
#或者:
exec(''.join([i,'=[]']))
这几种写法的区别只是字符串拼接法的区别,关于如何拼接字符串,以及不同方法间的区别,参看《Python拼接字符串的七种方式》。
Q 同学这个答案的核心在于 exec() 方法,它是内置的,用途是执行储存在字符串或文件中的代码段。
它的基础用法如下:
>>>exec('x=1+2')
>>>x
3
#执行代码段
>>>s='''
>>>x=10
>>>y=20
>>>sum=x+y
>>>print(sum)
>>>'''
>>>exec(s)
30
看完了exec()的用法,我们再回来看Q同学的答案。for-循环中取出来的i是字符串,而拼接后的字符串经过exec()的处理,就获得了动态编写代码的效果。
也就是说,因为字符串常量的内容被当做有效代码而执行了,其中的'A'-'D'元素,就取得了新的身份,变成了最终的A-D变量名。
这个方法看起来很简单啊,可是exec()方法太生僻,直到Q同学提出,我们才醒悟过来。
注意:在Python3中,exec()是个内置方法;而在Python2中,exec是个语句(statement),另外有个execfile()方法,两者相合并,就成了Python3中的exec()方法。本文使用的是Python3。
4、总结
抽象一下最初的问题,它实际问的是“如何将字符串内容作为其它对象的变量名”,更进一步地讲是——“如何将常量转化为变量”。
使用直接进行赋值的静态方法,行不通。
两位同学提出的方法都是间接的动态方法:一个是动态地进行变量赋值,通过修改命名空间而植入变量;一个是动态地执行代码,可以说是通过“走后门”的方式,安插了变量。
两种方法殊途同归,不管是白猫还是黑猫,它们都抓到了老鼠。
这两种方法已经给我们带来了很有价值的启发,同时,因为它们,群内小伙伴们更是发散地讨论一些相关联的话题,例如:S同学提出了另一种修改命名空间中变量的写法、L同学提到了eval()的意义、eval()与exec()的区别、我查到了为什么要慎用eval()、C与H同学提到了eval()的安全用法……
虽然,某些话题无法在群聊中充分展开,但是,这些话题知识的延展联系,大大地丰富了本文开头的问题,这一个微小的问题,牵连出来了两个大的知识体系。