自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python 程序員必知必會的開發(fā)者工具

開發(fā) 后端 開發(fā)工具
Python已經(jīng)演化出了一個廣泛的生態(tài)系統(tǒng),該生態(tài)系統(tǒng)能夠讓Python程序員的生活變得更加簡單,減少他們重復(fù)造輪的工作。同樣的理念也適用于工具開發(fā)者的工作,即便他們開發(fā)出的工具并沒有出現(xiàn)在最終的程序中。本文將介紹Python程序員必知必會的開發(fā)者工具。

Python已經(jīng)演化出了一個廣泛的生態(tài)系統(tǒng),該生態(tài)系統(tǒng)能夠讓Python程序員的生活變得更加簡單,減少他們重復(fù)造輪的工作。同樣的理念也適用于工具開發(fā)者的工作,即便他們開發(fā)出的工具并沒有出現(xiàn)在最終的程序中。本文將介紹Python程序員必知必會的開發(fā)者工具。

 

對于開發(fā)者來說,最實用的幫助莫過于幫助他們編寫代碼文檔了。pydoc模塊可以根據(jù)源代碼中的docstrings為任何可導(dǎo)入模塊生成格式良好的文檔。Python包含了兩個測試框架來自動測試代碼以及驗證代碼的正確性:1)doctest模塊,該模塊可以從源代碼或獨(dú)立文件的例子中抽取出測試用例。2)unittest模塊,該模塊是一個全功能的自動化測試框架,該框架提供了對測試準(zhǔn)備(test fixtures), 預(yù)定義測試集(predefined test suite)以及測試發(fā)現(xiàn)(test discovery)的支持。

trace模 塊可以監(jiān)控Python執(zhí)行程序的方式,同時生成一個報表來顯示程序的每一行執(zhí)行的次數(shù)。這些信息可以用來發(fā)現(xiàn)未被自動化測試集所覆蓋的程序執(zhí)行路徑,也 可以用來研究程序調(diào)用圖,進(jìn)而發(fā)現(xiàn)模塊之間的依賴關(guān)系。編寫并執(zhí)行測試可以發(fā)現(xiàn)絕大多數(shù)程序中的問題,Python使得debug工作變得更加簡單,這是 因為在大部分情況下,Python都能夠?qū)⑽幢惶幚淼腻e誤打印到控制臺中,我們稱這些錯誤信息為traceback。如果程序不是在文本控制臺中運(yùn)行 的,traceback也能夠?qū)㈠e誤信息輸出到日志文件或是消息對話框中。當(dāng)標(biāo)準(zhǔn)的traceback無法提供足夠的信息時,可以使用cgitb 模塊來查看各級棧和源代碼上下文中的詳細(xì)信息,比如局部變量。cgitb模塊還能夠?qū)⑦@些跟蹤信息以HTML的形式輸出,用來報告web應(yīng)用中的錯誤。

一旦發(fā)現(xiàn)了問題出在哪里后,就需要使用到交互式調(diào)試器進(jìn)入到代碼中進(jìn)行調(diào)試工作了,pdb模塊能夠很好地勝任這項工作。該模塊可以顯示出程序在錯誤產(chǎn)生時的執(zhí)行路徑,同時可以動態(tài)地調(diào)整對象和代碼進(jìn)行調(diào)試。當(dāng)程序通過測試并調(diào)試后,下一步就是要將注意力放到性能上了。開發(fā)者可以使用profile以及timit模塊來測試程序的速度,找出程序中到底是哪里很慢,進(jìn)而對這部分代碼獨(dú)立出來進(jìn)行調(diào)優(yōu)的工作。Python程序是通過解釋器執(zhí)行的,解釋器的輸入是原有程序的字節(jié)碼編譯版本。這個字節(jié)碼編譯版本可以在程序執(zhí)行時動態(tài)地生成,也可以在程序打包的時候就生成。compileall模塊可以處理程序打包的事宜,它暴露出了打包相關(guān)的接口,該接口能夠被安裝程序和打包工具用來生成包含模塊字節(jié)碼的文件。同時,在開發(fā)環(huán)境中,compileall模塊也可以用來驗證源文件是否包含了語法錯誤。

在源代碼級別,pyclbr模塊提供了一個類查看器,方便文本編輯器或是其他程序?qū)ython程序中有意思的字符進(jìn)行掃描,比如函數(shù)或者是類。在提供了類查看器以后,就無需引入代碼,這樣就避免了潛在的副作用影響。

