无法嵌入互操作类型 请改用适用的接口_可微编程-自上而下的产品形态 4 Python互操作性...
原文:Python互操作性
如設(shè)計(jì)概述文檔所述,Python API互操作性是本項(xiàng)目的一個重要需求。雖然Swift被設(shè)計(jì)為與其他編程語言(及其運(yùn)行時)集成,但動態(tài)語言的本質(zhì)并不需要支持靜態(tài)語言所需的深度集成。特別是Python被設(shè)計(jì)為嵌入到其他應(yīng)用程序中,并且有一個簡單的C接口API。在我們的工作中,我們可以提供一個元嵌入,它允許Swift程序使用pythonapi,就像它們直接嵌入Python本身一樣。
為了實(shí)現(xiàn)這一點(diǎn),Swift腳本/程序只需將Python解釋器鏈接到其代碼中。我們的目標(biāo)從“我們?nèi)绾问褂胮ythonapi”變?yōu)椤拔覀內(nèi)绾巫宲ythonapi感覺自然、可訪問,以及如何從Swift代碼中容易獲得?”這不是一個小問題——Swift和Python在設(shè)計(jì)上有很大的不同,包括它們處理錯誤的方法,Python的超級動態(tài)特性,兩種語言在表層語法上的差異,以及不想“妥協(xié)”Swift程序員所期望的東西。我們還關(guān)心方便性和工效學(xué),并認(rèn)為這是不可接受的需要包裝生成器,如SWIG。
本文中的TL;DR是我們對這個方向感覺良好,并且認(rèn)為這項(xiàng)工作有一些有趣的方面:通過編寫與Python無關(guān)的語言特性,我們能夠與Swift編寫的庫實(shí)現(xiàn)良好的Python互操作性,這是非常好的。這允許其他社區(qū)組成相同的功能集,以便直接與對其他社區(qū)(如Javascript、Ruby等)很重要的其他動態(tài)語言集成。這項(xiàng)工作獨(dú)立于Swift對TensorFlow的自動求導(dǎo)和圖形程序提取特性,也很有意義。整體方案
我們的總體方法基于這樣的觀察:Python是強(qiáng)類型的,但與大多數(shù)動態(tài)類型語言一樣,它的類型系統(tǒng)是在運(yùn)行時強(qiáng)制執(zhí)行的。雖然有很多人試圖在其上改造靜態(tài)類型系統(tǒng)(例如mypy、pytype和其他類型),但它們依賴于不健全的類型系統(tǒng),因此它們不是我們可以依賴的完整解決方案,而且它們違背了許多使Python及其庫真正偉大的設(shè)計(jì)前提。
許多人認(rèn)為Swift是一種靜態(tài)類型的語言,因此得出結(jié)論:正確的解決方案是將Python的流體形式塞進(jìn)一個靜態(tài)定義的洞中。然而,其他人意識到Swift結(jié)合了強(qiáng)大的靜態(tài)類型系統(tǒng)的優(yōu)點(diǎn)和一個(經(jīng)常被低估的!)動態(tài)類型系統(tǒng)。我們沒有試圖強(qiáng)迫Python的動態(tài)類型系統(tǒng)成為它不存在的東西,而是選擇在Python存在的地方遇到它,并完全接受它的動態(tài)類型方法。
這樣做的最終結(jié)果是,我們可以獲得非常自然的Python體驗(yàn)—直接使用Swift代碼。下面是一個這樣的示例;注釋掉的代碼顯示了純Python語法以進(jìn)行比較:
如您所見,這里的語法對于Python程序員來說是可以立即理解的:主要區(qū)別在于Swift要求在使用之前聲明值(使用let或var),并且我們選擇將Python內(nèi)置函數(shù)(如import、type、slice等)放在Python下。命名空間(只是為了避免混淆全局范圍)。這是在努力讓Python感覺自然和熟悉,同時又不損害Swift語言的全局設(shè)計(jì)之間有意識地平衡的結(jié)果。
這一行是通過一個簡單的需求建立起來的:我們不應(yīng)該依賴任何特定于Python的編譯器或語言特性來實(shí)現(xiàn)Python互操作——它應(yīng)該完全實(shí)現(xiàn)為一個Swift庫。畢竟,雖然Python對機(jī)器學(xué)習(xí)社區(qū)極其重要,但也有其他動態(tài)語言(Javascript、Ruby等)在其他領(lǐng)域有很強(qiáng)的立足點(diǎn),我們不希望這些領(lǐng)域中的每一個都給Swift語言帶來無盡的復(fù)雜性。
您可以在Python.swift中看到我們橋接層的當(dāng)前實(shí)現(xiàn)。這是純Swift代碼,與未修改的Swift 4.1一起工作。這種方法的局限性
因?yàn)槲覀冞x擇在Swift中接受Python的動態(tài)特性,所以我們得到了動態(tài)語言帶來的優(yōu)點(diǎn)和缺點(diǎn)。特別是,許多Swift程序員已經(jīng)開始期待并依賴于驚人的代碼完成,并欣賞在編譯時讓編譯器捕捉錯誤和其他小錯誤的舒適性。相反,Python程序員沒有這些功能(相反,bug通常在運(yùn)行時被捕獲),而且由于我們接受Python的動態(tài)特性,因此在Swift中pythonapi的工作方式是相同的。
經(jīng)過與Swift社區(qū)的仔細(xì)考慮,很明顯這是一種平衡:Swift的哲學(xué)和價值體系有多少可以投射到Python庫生態(tài)系統(tǒng)上。。。不破壞那些關(guān)于Python及其庫的真實(shí)和美好的東西?最后,我們得出結(jié)論,以Python為中心的模型是最好的折衷方案:我們應(yīng)該接受這樣一個事實(shí):Python是一種動態(tài)語言,它永遠(yuǎn)不會也永遠(yuǎn)不會在靜態(tài)編譯時有完美的代碼完成和錯誤檢測。
必須注意的是,Python確實(shí)擁有現(xiàn)有的生產(chǎn)力工具,這些工具可以發(fā)現(xiàn)一些bug并提供良好的工具特性,如代碼完成。這些工具通常基于不合理的試探法,但仍然非常有用。我們希望這些工具所使用的啟發(fā)式方法能夠集成到Swift源代碼工具和IDE生態(tài)系統(tǒng)中,但是我們需要有人來幫助構(gòu)建這個系統(tǒng)。如果您有興趣,請聯(lián)系我們。工作原理
我們將Python的動態(tài)類型系統(tǒng)映射到一個名為PythonObject的靜態(tài)Swift類型中,并允許PythonObject在運(yùn)行時接受任何動態(tài)Python值(類似于Abadi等人的方法)。PythonObject與Python C綁定中使用的PyObject*直接對應(yīng),并且可以執(zhí)行Python值在Python中執(zhí)行的任何操作。例如,這就像您在Python中所期望的那樣:
因?yàn)槲覀儾幌肫茐腟wift的全局設(shè)計(jì),所以我們將所有Python行為都限制在涉及這個PythonObject類型的表達(dá)式中。這確保了普通Swift代碼的語義保持不變,即使它與Python值混合、匹配、接口和混合。基本互操作性
從Swift 4.0開始,通過現(xiàn)有的語言特性已經(jīng)可以直接實(shí)現(xiàn)合理的基本互操作性:我們簡單地將PythonObject定義為一個Swift結(jié)構(gòu),它包裝了一個私有的Swift PyReference類,允許Swift接管Python引用計(jì)數(shù)的責(zé)任:
類似地,我們可以根據(jù)現(xiàn)有的Python運(yùn)行時接口在PythonObject上實(shí)現(xiàn)func+(以及其他受支持的Python操作符)。我們的實(shí)現(xiàn)如下:
我們還使PythonObject符合序列和其他協(xié)議,允許這樣的代碼工作:
此外,由于PythonObject符合MutableCollection,因此您可以完全訪問集合的Swift api,包括map、filter、sort等函數(shù)。Swift值之間的轉(zhuǎn)換
既然Swift可以表示和操作Python值,那么在Int和Array<Float>等Swift原生類型和Python等價類型之間進(jìn)行轉(zhuǎn)換就變得非常重要。這是由PythonConvertible協(xié)議處理的,基本的Swift類型(如Int)符合該協(xié)議,而Swift集合類型(如Array和Dictionary)符合條件(當(dāng)它們的元素符合時)。這使得轉(zhuǎn)換自然地適合Swift模型。
例如,如果您知道需要Swift整數(shù),或者希望將Swift整數(shù)轉(zhuǎn)換為Python,則可以使用:
類似地,像數(shù)組這樣的聚合類型的工作方式完全相同:
這完全符合Swift程序員所期望的模型:可失敗的轉(zhuǎn)換被投射到可選結(jié)果中(就像“string to int”轉(zhuǎn)換一樣),提供Swift程序員所期望的安全性和可預(yù)測性。
最后,因?yàn)槟梢栽L問Python的全部功能,所以Python的所有常規(guī)反射功能也都可以直接使用,包括Python.type、Python.id、Python.dir和Python inspect模塊。互操作性挑戰(zhàn)
上面的支持是可能的,因?yàn)镾wift的設(shè)計(jì)旨在并理解類型的庫級語法擴(kuò)展性的目標(biāo)。我們也很幸運(yùn),Python和Swift對于表達(dá)式(操作符和函數(shù)/方法調(diào)用)共享非常相似的表層語法。也就是說,由于Swift 4.0語法擴(kuò)展性的限制和有意設(shè)計(jì)的差異,我們需要克服一些挑戰(zhàn)。動態(tài)成員查找
盡管Swift 4.0是一種通用的可擴(kuò)展語言,但原始成員查找并不是庫的可擴(kuò)展特性。具體地說,給定一個x.y形式的表達(dá)式,x類型無法控制在訪問成員y時發(fā)生的事情。如果x的類型靜態(tài)地聲明了一個名為y的成員,那么這個表達(dá)式將被解析,否則編譯器將拒絕它。
在Swift 4.0的約束下,我們構(gòu)建了一個綁定來解決這個問題。例如,根據(jù)Python的PyObject_GetAttrString和PyObject_SetAttrString實(shí)現(xiàn)成員訪問非常簡單。允許的代碼如下:
然而,我們可能都同意,這并不能實(shí)現(xiàn)我們的目標(biāo),即為使用Python值提供一個自然且符合人體工程學(xué)的界面!除此之外,它不提供使用Swift L值的任何功能:無法拼寫a.x+=1的等價值。這兩個問題在表現(xiàn)力上有很大的差距。
在與Swift社區(qū)討論之后,這個問題的解決方案是允許庫代碼實(shí)現(xiàn)回退掛鉤來處理失敗的成員查找。此特性存在于許多動態(tài)語言中,包括Objective-C,因此,我們提出并實(shí)現(xiàn)了SE-0195:引入用戶定義的“動態(tài)成員查找”類型,該類型允許靜態(tài)類型為未解析的查找提供回退處理程序。斯威夫特社區(qū)通過斯威夫特進(jìn)化過程對這一建議進(jìn)行了詳細(xì)討論,并最終被接受。從Swift 4.1開始它就一直在運(yùn)輸。
因此,我們的互操作性庫能夠?qū)崿F(xiàn)以下掛鉤:
這使得上述代碼可以簡單地表示為:
... 自然的a.x+=1語法的工作原理和我們期望的一樣。這顯示了一個巨大的好處,即能夠?qū)⒄Z言、其庫和應(yīng)用程序的整個堆棧一起進(jìn)化,以實(shí)現(xiàn)一個目標(biāo)。動態(tài)可調(diào)用類型
除了成員查找之外,在調(diào)用值時,我們還有一個類似的挑戰(zhàn)。動態(tài)語言通常有“可調(diào)用”值的概念,它可以接受任意簽名,但是Swift 4.1不支持這樣的東西。例如,從Swift 4.1開始,我們的互操作性庫能夠通過這樣的接口使用pythonapi:
//Python:a=np.arange(15).reforme(3,5)
設(shè)a=np.arange.call(with:15).resforme.call(with:3,5)
//Python:d=np.array([1,2,3],dtype=“i2”)
設(shè)d=np.array.call(使用:[6,7,8],kwargs:[(“dtype”,“i2”)])
雖然可以做到這一點(diǎn),但顯然并沒有實(shí)現(xiàn)我們的方便和符合人體工程學(xué)的目標(biāo)。
使用Swift社區(qū)和#2評估這個問題,我們發(fā)現(xiàn)Python和Swift同時支持命名參數(shù)和未命名參數(shù):命名參數(shù)作為字典傳入。同時,Smalltalk派生語言增加了一個額外的問題:方法引用是原子單元,它包括方法的基名稱和任何關(guān)鍵字參數(shù)。雖然與這種風(fēng)格的語言的互操作性對Python并不重要,但我們希望確保Swift不會被描繪成妨礙與Ruby、Squeak和其他SmallTalk派生語言進(jìn)行良好互操作的角落。
我們目前的提議已經(jīng)討論過,但是還沒有實(shí)現(xiàn)(需要Swift社區(qū)的最終批準(zhǔn)),就是引入一個新的@dynamicCallable屬性來表示一個類型(比如PythonObject)可以處理動態(tài)調(diào)用解析。@dynamicCallable特性已經(jīng)實(shí)現(xiàn)并在Python互操作模塊中可用。
我們認(rèn)為這是非常有說服力的,并且確實(shí)縮小了在這些情況下存在的剩余表現(xiàn)力和人體工程學(xué)差距。我們相信這個特性對于Ruby、Squeak和其他動態(tài)語言來說是一個很好的解決方案,同時也是一個普遍有用的Swift語言特性,可以應(yīng)用于其他Swift庫。異常處理與錯誤處理
Python的異常處理方法類似于C++和許多其他語言,其中任何表達(dá)式都可以在任何時候拋出異常,而調(diào)用方可以選擇獨(dú)立地處理它們(或不)。相反,Swift的錯誤處理方法使“可拋出性”成為方法API契約的一個顯式部分,并強(qiáng)制調(diào)用方處理(或至少承認(rèn))可以拋出錯誤。
這是兩種語言之間固有的差異,我們不想用語言擴(kuò)展來掩蓋這種差異。我們目前對此的解決方案基于這樣的觀察:即使任何函數(shù)調(diào)用都可能拋出,但大多數(shù)調(diào)用都不會。此外,鑒于Swift在語言中明確了錯誤處理,因此Swift程序員中的Python也應(yīng)該考慮錯誤在哪里可以拋出和捕獲。我們用一個明確的.throwing來做PythonObject。下面是一個例子:
當(dāng)然,這與快速錯誤處理提供的所有正常機(jī)制集成在一起,包括使用try的能力?如果您想處理錯誤,但不關(guān)心異常中包含的詳細(xì)信息。目前的執(zhí)行情況和現(xiàn)狀
如上所述,Python互操作性庫的當(dāng)前實(shí)現(xiàn)可在Python.swift文件的GitHub上獲得。在實(shí)踐中,我們發(fā)現(xiàn)它在許多用例中都能很好地工作。然而,我們需要繼續(xù)開發(fā)和解決的一些問題是:
Python切片比Swift的切片語法更通用。現(xiàn)在您可以通過Python.slice(a,b,c)函數(shù)完全訪問它。然而,我們應(yīng)該從Swift中連接到標(biāo)準(zhǔn)的a…b范圍語法中,考慮實(shí)現(xiàn)striding操作符作為對基本范圍語法的擴(kuò)展可能會很有趣。我們需要研究并確定用于Python類的子類化的正確模型。目前沒有辦法使PythonObject這樣的結(jié)構(gòu)與元組模式匹配一起工作,因此我們使用.tuple2這樣的投影屬性。如果這在實(shí)踐中成為一個問題,我們可以調(diào)查在Swift中添加這個,但是我們目前認(rèn)為這個問題不足以在短期內(nèi)得到解決。總結(jié)和結(jié)論
我們對這個方向感到滿意,并認(rèn)為這項(xiàng)工作有幾個有趣的方面:Swift編譯器或語言中沒有Python特定的更改,這很好。通過編寫與Python無關(guān)的語言特性,我們能夠通過用Swift編寫的庫實(shí)現(xiàn)良好的Python互操作性。我們相信其他社區(qū)將能夠組成相同的功能集,直接與對其他社區(qū)(如JavaScript、Ruby等)重要的動態(tài)語言(及其運(yùn)行時)集成。
這項(xiàng)工作的另一個有趣的方面是,Python支持完全獨(dú)立于我們作為Swift for TensorFlow的一部分構(gòu)建的其他TensorFlow和自動微分邏輯。這是對Swift生態(tài)系統(tǒng)的一個通常有用的擴(kuò)展,它可以獨(dú)立運(yùn)行,對于服務(wù)器端開發(fā)或任何其他希望與現(xiàn)有pythonapi交互的東西都很有用。
最后,在Swift for TensorFlow的上下文中指出一個重要的警告是很重要的:雖然可以直接調(diào)用任意Python API,但是為您自動構(gòu)建TensorFlow圖的代碼分區(qū)分析無法理解動態(tài)Python API調(diào)用。雖然通過Python互操作層直接為TensorFlow(sessions、Keras等)使用api在技術(shù)上是可行的,但我們在Swift中為TensorFlow構(gòu)建的編譯器分析和轉(zhuǎn)換并不會給它帶來好處。相反,我們需要發(fā)明自己的高級api,并從Keras和其他現(xiàn)有api中汲取靈感。有關(guān)詳細(xì)信息,請參閱圖程序抽象文檔。
若有收獲,就賞束稻谷吧
0 顆稻谷
總結(jié)
以上是生活随笔為你收集整理的无法嵌入互操作类型 请改用适用的接口_可微编程-自上而下的产品形态 4 Python互操作性...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 优先队列的数组实现(有序)
- 下一篇: 优先队列的链表实现