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