可以負責任的說,這篇文檔是windows10安裝pygraphviz中,在中文技術網站中最新的文檔,沒有之一。是自己完全結合各種問題,包括調試等,總結出來的。
問題來源:主要是可視化RvNN網絡的樹結構。
pygraphviz安裝時,我參考了博文http://www.myexception.cn/perl-python/2046792.html。但是,文章的解決方案已經失效。
已有博文存在的問題:windows下pygraphviz‑1.3.1‑cp34‑none‑win_amd64.whl文件無法適用於python3.6版本
站在巨人的肩膀上。
前述作者在上述博文鏈接中闡述,windows10下安裝pygraphviz要去http://www.lfd.uci.edu/~gohlke/pythonlibs/的地址下載python packages在windows平台上的安裝包。
但是,現在這個資源已經404.
其提供的過程就是:先安裝graphviz,然后使用如下命令安裝pygraphviz
pip install pygraphviz‑1.3.1‑cp27‑none‑win_amd64.whl
很遺憾,雖然網上沒有這些資源了,我在csdn上還是找到了如下版本:
可惜,python是3.6,使用這個,依舊安裝失敗。
因為只在python3.4版本上才能使用。
基於pygraphviz源碼包的方式安裝
先安裝graphviz-2.38.msi文件。是官網上的。
第一個問題:缺失vc14
去官網下載代碼。
https://github.com/pygraphviz/
直接python setup.py install,會報出找不到vc14版本的相關錯誤。
但是信息不夠詳細。於是,spyder帶參數install調試setup.py程序,看究竟是哪一步出了問題。
在spyder console鍵入:
然后一直跟蹤到出錯的位置:
調試pygraphviz的setup.py 並且帶參數”install” 調試,以后發現,其出錯函數在這里: 執行以后會提示:
那么對於這個函數,里面有這樣一段說明: Microsoft Visual C++ 14.0: Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) Microsoft Visual Studio 2017 (x86, x64, arm, arm64) Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) 也就是說:安裝對應的即可。 找到了相應的Microsoft Visual C++ 14.0 builder的生成器。但是由於我的電腦安裝了vs2015,因此產生沖突。所以,我升級visual studio為2017版本。下載界面在:
def msvc14_get_vc_env(plat_spec): """ Patched "distutils._msvccompiler._get_vc_env" for support extra compilers.
Set environment without use of "vcvarsall.bat".
Known supported compilers ------------------------- Microsoft Visual C++ 14.0: Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) Microsoft Visual Studio 2017 (x86, x64, arm, arm64) Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
Parameters ---------- plat_spec: str Target architecture.
Return ------ environment: dict """ # Try to get environment from vcvarsall.bat (Classical way) try: return get_unpatched(msvc14_get_vc_env)(plat_spec) except distutils.errors.DistutilsPlatformError: # Pass error Vcvarsall.bat is missing pass
# If error, try to set environment directly try: return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() except distutils.errors.DistutilsPlatformError as exc: _augment_exception(exc, 14.0) raise |
因此,下載Microsoft Visual C++ 14.0並且安裝。但是提示Visual studio2015版本和它不兼容。於是,又升級2015版本到2017版本,然后再執行這個程序。成功。這個程序我是從csdn上下載的,后續會放到本文末尾的附件鏈接當中。
第二個問題:缺失頭文件
pygraphviz/graphviz_wrap.c(2987): fatal error C1083: Cannot open include file: 'graphviz/cgraph.h': No such file or directory
error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86_amd64\\cl.exe' failed with exit status 2
此時,修改setup.py文件,添加如下代碼。這是因為pygraphviz要對一個graphviz_wrap.c文件進行編譯,因此就要設置頭文件和庫文件的包含路徑。
注意,實際lib是在release下。
第三個問題:cannot open input file 'cdt.lib'
雖然添加了頭文件路徑和庫文件路徑,但是會提示無法打開lib文件。