文檔字符串與doctest模塊

如果函數(shù),類或者是模塊的***行是一個字符串,那么這個字符串就是一個文檔字符串??梢哉J(rèn)為包含文檔字符串是一個良好的編程習(xí)慣,這是因為這些字符串可以給Python程序開發(fā)工具提供一些信息。比如,help()命令能夠檢測文檔字符串,Python相關(guān)的IDE也能夠進(jìn)行檢測文檔字符串的工作。由于程序員傾向于在交互式shell中查看文檔字符串,所以***將這些字符串寫的簡短一些。例如

  1. # mult.py 
  2. class Test: 
  3.     """ 
  4.     >>> a=Test(5) 
  5.     >>> a.multiply_by_2() 
  6.     10 
  7.     """ 
  8.     def __init__(self, number): 
  9.         self._number=number 
  10.  
  11.     def multiply_by_2(self): 
  12.         return self._number*2 

在編寫文檔時,一個常見的問題就是如何保持文檔和實際代碼的同步。例如,程序員也許會修改函數(shù)的實現(xiàn),但是卻忘記了更新文檔。針對這個問題,我們可以使用 doctest模塊。doctest模塊收集文檔字符串,并對它們進(jìn)行掃描,然后將它們作為測試進(jìn)行執(zhí)行。為了使用doctest模塊,我們通常會新建一 個用于測試的獨(dú)立的模塊。例如,如果前面的例子Test class包含在文件mult.py中,那么,你應(yīng)該新建一個testmult.py文件用來測試,如下所示:

  1. # testmult.py 
  2.  
  3. import mult, doctest 
  4.  
  5. doctest.testmod(mult, verbose=True
  6.  
  7. # Trying: 
  8. #     a=Test(5) 
  9. # Expecting nothing 
  10. # ok 
  11. # Trying: 
  12. #     a.multiply_by_2() 
  13. # Expecting: 
  14. #     10 
  15. # ok 
  16. # 3 items had no tests: 
  17. #     mult 
  18. #     mult.Test.__init__ 
  19. #     mult.Test.multiply_by_2 
  20. # 1 items passed all tests: 
  21. #    2 tests in mult.Test 
  22. # 2 tests in 4 items. 
  23. # 2 passed and 0 failed. 
  24. # Test passed. 

在這段代碼中,doctest.testmod(module)會執(zhí)行特定模塊的測試,并且返回測試失敗的個數(shù)以及測試的總數(shù)目。如果所有的測試都通過了,那么不會產(chǎn)生任何輸出。否則的話,你將會看到一個失敗報告,用來顯示期望值和實際值之間的差別。如果你想看到測試的詳細(xì)輸出,你可以使用testmod(module, verbose=True).

如果不想新建一個單獨(dú)的測試文件的話,那么另一種選擇就是在文件末尾包含相應(yīng)的測試代碼:

  1. if __name__ == '__main__': 
  2.     import doctest 
  3.     doctest.testmod() 

如果想執(zhí)行這類測試的話,我們可以通過-m選項調(diào)用doctest模塊。通常來講,當(dāng)執(zhí)行測試的時候沒有任何的輸出。如果想查看詳細(xì)信息的話,可以加上-v選項。

  1. $ python -m doctest -v mult.py 

單元測試與unittest模塊

如果想更加徹底地 對程序進(jìn)行測試,我們可以使用unittest模塊。通過單元測試,開發(fā)者可以為構(gòu)成程序的每一個元素(例如,獨(dú)立的函數(shù),方法,類以及模塊)編寫一系列 獨(dú)立的測試用例。當(dāng)測試更大的程序時,這些測試就可以作為基石來驗證程序的正確性。當(dāng)我們的程序變得越來越大的時候,對不同構(gòu)件的單元測試就可以組合起來 成為更大的測試框架以及測試工具。這能夠極大地簡化軟件測試的工作,為找到并解決軟件問題提供了便利。

  1. # splitter.py 
  2. import unittest 
  3.  
  4. def split(line, types=None, delimiter=None): 
  5.     """Splits a line of text and optionally performs type conversion. 
  6.     ... 
  7.     """ 
  8.     fields = line.split(delimiter) 
  9.     if types: 
  10.         fields = [ ty(val) for ty,val in zip(types,fields) ] 
  11.     return fields 
  12.  
  13. class TestSplitFunction(unittest.TestCase): 
  14.     def setUp(self): 
  15.         # Perform set up actions (if any) 
  16.         pass 
  17.     def tearDown(self): 
  18.         # Perform clean-up actions (if any) 
  19.         pass 
  20.     def testsimplestring(self): 
  21.         r = split('GOOG 100 490.50') 
  22.         self.assertEqual(r,['GOOG','100','490.50']) 
  23.     def testtypeconvert(self): 
  24.         r = split('GOOG 100 490.50',[str, int, float]) 
  25.         self.assertEqual(r,['GOOG', 100, 490.5]) 
  26.     def testdelimiter(self): 
  27.         r = split('GOOG,100,490.50',delimiter=',') 
  28.         self.assertEqual(r,['GOOG','100','490.50']) 
  29.  
  30. # Run the unittests 
  31. if __name__ == '__main__': 
  32.     unittest.main() 
  33.  
  34. #... 
  35. #---------------------------------------------------------------------- 
  36. #Ran 3 tests in 0.001s 
  37.  
  38. #OK 

在使用單元測試時,我們需要定義一個繼承自unittest.TestCase的類。在這個類里面,每一個測試都以方法的形式進(jìn)行定義,并都以test打頭進(jìn)行命名——例如,’testsimplestring‘,’testtypeconvert‘以及類似的命名方式(有必要強(qiáng)調(diào)一下,只要方法名以test打頭,那么無論怎么命名都是可以的)。在每個測試中,斷言可以用來對不同的條件進(jìn)行檢查。

實際的例子:

假如你在程序里有一個方法,這個方法的輸出指向標(biāo)準(zhǔn)輸出(sys.stdout)。這通常意味著是往屏幕上輸出文本信息。如果你想對你的代碼進(jìn)行測試來證明這一點,只要給出相應(yīng)的輸入,那么對應(yīng)的輸出就會被顯示出來。

  1. # url.py 
  2.  
  3. def urlprint(protocol, host, domain): 
  4.     url = '{}://{}.{}'.format(protocol, host, domain) 
  5.     print(url) 

內(nèi)置的print函數(shù)在默認(rèn)情況下會往sys.stdout發(fā)送輸出。為了測試輸出已經(jīng)實際到達(dá),你可以使用一個替身對象對其進(jìn)行模擬,并且對程序的期望值進(jìn)行斷言。unittest.mock模塊中的patch()方法可以只在運(yùn)行測試的上下文中才替換對象,在測試完成后就立刻返回對象原始的狀態(tài)。下面是urlprint()方法的測試代碼:

  1. #urltest.py 
  2.  
  3. from io import StringIO 
  4. from unittest import TestCase 
  5. from unittest.mock import patch 
  6. import url 
  7.  
  8. class TestURLPrint(TestCase): 
  9.     def test_url_gets_to_stdout(self): 
  10.         protocol = 'http' 
  11.         host = 'www' 
  12.         domain = 'example.com' 
  13.         expected_url = '{}://{}.{}\n'.format(protocol, host, domain) 
  14.  
  15.         with patch('sys.stdout', new=StringIO()) as fake_out: 
  16.             url.urlprint(protocol, host, domain) 
  17.             self.assertEqual(fake_out.getvalue(), expected_url) 

urlprint()函數(shù)有三個參數(shù),測試代碼首先給每個參數(shù)賦了一個假值。變量expected_url包含了期望的輸出字符串。為了能夠執(zhí)行測試,我們使用了unittest.mock.patch()方法作為上下文管理器,把標(biāo)準(zhǔn)輸出sys.stdout替換為了StringIO對象,這樣發(fā)送的標(biāo)準(zhǔn)輸出的內(nèi)容就會被StringIO對象所接收。變量fake_out就是在這一過程中所創(chuàng)建出的模擬對象,該對象能夠在with所處的代碼塊中所使用,來進(jìn)行一系列的測試檢查。當(dāng)with語 句完成時,patch方法能夠?qū)⑺械臇|西都復(fù)原到測試執(zhí)行之前的狀態(tài),就好像測試沒有執(zhí)行一樣,而這無需任何額外的工作。但對于某些Python的C擴(kuò) 展來講,這個例子卻顯得毫無意義,這是因為這些C擴(kuò)展程序繞過了sys.stdout的設(shè)置,直接將輸出發(fā)送到了標(biāo)準(zhǔn)輸出上。這個例子僅適用于純 Python代碼的程序(如果你想捕獲到類似C擴(kuò)展的輸入輸出,那么你可以通過打開一個臨時文件然后將標(biāo)準(zhǔn)輸出重定向到該文件的技巧來進(jìn)行實現(xiàn))。

