用Python擴展VBScript以及Office VBA
VBA用戶常常苦於VBA的第三方庫不夠豐富。比如,在Excel里抓一些網頁進行分析就不是那麼太方便。而Python的第三方庫就要豐富的多,而且使用方便。本文就介紹如何安全的將Python引入VBA環境中。方法非常簡單,值得一試。=========================================================================================================================================
在如何實現 C/C++ 與 Python 的通信? 這篇文章的最後,介紹了所謂『Python與Excel混合編程』的問題。值得提醒的是,該文章所介紹的方法只是展示了技術上的可能性,實際上並不實用。實際上,實現VBA與Python的混合編程,既不需要Cython,更不需要Visual Studio。pywin32 項目為此已經提供了足夠的支持,只需要在Python基礎上安裝一個不到10M的擴展包即可。
1. 安裝pywin32
安裝好Python後,在此處 下載和你Python對應的pywin32二進位安裝包。比如,你的Python是32位的Python 2.7.12,則下載pywin32-220.win32-py2.7.exe;又如你的Python是64位的Python 3.5.2,則下載pywin32-220.win-amd64-py3.5.exe。注意,這裡討論的是Python的32位/64位,而不是Windows的32位/64位。
安裝過程非常簡單,一路Next即可。
2.創建COM伺服器
『COM伺服器』對於很多讀者可能有點陌生。這裡的COM不是網址里的那個.com,也不是串口,而是微軟開發的一種二進位代碼共享的技術。COM的初衷之一就是讓各種語言編寫的應用程序以一種統一的介面進行通信。本篇文章討論的Python和VBS/VBA混合編程,使用的就是COM技術。由於我們的目標是用Python擴展VBS/VBA,換句話說,就是Python給VBS/VBA提供服務。所以需要用Python創建伺服器,VBS/VBA是客戶端。
下面舉一個例子說明。比如說,我們有一個普通的Python類:
class PyHello:n def __init__(self):n passnn def SayHello(self):n return Hello Python!n n def DoAdd(self,a,b):n return a + bnnif __name__==__main__:n a = PyHello()n print a.SayHello()n print a.DoAdd(3,4)n
功能非常簡單,也無需多解釋。
下面在這個類的基礎上做一個COM伺服器,需要做的有三件事:
- 給這個COM伺服器起一個名字
- 指定哪些函數對於客戶端是可見的
- 安裝COM伺服器
做1和2,只需要在原來類的基礎上添加特定名稱的類成員變數。為了保持PyHello這個類的純潔性,我們不在它的基礎之上改,而是繼承這個類。完整的COM伺服器代碼如下:
class PyHello:nn def __init__(self):n passnn def SayHello(self):n return Hello Python!n n def DoAdd(self,a,b):n return a + bn nclass _WrapPyHello(PyHello):n _reg_clsid_ = {4ae5ed1d-c378-4da1-9816-5a038112deaa}n _reg_progid_ = "Python.PyHello" n _public_methods_ = [SayHello,DoAdd]nnif __name__==__main__:n import win32com.server.registern win32com.server.register.UseCommandLine(_WrapPyHello)n
我們看到代碼里創建了一個類_WrapPyHello(名字隨便起),它繼承了PyHello。在這個新的類裡面,有3個類變數。
第一個_reg_clsid_是COM伺服器的Class ID。這個字元串必須是『全球唯一』的,對,你沒有看錯,不是程序的全局唯一,也不是電腦里的唯一,而是全世界唯一。這個字元串的格式叫 GUID ,唯一的代表了你這個COM伺服器。產生它的方法有很多種。如果你能上網,就訪問https://www.guidgen.com/,得到一個GUID複製到你的程序里。或者,打開Python交互模式,用pythoncom產生一個GUID:
>>> import pythoncomn>>> print pythoncom.CreateGuid()n{9B1829A1-4F3F-4D44-89D4-5CAB282F33A7}n
總之,不要抄上面代碼里那個4ae5ed1d-...就對了。
第二個_reg_progid_是COM伺服器的Program ID,這是一個人類可讀的名字。要求可讀性好一點就行了。一般來說就起你自己產品的名稱。例如,Excel的Program ID叫做 Excel.Application。
名字起好了,接下來指定那些函數被導出。把這些函數的名字放到一個List裡面,提供給_public_methods_就可以了。
__main__的這部分是pywin32提供的便捷函數,負責把_WrapPyHello安裝到系統里,也負責卸載這個COM伺服器。
我們把這段代碼保存為PyHello.py,然後在命令行執行:
C:UsersAdministrator> PyHello.pyn
在Windows Vista以上版本的Windows中,可能會彈出用戶賬戶控制提示框,選擇【是】。
出現提示 Registered: Python.PyHello 就說明成功了
如果想卸載這個COM伺服器,運行
C:UsersAdministrator> PyHello.py --unregistern
3.在VBS/VBA中使用COM伺服器
無論在獨立的VBScript里,還是在Office VBA里,使用COM伺服器的方法都是一樣的——即使用VBS/VBA內建的CreateObject方法,傳入Program ID,就得到一個Python類的『實例』。COM技術隱藏了跨語言的技術細節。得到實例後,可以用VBS/VBA的成員操作符.使用實例所導出的函數。
寫一個VBS測試一下。複製以下代碼,保存為test.vbs(任何位置),雙擊執行。
Set inst = CreateObject("Python.PyHello")nmsgbox inst.SayHello()nmsgbox inst.DoAdd(1,3)n
當然,在Excel里也可以如法炮製:
如果對Python程序不滿意,可以隨時修改而不必每次都重新安裝COM伺服器。注意不要移動Python程序的文件位置,否則就會出現類似『找不到對象』的虐心對話框。4. 類型轉換
簡單地說,Python和VBS/VBA里的數字(包括整數和浮點數)和字元串是通用的。Python的list會轉換為VBS/VBA的Variant Array,可以像VBS/VBA的Array類型一樣使用。需要注意的是,如果將Python的tuple返回給VBS/VBA,那麼後者只認可tuple第一個元素的值。
其他類型,如dict,class,class instance,無法從Python類型轉換為VBS/VBA類型。我們可以在Python里儘可能多的實現功能,而把VBS/VBA當做一個橋樑。
5. 發布COM伺服器
如果別人的電腦里沒有安裝Python,那麼我們就需要把Python代碼編譯為二進位供他人使用。py2exe對Python的COM伺服器提供了良好的支持。安裝py2exe,請閱讀如何發布你的Python應用程序。
在PyHello.py同一目錄下建立setup.py,內容為:from distutils.core import setupnimport py2exennsetup(com_server=["PyHello"],n zipfile = None,n options = {n py2exe : { n "bundle_files": 1,n "dll_excludes": ["MSVCP90.dll","w9xpopen.exe"]n }n }n)n
然後運行
python setup.py py2exen
即在dist目錄下得到PyHello.exe和PyHello.dll。發布任何一個文件均可以。安裝和卸載PyHello.exe的方法是
PyHello.exe /registernPyHello.exe /unregistern
安裝和卸載PyHello.dll的方法是
regsvr32 PyHello.dllnregsvr32 /u PyHello.dlln
安裝好二進位文件後,不能移動它們的位置,否則就找不到對象了。
推薦閱讀:
※Python進階:理解Python中的非同步IO和協程(Coroutine),並應用在爬蟲中
※為什麼Python裡面的locals()是只讀的
※強烈推薦 | 數據分析師的必讀書單
※用python-pandas作圖矩陣
※Python進階課程筆記(四)
TAG:Python | VisualBasic | VBA |