前几天, 我们 Python 猫交流学习群 里的 M 同学提了个问题. 这个问题挺有意思, 经初次讨论, 我们认为它无解.
然而, 我认为它很有价值, 应该继续思考怎么解决, 所以就在私密的知识星球上记录了下来.
万万没想到的是, 在第二天, 有两位同学接连给出了解决方法!
由此, 群内出现了一轮热烈的技术交流.
本文将相关的内容要点作了梳理, 并由此引申到更进一步的学习话题, 希望对你有所帮助.
1, 如何动态生成变量名?
M 同学的问题如下:
打扰一下大家, 请教一个问题, 已知 list = ['A', 'B', 'C', 'D'] , 如何才能得到以 list 中元素命名的新列表 A = [], B = [], C = [], D = [] 呢?
简单理解, 这个问题的意思是, 将字符串内容作为其它对象的变量名.
list 中的元素是字符串, 此处的'A'-'D' 是常量 , 而在要求的结果中, A-D 是变量 .
如果强行直接将常量当做变量使用, 它会报错:
- >>> 'A' = []
- ...SyntaxError: can't assign to literal
报错中的 literal 指的是字面量 , 这是计算机科学中常见的一个概念, 用于表达源代码中的固定值. 例如, 整数, 浮点数, 字符串等基本类型, 就是字面量.
字面量指的就是一个量本身, 可以理解为一种原子性的实体, 当然不能再被赋值了.
所以, 取出的字符串内容, 并不能直接用作变量名, 需要另想办法.
有初学者可能会想, list[0] = [] 行不行? 当然不行, 因为没有出现 A . 那 A = list[0] , 接着 A = [] 呢? 那也不行, 因为这里的 A 是你凭空定义出来的, 而不是从已有条件中生成的.
当时, 群里只有两三个同学参与了讨论, 我们没想到解决办法. 但是, 我觉得这个题目很有意思, 值得玩味.
因为, 如果能解决这个问题, 那就意味着可以不作预先定义, 而是动态地生成变量名, 这不仅能减少给变量取名的麻烦, 还实现了自动编码!
可以设想一下未来, 人工智能在编写代码的时候, 如果能根据已知条件, 动态生成变量名, 那编写代码的过程不就顺利多了么?(据说, 现在已经有人工智能可以编写代码了, 不知它在取变量名时, 是用的什么方法?)
2, 办法总是有的
最近, 学习群里蒙混进来了几个打广告的, 为此, 我决定提高审核门槛, 例如, 用群里的问题来作个考核.
万万没想到的是, 第一个被考核到的 Q 同学, 几乎不假思索地就说出了一个解决上述问题的思路. 而偏偏就是那么巧 , 几乎在同时, 群内的 J 同学给出了另外一个解决方法(他没看到群内的讨论, 而是看到了知识星球的记录, 才知道这个问题的).
也就是说, 前一晚还以为无解的问题, 在第二天竟得到了两种不同的解决方法!
那么, 他们的答案是什么呢?
- # J 同学的解答
- >>> list1 = ['A', 'B', 'C', 'D']
- >>> for i in list1:
- >>> globals()[i] = []
- >>> A
- []
这个方法通过修改全局命名空间, 巧妙地 "定义" 出了新的变量. globals() 方法取出来的是一个字典, 字符串'A' 是其中一个键值(key), 而这个键值恰恰是全局命名空间中的一个变量, 这就实现了从常量到变量的转化.
在数据结构层面上, 空列表 [] 作为一个值 (value) 跟它的字符串键值绑定在一起, 而在运用层面上, 它作为变量内容而跟变量名绑定在一起.
看到这个回答的时候, 我就突然想起来了, 上个月转载过一篇《Python 动态赋值的陷阱 https://mp.weixin.qq.com/s/f9BBe2W1X1p7NcHg665I4A 》, 讲的正是动态地进行变量赋值 的问题啊! 我似乎只关注了 globals() 与 locals() 用法的区别, 却没有真正地掌握它们的原初用途.
J 同学说, 他正是看了那篇文章, 才学得了这个方法. 这就有意思了, 我分享了一个自己囫囵吞枣的知识, 然后它被 J 同学吸收掌握, 最后反馈回来解决了我的难题.
我真切地感受到了知识分享的魅力: 知识在流动中获得生命, 在碰撞中锃亮色泽.
同时, 我也真切地明白了一个互助的学习团体的好处: 利人者也利己, 互助者共同进步.
3, 动态执行代码的方法
新进群的 Q 同学, 提供了一个不同的答案:
- # Q 同学的解答
- >>> list1 = ['A', 'B', 'C', 'D']
- >>> for i in list1:
- >>> exec(f"{i} = []")
- >>> A
- []
他的写法用到了 Python 3.6 才引入的 f-strings 特性, 事实上, 在较低版本中, 也是可以实现的, 只需要保证 exec() 方法接收的参数是包含了变量 i 的字符串即可, 例如这样写:
- # 以下代码可替换上例的第 4 行
- exec(i + "= []")
- # 或者:
- exec("{} = []".format(i))
- # 或者:
- exec(''.join([i,'= []']))
这几种写法的区别只是字符串拼接法的区别, 关于如何拼接字符串, 以及不同方法之间的区别, 可参看《详解 Python 拼接字符串的七种方式 https://mp.weixin.qq.com/s/Whrd6NiD4Y2Z-YSCy4XJ1w 》.
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() 的安全用法......
虽然, 某些话题无法在群聊中充分展开, 但是, 这些话题知识的延展联系, 大大地丰富了本文开头的问题, 这一个微小的问题, 牵连出来了两个大的知识体系.
最后, 真得感谢群内的这些爱学习的优秀的同志们! 除了文中提及的, 还有一些同学也做了积极贡献, 大家都很给力!
相关链接:
《Python 动态赋值的陷阱 https://mp.weixin.qq.com/s/f9BBe2W1X1p7NcHg665I4A 》
《详解 Python 拼接字符串的七种方式 https://mp.weixin.qq.com/s/Whrd6NiD4Y2Z-YSCy4XJ1w 》
eval(),exec()及其相关函数: https://www.tuicool.com/wx/vEbeumE
公众号[Python 猫] , 专注 Python 技术, 数据科学和深度学习, 力图创造一个有趣又有用的学习分享平台. 本号连载优质的系列文章, 有喵星哲学猫系列, Python 进阶系列, 好书推荐系列, 优质英文推荐与翻译等等, 欢迎关注哦. PS: 后台回复 "爱学习", 免费获得一份学习大礼包.
来源: https://juejin.im/post/5c8dc8bbf265da2dbf5f53ec