我們發現,錯誤是在執行running build_ext時,注意,ext就是extension的意思,也就是在python程序中調用編譯器編譯C語言的相關文件。
可以看到:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe對文件進行編譯的時候,里面已經添加了"-IC:\Program Files (x86)\Graphviz2.38\include"的頭文件路徑,解決了之前頭文件缺失的問題。
但是,在執行后續C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\link.exe
即鏈接所有庫,生成可執行程序的時候,卻提示無法打開cdt.lib文件,我們看到這里面的尋址路徑中並沒有之前修改setup.py文件中添加的那句話:
library_dirs=['C:\Program Files (x86)\Graphviz2.38\lib\release\lib']
但是,修改setup.py文件時添加的include_dirs=['C:\Program Files (x86)\Graphviz2.38\include']
確實生效了。
為什么在setup.py中添加library_dirs之后,再調用VC的鏈接程序,並沒有向指定庫文件路徑下尋找cdt.lib呢?
這是問題的根本所在。啟動調試進程,調試setup.py文件。在spyder的console端鍵入如下:
debugfile('H:/pygraphviz-master-wrong2/setup.py', args='install', wdir='H:/pygraphviz-master-wrong2')
跟蹤上圖中setup函數中的執行,尤其是對ext_modules的數據處理。跟蹤這個數據處理,就能找到哪里引用了include_dirs,哪里引用了library_dirs。
數據就是通道
我們需要找到輸出異常的位置,離它越近越好,就像逼近真相。
查看出錯時的輸出信息,有如下最關鍵的地方:
- running build_ext
- building 'pygraphviz._graphviz' extension
然后跟蹤程序執行,會在python的系統文件dist.py中有run_commands的函數
這里面就有running %s的輸出。那么我相信關鍵進程就在cmd_obj.run()中。正是這個執行過程,里面出錯。
所以,log.info處下斷點,當輸出running buid_ext之后,進入run的函數內部:run內部又會跟進到了build_ext.py文件的核心函數run中:
里面會對compiler編譯器進行設置,一直到執行self.build_extensions函數。
跟入該函數,
然后,在console端調試:
這里面的extension的命名“pygraphviz._graphviz”和setup.py文件中指定的命名是一致的。
也就是說,到目前為止:
已經找到了對setup.py文件中extension進行處理的核心代碼,繼續跟蹤就會知道library_dirs為什么會失效。
跟蹤進入cython_sources函數,就會看到:sources在第一個for循環中正是extension除去名稱之后的第一行。
我們在cython_sources中下斷點,直到sources是library-dirs的那一行。
可惜,直接一次循環就報出了本文所出現的link.exe鏈接的錯誤。
於是,直接跟入build_extension函數,就第一次循環就跟進去:如下,
我們輸出可以看到:
我們跟進compile函數,確實沒有給library_dirs進行賦值的選項。
繼續跟,跟完compile以后,發現compile只是進行了編譯操作。
會輸出:
ipdb> C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD "-IC:\Program Files (x86)\Graphviz2.38\include" -IC:\ProgramData\Anaconda3\include -IC:\ProgramData\Anaconda3\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /Tcpygraphviz/graphviz_wrap.c /Fobuild\temp.win-amd64-3.6\Release\pygraphviz/graphviz_wrap.obj
現在,繼續在build_extension中跟入:
這個就是鏈接過程。也就是在python程序中調用c編譯器編譯c目標程序時,會執行的函數。
我們跟進去。
ipdb> print (ext.library_dirs)
['C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib']
這也說明,確確實實是傳入進去了。我們進入這個函數:
一直跟到里面的一個link函數時,我們發現有問題!
也就是,傳入的library_dirs已經變成了
C:\Program Files (x86)\Graphviz2.38\lib
elease\lib
這是什么鬼!!!
里面有一個_fix_lib_args的操作,但是可以看到:
lib路徑已經錯了。本來應該是lib\\release\lib的,卻變成了libelease\lib了。
然后一直進入到:
self.spawn([self.linker] + ld_args)
我們可以輸出如下:
結果這里面輸出的ld_args竟然是:
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib'
千萬不要以為是正確的地址。正確的是C:\\Program Files (x86)\\Graphviz2.38\\lib\\release\\lib。
我們來觀察一下:
ld_args = (ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename])
然后ld_args是:
猛地一看,還是錯誤的。為什么在console端用print(ld_args )輸出的是:
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib'
這是因為,你沒有雙擊開。你雙擊開看到的是:
看到了嗎?這是windows下的換行符號\r。在控制台輸出的時候變成了\r。
在spyder中查看的時候,是換行。
而實際上:
目前為止,真正傳入的就是這么個玩意:
/LIBPATH:C:\Program Files (x86)\Graphviz2.38\lib
elease\lib
也就是說,是換行符號\r。
我們雙擊點開libopts也是一樣的。libopts是構成ld_args的重要組成。所以,后面的就不用跟了。
link程序必然出錯。因為找不到cdt.lib文件。所以,link.exe程序必然失敗,報出1181錯誤。
現在已經知道怎么改了,就是把setup.py中路徑的反斜杠,全部改為斜杠。但是我不能容忍不知道為什么傳遞的時候出錯。
為什么在某個環節C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
變成了C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
我們會發現是在調用link_shared_object的時候出的錯誤:
此時調試輸出的是:
當跟入函數以后:
所以,錯誤就是ext.library_dirs生成的過程出錯。
那么就要追蹤ext.library_dirs是如何依據setup.py文件中的extension項目生成的。
所以,關鍵是找到調用build_extension函數的地方,並且看是誰把值傳入了ext。
現在開始倒推:
def build_extensions(self): # First, sanity-check the 'extensions' list self.check_extensions_list(self.extensions) for ext in self.extensions: ext.sources = self.cython_sources(ext.sources, ext) self.build_extension(ext)
在進入這個函數的時候,輸出仍然是:
ipdb> print (ext.library_dirs) ['C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib']
所以,錯誤就在於生成self.extensions的過程中就已經注定了。
那么什么時候生成self.extensions的呢?
我們進入check_extensions_list函數,仍舊是輸出的是錯誤的地址。
所以,錯誤的形成不是在build_extensions中。
而是在調用build_extensions之前,然后生成了self.extensions,里面包含了'扭曲"的地址。
那么就是build_ext類的問題了,因為它就是那個self。
我們通過觀察build_ext的類結構,和快速的掃描代碼,找到了。
它的finalize_options函數中對self.extensions進行了設置。
這個時候,剛剛執行完下面代碼:
self.extensions = self.distribution.ext_modules
我們就在console端鍵入:
可以看出,已經錯了。所以,錯誤的形成不是在self.extensions的生成中。
而是在self.distribution.ext_modules的生成中。於是,進一步倒推。
在build_ext中,並不能找到self.distribution的相關代碼。我們發現,build_ext繼承的是Command類。Command類中有self.distribution。
所以,我們就要關注於build_ext這個類對象的生成。
這個時候,就要重頭開始調試,看哪個地方生成了build_ext這個類的對象引用。
- 調試到run_command函數內部,下斷點,當執行完log.info("running %s", command)以后輸出的是running build_ext,我們繼續下一步
-
log.info("running %s", command) cmd_obj = self.get_command_obj(command) cmd_obj.ensure_finalized() cmd_obj.run()
肯定是在上面的三行代碼中,完成了對build_ext類的生成,在這里完成了對self.distribution的操作。
首先跟入get_command_obj函數,感覺這個是最有可能對Command子類build_ext類對象的生成過程。因為從名字來看就是get_command_obj,並且注釋是:
"""Return the command object for 'command'. Normally this object is cached on a previous call to 'get_command_obj()'; if no command object for 'command' is in the cache, then we either create and return it (if 'create' is true) or return None. """
執行完第一行程序:
cmd_obj = self.command_obj.get(command)
由於之前是有:
self.extensions = self.distribution.ext_modules
所以我們直接在調試console輸入:
結果發現:名稱已經出錯了!!!
那么問題肯定是在self.command_obj.get中了!!!它在生成build_ext類對象的時候,初始化父類Command的distribution成員時,就已經把地址”扭曲“了!!!
最遺憾的是!!!這個self.command_obj.get壓根跟入不進去。很有可能代碼不是開源的,只是作為功能提供在鏈接庫當中。所以,這是一個bug。
什么bug???
嘔血調試發現python的bug
就是在setup.py文件中的extension中寫入library_dirs的時候,如果傳入如下的地址:
library_dirs=['C:\Program Files (x86)\Graphviz2.38\lib\release\lib'],
那么出於某種原因,python系統,會將\r看作換行符。因此,在轉變的過程中:
本來是要將\處理成\\的,但是這個\r被遺漏了。於是就產生了如下“扭曲”的地址
C:\\Program Files (x86)\\Graphviz2.38\\lib\release\\lib
最后,python中編譯(setup.py文件中extension指定)的c程序,就會出現找不到庫的問題。
於是乎,報告這個bug的同時,建議全部用斜杠/。
提交了bug,和處理結果(被老外狠狠的鄙視了一把,他不認為是bug)
回復:
msg3530 (view) Author: berker.peksag Date: 2018-08-20.16:26:32
remove
> In windows, we always give a path using '\' and python 3 can correctly dispose
> it just as we using '/' in Linux. But if you offer
> a path in windows with a '\' followed as 'r'. Everyting will goes wrong.
You need to use raw strings to avoid this. Replace
"C:\Program Files (x86)\Graphviz2.38\lib\release\lib"
with
r"C:\Program Files (x86)\Graphviz2.38\lib\release\lib"
See https://blog.lerner.co.il/avoiding-windows-backslash-problems-with-pythons-raw-strings/ for more details about raw strings.
This tracker is for issues with bugs.python.org. Please use Stack Overflow or python-list to ask usage questions.
老外不承認是bug。說,windows反斜杠的處理,要加r。嗯嗯。就這樣吧。
第四個問題:unresolved external symbol agwrite
解決方案見網址:
https://github.com/pygraphviz/pygraphviz/issues/58
這里面說用64位lib下的庫文件覆蓋到目錄lib下即可。也就是說這些unresolved是缺失了一些庫。可以看到是生成_graphviz.cp36_win_amd64.lib的時候出錯。這是因為安裝的graphviz是32位的模塊,缺失了很多庫文件。 這個庫,會放在本文附件里。 接着問題就能排除。
注意是將我附件中的\GraphViz_x64-master\graphviz-2.38_x64\lib放置到: graphviz的msi安裝程序之后的release的lib下面,而不是直接的lib下面C:\Program Files (x86)\Graphviz2.38\lib\release\lib。 但是,僅僅覆蓋lib文件時不夠的。否則在后續的測試程序中,import pygraphviz的時候回報錯提示,dll win32位的有問題。 所以,除了lib路徑需要特殊處理以外,需要把附件中GraphViz_x64-master\graphviz-2.38_x64中的 所有的內容全部覆蓋到C:\Program Files (x86)\Graphviz2.38中去。(所以,你直接換個名字吧。具體見文末的總結)
倒數第二個問題:unresolved external symbol PyIOBase_Type
仍然是下面的這個網址:
https://github.com/pygraphviz/pygraphviz/issues/58
里面提到:(在網絡海量信息中去偽存真)
最后在如下這個網址:
https://github.com/pygraphviz/pygraphviz/issues/74#issuecomment-238323405
中找到:
點擊進去(https://github.com/Kagami/pygraphviz/commit/fe442dc16accb629c3feaf157af75f67ccabbd6e)
就是一個補丁文件:
按照補丁文件對graphviz.i和pygraphviz/graphviz_wrap.c進行修改(我一行一行對着補丁改的。。。應該有依據補丁的自動化修改工具)。修改后的文件見附件。
最后一個問題,測試程序時輸出ValueError: Program neato not found in path。
import pygraphviz as pgv A=pgv.AGraph() A.add_edge(1,2) A.add_edge(2,3) A.add_edge(1,3) print(A.string()) # print to screen print("Wrote simple.dot") A.write('simple.dot') # write to simple.dot B=pgv.AGraph('simple.dot') # create a new graph from file B.layout() # layout with default (neato) B.draw('simple.png') # draw png print("Wrote simple.png")
上面是測試程序。通過搜集資料可知,是因為neato的問題。雙擊C:\Program Files (x86)\Graphviz2.38\bin下的neato,會報出異常。
如何解決,見文末總結。
附件
鏈接:https://pan.baidu.com/s/18VKkVj_CupmvFwihdHwH8Q 密碼:aqr7
總結,全部安裝過程
- 安裝graphviz-2.38.msi,這是官網提供的graphviz文件,安裝位置是:C:\Program Files (x86)\Graphviz2.38\
- 安裝vc14編譯器,在visualcppbuildtools_full中,前提是安裝了visual studio2017版本。
- pygraphviz-master的setup.py文件中按照前述指導,添加頭文件和庫文件目錄路徑。或者直接替換為我附件中提供的setup.py文件
- 如果此時直接python setup.py install,會提示很多的 error LNK2001: unresolved external symbol錯誤。這個時候,是因為安裝的graphviz2.38不是64位版本。這個時候,是因為缺少64位graphviz的相關lib文件。(而且截止到目前為止,我都沒有在graphviz中找到相關的明確指明為64位的安裝包。我提供的64位的目錄文件是從github上一個網頁提供的資源上下載的)這個時候,我們將C:\Program Files (x86)\Graphviz2.38中的Graphviz2.38直接改為Graphviz2.38_msi,表示這個目錄下是原始官網graphviz-2.38.msi安裝后的文件目錄。
- 然后將,附件中的H:\安裝包和補丁文件\GraphViz_x64-master\GraphViz_x64-master\graphviz-2.38_x64完全復制到C:\Program Files (x86)下,並且改名為Graphviz2.38,同時將lib目錄下的所有文件復制到lib\release\lib目錄中。
- 此時,再python setup.py install安裝會提示:LNK2001: unresolved external symbol PyIOBase_Type。按照前述指導,將附件中我按照前述網址指導的patch修改后的graphviz_wrap.c和graphviz.i文件替換到H:\pygraphviz-master\pygraphviz中去。
- 再次運行python setup.py install文件。成功。
- 運行前述測試程序。會提示:ValueError: Program neato not found in path。因為我提供的64位的graphviz中沒有這個neato文件。事實上,我查了一下,gvplugin_neato_layout.dll文件是有的。唯獨缺失neato.exe文件。我直接懷疑是https://github.com/mahkoCosmo/GraphViz_x64/的作者有意而為之。這種事情以前讀研的時候遇到,BAP(一個二進制分析平台)的源碼,我曾經一直在研究。后來全部源碼被下架。實話講,國內很多項目都是參照國外的開源代碼。如果國外搞技術封鎖,呵呵。。。。。。
- 如何解決?直接將C:\Program Files (x86)\Graphviz2.38_msi\bin寫入系統的path變量中。然后重啟spyder和Anaconda,即可。
- 得到最后的結果。結束。
- 有人會問,兩個版本的graphviz(即一個國外提供的免安裝版本,還一個是官網的版本)並存,會有影響嗎?我的解釋是,那就看pygraphviz是怎么寫的了。但是到目前為止,沒有遇到錯誤。編譯和鏈接pygraphviz中的graphviz_wrap.c文件的時候,需要國外某作者提供的免安裝版本的lib文件,官網的有缺失(或者說官網的64位版本能夠支持pygraphviz需求的,已經找不到資源)。所以就必須要用國外提供的免安裝版本。但是,國外免安裝版本中的neato文件又被蓄意刪去。這個時候,實際程序運行的過程中,pygraphviz會調用neato文件。因此,我們就把官網graphviz安裝包安裝后的路徑寫入到系統Path變量中。這樣就能調用其neato程序。
- 如果有人還不明白的話,我這樣解釋吧。假如你只是將GraphViz_x64-master中的lib文件修改到C:\Program Files (x86)\Graphviz2.38中的lib\release\lib目錄下,你編譯,鏈接,都不會有問題。但是,你再運行測試程序的時候,直接import pygraphviz都會出錯,會提示“DLL 不是有效的win32程序”(注意,win32不是說就是32位。64位也說是win32。)。為什么?這是因為,官網提供的graphviz,安裝在64位系統以后,也是32位的模塊。而目前壓根找不到64位的模塊。於是,只好用國外某作者可能蓄意刪除neato程序以后的graphviz_x64文件的全部內容。然后再將pygraphviz運行時可能缺失的neato文件用原始msi安裝后的neato文件進行補充。
- 所以,就是兩版本兼容。
- 總的來說,pygraphviz很強勢。但是為什么做的,連win10 64位下的安裝支持的都不夠好。原因未知。當哪天突然有人不想開源了,想做成商業化了,我估計這種情況會更多。正如本文最初提到的,連whl文件的地址都失效了。這不是技術封鎖,是什么。
- 最后,我的附件中提供pygraphviz-1.3.1-cp34-none-win_amd64.whl文件。僅限於win 64位系統,以及python 3.4上使用。我沒有實驗過。但是python 3.6版本是肯定不行。我相信大部分人不會只為了裝一個pygraphviz,而把python降低為3.4版本。