前幾天,咱們Python貓交流學習羣
裏的 M 同窗提了個問題。這個問題挺有意思,經初次討論,咱們認爲它無解。安全
然而,我認爲它頗有價值,應該繼續思考怎麼解決,因此就在私密的知識星球上記錄了下來。數據結構
萬萬沒想到的是,在次日,有兩位同窗接連給出瞭解決方法!函數
由此,羣內出現了一輪熱烈的技術交流。學習
本文將相關的內容要點做了梳理,並由此引伸到更進一步的學習話題,但願對你有所幫助。ui
M 同窗的問題以下:編碼
打擾一下你們,請教一個問題,已知 list = ['A', 'B', 'C', 'D'] , 如何才能獲得以 list 中元素命名的新列表 A = [], B = [], C = [], D = [] 呢?人工智能
簡單理解,這個問題的意思是,將字符串內容做爲其它對象的變量名。spa
list 中的元素是字符串,此處的 ‘A’-‘D’ 是常量 ,而在要求的結果中,A-D 是變量 。翻譯
若是強行直接將常量當作變量使用,它會報錯:code
>>> 'A' = [] ...SyntaxError: can't assign to literal
報錯中的literal
指的是字面量
,這是計算機科學中常見的一個概念,用於表達源代碼中的固定值。 例如,整數、浮點數、字符串等基本類型,就是字面量。
字面量指的就是一個量自己,能夠理解爲一種原子性的實體,固然不能再被賦值了。
因此,取出的字符串內容,並不能直接用做變量名,須要另想辦法。
有初學者可能會想,list[0] = [] 行不行?固然不行,由於沒有出現 A 。那 A = list[0] ,接着 A = [] 呢?那也不行,由於這裏的 A 是你憑空定義出來的,而不是從已有條件中生成的。
當時,羣裏只有兩三個同窗參與了討論,咱們沒想到解決辦法。可是,我以爲這個題目頗有意思,值得玩味。
由於,若是能解決這個問題,那就意味着能夠不做預先定義,而是動態地生成變量名,這不只能減小給變量取名的麻煩,還實現了自動編碼!
能夠設想一下將來,人工智能在編寫代碼的時候,若是能根據已知條件,動態生成變量名,那編寫代碼的過程不就順利多了麼?(聽說,如今已經有人工智能能夠編寫代碼了,不知它在取變量名時,是用的什麼方法?)
最近,學習羣裏矇混進來了幾個打廣告的,爲此,我決定提升審覈門檻,例如,用羣裏的問題來做個考覈。
萬萬沒想到的是,第一個被考覈到的 Q 同窗,幾乎不假思索地就說出了一個解決上述問題的思路。而恰恰就是那麼巧 ,幾乎在同時,羣內的 J 同窗給出了另一個解決方法(他沒看到羣內的討論,而是看到了知識星球的記錄,才知道這個問題的)。
也就是說,前一晚還覺得無解的問題,在次日竟獲得了兩種不一樣的解決方法!
那麼,他們的答案是什麼呢?
# J 同窗的解答 >>> list1 = ['A', 'B', 'C', 'D'] >>> for i in list1: >>> globals()[i] = [] >>> A []
這個方法經過修改全局命名空間,巧妙地「定義」出了新的變量。globals() 方法取出來的是一個字典,字符串 ‘A’ 是其中一個鍵值(key),而這個鍵值偏偏是全局命名空間中的一個變量,這就實現了從常量到變量的轉化。
在數據結構層面上,空列表 [] 做爲一個值(value)跟它的字符串鍵值綁定在一塊兒,而在運用層面上,它做爲變量內容而跟變量名綁定在一塊兒。
看到這個回答的時候,我就忽然想起來了,上個月轉載過一篇《Python 動態賦值的陷阱》,講的正是動態地進行變量賦值 的問題啊!我彷佛只關注了 globals() 與 locals() 用法的區別,卻沒有真正地掌握它們的原初用途。
J 同窗說,他正是看了那篇文章,才學得了這個方法。這就有意思了,我分享了一個本身囫圇吞棗的知識,而後它被 J 同窗吸取掌握,最後反饋回來解決了個人難題。
我真切地感覺到了知識分享的魅力:知識在流動中得到生命,在碰撞中鋥亮色澤。
同時,我也真切地明白了一個互助的學習團體的好處:利人者也利己,互助者共同進步。
新進羣的 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拼接字符串的七種方式》。
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。
抽象一下最初的問題,它實際問的是「如何將字符串內容做爲其它對象的變量名」,更進一步地講是——「如何將常量轉化爲變量 」。
使用直接進行賦值的靜態方法,行不通。
兩位同窗提出的方法都是間接的動態方法:一個是動態地進行變量賦值,經過修改命名空間而植入變量;一個是動態地執行代碼,能夠說是經過「走後門」的方式,安插了變量。
兩種方法異曲同工,不論是白貓仍是黑貓,它們都抓到了老鼠。
這兩種方法已經給咱們帶來了頗有價值的啓發,同時,由於它們,羣內小夥伴們更是發散地討論一些相關聯的話題,例如:S 同窗提出了另外一種修改命名空間中變量的寫法、L 同窗提到了 eval() 的意義、eval() 與 exec() 的區別、我查到了爲何要慎用 eval() 、C 與 H 同窗提到了 eval() 的安全用法......
雖然,某些話題沒法在羣聊中充分展開,可是,這些話題知識的延展聯繫,大大地豐富了本文開頭的問題,這一個微小的問題,牽連出來了兩個大的知識體系。
最後,真得感謝羣內的這些愛學習的優秀的同志們!除了文中說起的,還有一些同窗也作了積極貢獻,你們都很給力!
相關連接:
eval()、exec()及其相關函數:https://www.tuicool.com/wx/vEbeumE
公衆號【Python貓】, 專一Python技術、數據科學和深度學習,力圖創造一個有趣又有用的學習分享平臺。本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、優質英文推薦與翻譯等等,歡迎關注哦。PS:後臺回覆「愛學習」,免費得到一份學習大禮包。