Python調(diào)試器與pdb模塊

Python在 pdb模塊中包含了一個簡單的基于命令行的調(diào)試器。pdb模塊支持事后調(diào)試(post-mortem debugging),棧幀探查(inspection of stack frames),斷點(breakpoints),單步調(diào)試(single-stepping of source lines)以及代碼審查(code evaluation)。

好幾個函數(shù)都能夠在程序中調(diào)用調(diào)試器,或是在交互式的Python終端中進(jìn)行調(diào)試工作。

在所有啟動調(diào)試器的函數(shù)中,函數(shù)set_trace()也許是最簡易實用的了。如果在復(fù)雜程序中發(fā)現(xiàn)了問題,可以在代碼中插入set_trace()函數(shù),并運(yùn)行程序。當(dāng)執(zhí)行到set_trace()函數(shù)時,這就會暫停程序的執(zhí)行并直接跳轉(zhuǎn)到調(diào)試器中,這時候你就可以大展手腳開始檢查運(yùn)行時環(huán)境了。當(dāng)退出調(diào)試器時,調(diào)試器會自動恢復(fù)程序的執(zhí)行。

假設(shè)你的程序有問題,你想找到一個簡單的方法來對它進(jìn)行調(diào)試。

如果你的程序崩潰時報了一個異常錯誤,那么你可以用python3 -i someprogram.py這個命令來運(yùn)行你的程序,這能夠很好地發(fā)現(xiàn)問題所在。-i選項表明只要程序終結(jié)就立即啟動一個交互式shell。在這個交互式shell中,你就可以很好地探查到底發(fā)生了什么導(dǎo)致程序的錯誤。例如,如果你有以下代碼:

  1. def function(n): 
  2.     return n + 10 
  3.  
  4. function("Hello"

如果使用python3 -i 命令運(yùn)行程序就會產(chǎn)生如下輸出:

  1. python3 -i sample.py 
  2. Traceback (most recent call last): 
  3.   File "sample.py", line 4in <module> 
  4.     function("Hello"
  5.   File "sample.py", line 2in function 
  6.     return n + 10 
  7. TypeError: Can&#039;t convert &#039;int&#039; object to str implicitly 
  8. >>> function(20
  9. 30 
  10. >>> 

如果你沒有發(fā)現(xiàn)什么明顯的錯誤,那么你可以進(jìn)一步地啟動Python調(diào)試器。例如:

  1. >>> import pdb 
  2. >>> pdb.pm() 
  3. > sample.py(4)func() 
  4. -> return n + 10 
  5. (Pdb) w 
  6. sample.py(6)<module>() 
  7. -> func(&#039;Hello&#039;) 
  8. > sample.py(4)func() 
  9. -> return n + 10 
  10. (Pdb) print n 
  11. &#039;Hello&#039; 
  12. (Pdb) q 
  13. >>> 

如果你的代碼身處的環(huán)境很難啟動一個交互式shell的話(比如在服務(wù)器環(huán)境下),你可以增加錯誤處理的代碼,并自己輸出跟蹤信息。例如:

  1. import traceback 
  2. import sys 
  3. try
  4.     func(arg) 
  5. except
  6.     print(&#039;**** AN ERROR OCCURRED ****&#039;) 
  7.     traceback.print_exc(file=sys.stderr) 

如果你的程序并沒有崩潰,而是說程序的行為與你的預(yù)期表現(xiàn)的不一致,那么你可以嘗試在一些可能出錯的地方加入print()函數(shù)。如果你打算采用這種方案 的話,那么還有些相關(guān)的技巧值得探究。首先,函數(shù)traceback.print_stack()能夠在被執(zhí)行時立即打印出程序中棧的跟蹤信息。例如:

  1. >>> def sample(n): 
  2. ...     if n > 0
  3. ...         sample(n-1
  4. ...     else
  5. ...         traceback.print_stack(file=sys.stderr) 
  6. ... 
  7. >>> sample(5
  8. File "<stdin>", line 1in <module> 
  9. File "<stdin>", line 3in sample 
  10. File "<stdin>", line 3in sample 
  11. File "<stdin>", line 3in sample 
  12. File "<stdin>", line 3in sample 
  13. File "<stdin>", line 3in sample 
  14. File "<stdin>", line 5in sample 
  15. >>> 

另外,你可以在程序中任意一處使用pdb.set_trace()手動地啟動調(diào)試器,就像這樣:

  1. import pdb 
  2. def func(arg): 
  3.     ... 
  4.     pdb.set_trace() 
  5.     ... 

深入解析大型程序的時候,這是一個非常實用的技巧,這樣操作能夠清楚地了解程序的控制流或是函數(shù)的參數(shù)。比如,一旦調(diào)試器啟動了之后,你就可以使用print或者w命令來查看變量,來了解棧的跟蹤信息。

在進(jìn)行軟件調(diào)試時,千萬不要讓事情變得很復(fù)雜。有時候僅僅需要知道程序的跟蹤信息就能夠解決大部分的簡單錯誤(比如,實際的錯誤總是顯示在跟蹤信息的***一行)。在實際的開發(fā)過程中,將print()函數(shù)插入到代碼中也能夠很方便地顯示調(diào)試信息(只需要記得在調(diào)試完以后將print語句刪除掉就行了)。調(diào)試器的通用用法是在崩潰的函數(shù)中探查變量的值,知道如何在程序崩潰以后再進(jìn)入到調(diào)試器中就顯得非常實用。在程序的控制流不是那么清楚的情況下,你可以插入pdb.set_trace()語句來理清復(fù)雜程序的思路。本質(zhì)上,程序會一直執(zhí)行直到遇到set_trace()調(diào)用,之后程序就會立刻跳轉(zhuǎn)進(jìn)入到調(diào)試器中。在調(diào)試器里,你就可以進(jìn)行更多的嘗試。如果你正在使用Python的IDE,那么IDE通常會提供基于pdb的調(diào)試接口,你可以查閱IDE的相關(guān)文檔來獲取更多的信息。

下面是一些Python調(diào)試器入門的資源列表:

  1. 閱讀Steve Ferb的文章 &#8220;Debugging in Python&#8221;
  2. 觀看Eric Holscher的截圖 &#8220;Using pdb, the Python Debugger&#8221;
  3. 閱讀Ayman Hourieh的文章 &#8220;Python Debugging Techniques&#8221;
  4. 閱讀 Python documentation for pdb &#8211; The Python Debugger
  5. 閱讀Karen Tracey的D jango 1.1 Testing and Debugging一書中的第九章——When You Don&#8217;t Even Know What to Log: Using Debuggers

程序分析

profile模塊和cProfile模塊可以用來分析程序。它們的工作原理都一樣,唯一的區(qū)別是,cProfile模塊 是以C擴(kuò)展的方式實現(xiàn)的,如此一來運(yùn)行的速度也快了很多,也顯得比較流行。這兩個模塊都可以用來收集覆蓋信息(比如,有多少函數(shù)被執(zhí)行了),也能夠收集性 能數(shù)據(jù)。對一個程序進(jìn)行分析的最簡單的方法就是運(yùn)行這個命令:

  1. % python -m cProfile someprogram.py 

此外,也可以使用profile模塊中的run函數(shù):

  1. run(command [, filename]) 

該函數(shù)會使用exec語句執(zhí)行command中的內(nèi)容。filename是可選的文件保存名,如果沒有filename的話,該命令的輸出會直接發(fā)送到標(biāo)準(zhǔn)輸出上。

下面是分析器執(zhí)行完成時的輸出報告:

  1. 126 function calls (6 primitive calls) in 5.130 CPU seconds 
  2. Ordered by: standard name 
  3. ncalls tottime percall cumtime percall filename:lineno(function) 
  4. 1 0.030 0.030 5.070 5.070 <string>:1(?) 
  5. 121/1 5.020 0.041 5.020 5.020 book.py:11(process) 
  6. 1 0.020 0.020 5.040 5.040 book.py:5(?) 
  7. 2 0.000 0.000 0.000 0.000 exceptions.py:101(_ _init_ _) 
  8. 1 0.060 0.060 5.130 5.130 profile:0(execfile(&#039;book.py&#039;)) 
  9. 0 0.000 0.000 profile:0(profiler) 

當(dāng)輸出中的***列包含了兩個數(shù)字時(比如,121/1),后者是元調(diào)用(primitive call)的次數(shù),前者是實際調(diào)用的次數(shù)(譯者注:只有在遞歸情況下,實際調(diào)用的次數(shù)才會大于元調(diào)用的次數(shù),其他情況下兩者都相等)。對于絕大部分的應(yīng)用 程序來講使用該模塊所產(chǎn)生的的分析報告就已經(jīng)足夠了,比如,你只是想簡單地看一下你的程序花費(fèi)了多少時間。然后,如果你還想將這些數(shù)據(jù)保存下來,并在將來 對其進(jìn)行分析,你可以使用pstats模塊。

假設(shè)你想知道你的程序究竟在哪里花費(fèi)了多少時間。

如果你只是想簡單地給你的整個程序計時的話,使用Unix中的time命令就已經(jīng)完全能夠應(yīng)付了。例如:

  1. bash % time python3 someprogram.py 
  2. real 0m13.937s 
  3. user 0m12.162s 
  4. sys 0m0.098s 
  5. bash % 

通常來講,分析代碼的程度會介于這兩個極端之間。比如,你可能已經(jīng)知道你的代碼會在一些特定的函數(shù)中花的時間特別多。針對這類特定函數(shù)的分析,我們可以使用修飾器decorator,例如:

  1. import time 
  2. from functools import wraps 
  3.  
  4. def timethis(func): 
  5.     @wraps(func) 
  6.     def wrapper(*args, **kwargs): 
  7.         start = time.perf_counter() 
  8.         r = func(*args, **kwargs) 
  9.         end = time.perf_counter() 
  10.         print(&#039;{}.{} : {}&#039;.format(func.__module__, func.__name__, end - start)) 
  11.         return r 
  12.     return wrapper 

使用decorator的方式很簡單,你只需要把它放在你想要分析的函數(shù)的定義前面就可以了。例如:

  1. >>> @timethis 
  2. ... def countdown(n): 
  3. ...     while n > 0
  4. ...         n -= 1 
  5. ... 
  6. >>> countdown(10000000
  7. __main__.countdown : 0.803001880645752 
  8. >>> 

如果想要分析一個語句塊的話,你可以定義一個上下文管理器(context manager)。例如:

  1. import time 
  2. from contextlib import contextmanager 
  3.  
  4. @contextmanager 
  5. def timeblock(label): 
  6.     start = time.perf_counter() 
  7.     try
  8.         yield 
  9.     finally
  10.         end = time.perf_counter() 
  11.         print(&#039;{} : {}&#039;.format(label, end - start)) 

接下來是如何使用上下文管理器的例子:

  1. >>> with timeblock(&#039;counting&#039;): 
  2. ...     n = 10000000 
  3. ...     while n > 0
  4. ...         n -= 1 
  5. ... 
  6. counting : 1.5551159381866455 
  7. >>> 

 

責(zé)任編輯:陳四芳 來源: 開源中國編譯
相關(guān)推薦

2019-01-30 14:14:16

LinuxUNIX操作系統(tǒng)

2023-09-12 11:25:15

2025-02-10 08:18:27

JSON數(shù)據(jù)交換格式

2020-05-13 11:20:57

MySQL規(guī)范數(shù)據(jù)庫

2023-11-08 18:01:53

硬重置Git命令

2024-01-10 18:01:22

編程技巧Java 12

2023-11-21 20:15:10

Git命令開發(fā)

2018-09-18 10:56:52

Android開發(fā)http

2020-11-25 10:40:58

程序員技能開發(fā)者

2023-05-11 08:01:08

Code開發(fā)保護(hù)機(jī)制

2021-05-18 16:42:51

開發(fā)博學(xué)谷工具

2015-12-11 14:38:54

開發(fā)快速開發(fā)工具

2012-05-14 18:35:20

Windows Pho

2013-07-18 17:22:07

Android開發(fā)資源Android開發(fā)學(xué)習(xí)Android開發(fā)

2013-05-06 15:41:30

Android開發(fā)資源

2019-07-09 11:09:34

程序員Linux技術(shù)

2017-08-03 14:25:13

Python陷阱與缺陷

2015-12-04 09:33:15

程序員前端演進(jìn)史

2020-05-25 09:30:30

程序員工具開發(fā)者

2018-07-11 14:04:53

Python陷阱缺陷
點贊
收藏

51CTO技術(shù)棧公眾號