异常处理、socke基于TCP协议编程
一、異常處理
?1、錯誤和異常
1.程序中難免出現(xiàn)錯誤,而錯誤分成兩種
(1)語法錯誤(這種錯誤過不了Python解釋器的語法檢測,必須在程序執(zhí)行前改正)
#語法錯誤示范一 if#語法錯誤示范二 def test:pass#語法錯誤示范三 class Foopass#語法錯誤示范四 print(haha(2)邏輯錯誤
#用戶輸入不完整(比如輸入為空)或者輸入非法(輸入不是數(shù)字) num=input(">>: ") int(num)#無法完成計算 res1=1/0 res2=1+'str'2.什么是異常
異常就是程序運行時發(fā)生錯誤的信號,在python中,錯誤觸發(fā)的異常如下:
?3.Python中的異常種類
在python中不同的異常可以用不同的類型(python中統(tǒng)一了類與類型,類型即類)去標識,不同的類對象標識不同的異常,一個異常標識一種錯誤。
AttributeError 試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性x IOError 輸入/輸出異常;基本上是無法打開文件 ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤 IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊 IndexError 下標索引超出序列邊界,比如當(dāng)x只有三個元素,卻試圖訪問x[5] KeyError 試圖訪問字典里不存在的鍵 KeyboardInterrupt Ctrl+C被按下 NameError 使用一個還未被賦予對象的變量 SyntaxError Python代碼非法,代碼不能編譯(個人認為這是語法錯誤,寫錯了) TypeError 傳入對象類型與要求的不符合 UnboundLocalError 試圖訪問一個還未被設(shè)置的局部變量,基本上是由于另有一個同名的全局變量, 導(dǎo)致你以為正在訪問它 ValueError 傳入一個調(diào)用者不期望的值,即使值的類型是正確的 ArithmeticError AssertionError AttributeError BaseException BufferError BytesWarning DeprecationWarning EnvironmentError EOFError Exception FloatingPointError FutureWarning GeneratorExit ImportError ImportWarning IndentationError IndexError IOError KeyboardInterrupt KeyError LookupError MemoryError NameError NotImplementedError OSError OverflowError PendingDeprecationWarning ReferenceError RuntimeError RuntimeWarning StandardError StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TypeError UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError UnicodeTranslateError UnicodeWarning UserWarning ValueError Warning ZeroDivisionError #觸發(fā)IndexError l=['egon','aa'] l[3]#觸發(fā)KeyError dic={'name':'egon'} dic['age']#觸發(fā)ValueError s='hello' int(s)?2、異常處理
?2.1什么是異常處理?
python解釋器檢測到錯誤,觸發(fā)異常(也允許程序員自己觸發(fā)異常)。
程序員編寫特定的代碼,專門用來捕捉這個異常(這段代碼與程序邏輯無關(guān),與異常處理有關(guān))。
如果捕捉成功則進入另外一個處理分支,執(zhí)行你為其定制的邏輯,使程序不會崩潰,這就是異常處理。
?2.2為何要進行異常處理?
python解析器去執(zhí)行程序,檢測到了一個錯誤時,觸發(fā)異常,異常觸發(fā)后且沒被處理的情況下,程序就在當(dāng)前異常處終止,后面的代碼不會運行,誰會去用一個運行著突然就崩潰的軟件。
所以你必須提供一種異常處理機制來增強你程序的健壯性與容錯性 。
?2.3 如何進行異常處理?
首先須知,異常是由程序的錯誤引起的,語法上的錯誤跟異常處理無關(guān),必須在程序運行前就修正。
一: 使用if判斷式
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng'#第一段代碼 num1=input('>>: ') #輸入一個字符串試試 int(num1)#第二段代碼 num2=input('>>: ') #輸入一個字符串試試 int(num2)#第三段代碼 num3=input('>>: ') #輸入一個字符串試試 int(num3) #_*_coding:utf-8_*_ __author__ = 'Linhaifeng'num1=input('>>: ') #輸入一個字符串試試 if num1.isdigit():int(num1) #我們的正統(tǒng)程序放到了這里,其余的都屬于異常處理范疇 elif num1.isspace():print('輸入的是空格,就執(zhí)行我這里的邏輯') elif len(num1) == 0:print('輸入的是空,就執(zhí)行我這里的邏輯') else:print('其他情情況,執(zhí)行我這里的邏輯')#第二段代碼 # num2=input('>>: ') #輸入一個字符串試試 # int(num2)#第三段代碼 # num3=input('>>: ') #輸入一個字符串試試 # int(num3)''' 問題一: 使用if的方式我們只為第一段代碼加上了異常處理,針對第二段代碼,你得重新寫一堆if,elif等 第三段,你還得在寫一遍,當(dāng)然了,你說,可以合在一起啊,沒錯,你合起來看看,你的代碼還能被看懂嗎??? 而這些if,跟你的代碼邏輯并無關(guān)系,這就好比你在你心愛的程序中到處拉屎,拉到最后,誰都不愛看你的爛代碼,為啥,因為可讀性差,看不懂問題二: 第一段代碼和第二段代碼實際上是同一種異常,都是ValueError,相同的錯誤按理說只處理一次就可以了,而用if,由于這二者if的條件不同,這只能逼著你重新寫一個新的if來處理第二段代碼的異常 第三段也一樣 '''總結(jié):
1.if判斷式的異常處理只能針對某一段代碼,對于不同的代碼段的相同類型的錯誤你需要寫重復(fù)的if來進行處理。
2.在你的程序中頻繁的寫與程序本身無關(guān),與異常處理有關(guān)的if判斷,造成代碼可讀性極其的差。
3.這是可以解決異常的,只是存在1,2的問題,所以,千萬不要妄下定論if不能用來異常處理。
二:python為每一種異常定制了一個類型,然后提供了一種特定的語法結(jié)構(gòu)用來進行異常處理。
part1:基本語法
try:被檢測的代碼塊 except 異常類型:try中一旦檢測到異常,就執(zhí)行這個位置的邏輯 f=open('a.txt') g=(line.strip() for line in f) ''' next(g)會觸發(fā)迭代f,依次next(g)就可以讀取文件的一行行內(nèi)容,無論文件a.txt有多大,同一時刻內(nèi)存中只有一行內(nèi)容。 提示:g是基于文件句柄f而存在的,因而只能在next(g)拋出異常StopIteration后才可以執(zhí)行f.close() '''f=open('a.txt')g=(line.strip() for line in f) for line in g:print(line) else:f.close()try:f=open('a.txt')g=(line.strip() for line in f)print(next(g))print(next(g))print(next(g))print(next(g))print(next(g)) except StopIteration:f.close()part2:異常類只能用來處理指定的異常情況,如果非指定異常則無法處理。
# 未捕獲到異常,程序直接報錯s1 = 'hello' try:int(s1) except IndexError as e:print epart3:多分支
1 s1 = 'hello' 2 try: 3 int(s1) 4 except IndexError as e: 5 print(e) 6 except KeyError as e: 7 print(e) 8 except ValueError as e: 9 print(e)part4:萬能異常 在python的異常中,有一個萬能異常:Exception,他可以捕獲任意異常,即:
s1 = 'hello' try:int(s1) except Exception as e:print(e) #可在多分支后面使用Exceptions1 = 'hello' try:int(s1) except IndexError as e:print(e) except KeyError as e:print(e) except ValueError as e:print(e) except Exception as e:print(e)
part5:異常的其他結(jié)構(gòu)
s1 = 'hello' try:int(s1) except IndexError as e:print(e) except KeyError as e:print(e) except ValueError as e:print(e) #except Exception as e: # print(e) else:print('try內(nèi)代碼塊沒有異常則執(zhí)行我') finally:print('無論異常與否,都會執(zhí)行該模塊,通常是進行清理工作')part6:主動觸發(fā)異常
#_*_coding:utf-8_*_try:raise TypeError('類型錯誤') except Exception as e:print(e)part7:自定義異常
#_*_coding:utf-8_*_class EgonException(BaseException):def __init__(self,msg):self.msg=msgdef __str__(self):return self.msgtry:raise EgonException('類型錯誤') except EgonException as e:print(e)part8:斷言
# assert 條件assert 1 == 1 #滿足assert 的條件才執(zhí)行后面的代碼assert 1 == 2part9:try..except的方式比較if的方式的好處
try..except這種異常處理機制就是取代if那種方式,讓你的程序在不犧牲可讀性的前提下增強健壯性和容錯性。
異常處理中為每一個異常定制了異常類型(python中統(tǒng)一了類與類型,類型即類),對于同一種異常,一個except就可以捕捉到,可以同時處理多段代碼的異常(無需‘寫多個if判斷式’)減少了代碼,增強了可讀性 。
使用try..except的方式
1:把錯誤處理和真正的工作分開來
2:代碼更易組織,更清晰,復(fù)雜的工作任務(wù)更容易實現(xiàn);
3:毫無疑問,更安全了,不至于由于一些小的疏忽而使程序意外崩潰了;
try...except應(yīng)該盡量少用,因為它本身就是你附加給你的程序的一種異常處理的邏輯,與你的主要的工作是沒有關(guān)系的
這種東西加的多了,會導(dǎo)致你的代碼可讀性變差。
二、socket編程
1、客戶端/服務(wù)器架構(gòu)
即C/S架構(gòu),包括
1.硬件C/S架構(gòu)(打印機)
2.軟件C/S架構(gòu)(web服務(wù))
美好的愿望:
最常用的軟件服務(wù)器是 Web 服務(wù)器。一臺機器里放一些網(wǎng)頁或 Web 應(yīng)用程序,然后啟動 服務(wù)。這樣的服務(wù)器的任務(wù)就是接受客戶的請求,把網(wǎng)頁發(fā)給客戶(如用戶計算機上的瀏覽器),然 后等待下一個客戶請求。這些服務(wù)啟動后的目標就是“永遠運行下去”。雖然它們不可能實現(xiàn)這樣的 目標,但只要沒有關(guān)機或硬件出錯等外力干擾,它們就能運行非常長的一段時間。?
?
生活中的C/S架構(gòu):
老男孩是S端,所有的學(xué)員是C端
飯店是S端,所有的食客是C端
互聯(lián)網(wǎng)中處處是C/S架構(gòu)(黃色網(wǎng)站是服務(wù)端,你的瀏覽器是客戶端;騰訊作為服務(wù)端為你提供視頻,你得下個騰訊視頻客戶端才能看狗日的視頻)
?
C/S架構(gòu)與socket的關(guān)系:
我們學(xué)習(xí)socket就是為了完成C/S架構(gòu)的開發(fā)。
2、osi七層
引子:
須知一個完整的計算機系統(tǒng)是由硬件、操作系統(tǒng)、應(yīng)用軟件三者組成,具備了這三個條件,一臺計算機系統(tǒng)就可以自己跟自己玩了(打個單機游戲,玩?zhèn)€掃雷啥的)
如果你要跟別人一起玩,那你就需要上網(wǎng)了(訪問個黃色網(wǎng)站,發(fā)個黃色微博啥的),互聯(lián)網(wǎng)的核心就是由一堆協(xié)議組成,協(xié)議就是標準,全世界人通信的標準是英語,如果把計算機比作人,互聯(lián)網(wǎng)協(xié)議就是計算機界的英語。所有的計算機都學(xué)會了互聯(lián)網(wǎng)協(xié)議,那所有的計算機都就可以按照統(tǒng)一的標準去收發(fā)信息從而完成通信了。人們按照分工不同把互聯(lián)網(wǎng)協(xié)議從邏輯上劃分了層級,詳見另一篇博客。
參考博客(網(wǎng)絡(luò)通信原理):
http://www.cnblogs.com/linhaifeng/articles/5937962.html
http://www.cnblogs.com/maple-shaw/p/6891223.html
?
為何學(xué)習(xí)socket一定要先學(xué)習(xí)互聯(lián)網(wǎng)協(xié)議:
1.首先:本節(jié)課程的目標就是教會你如何基于socket編程,來開發(fā)一款自己的C/S架構(gòu)軟件。
2.其次:C/S架構(gòu)的軟件(軟件屬于應(yīng)用層)是基于網(wǎng)絡(luò)進行通信的
3.然后:網(wǎng)絡(luò)的核心即一堆協(xié)議,協(xié)議即標準,你想開發(fā)一款基于網(wǎng)絡(luò)通信的軟件,就必須遵循這些標準。
4.最后:就讓我們從這些標準開始研究,開啟我們的socket編程之旅。
3、socket層
?4、socket是什么
ocket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計模式中,Socket其實就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。
所以,我們無需深入理解tcp/udp協(xié)議,socket已經(jīng)為我們封裝好了,我們只需要遵循socket的規(guī)定去編程,寫出的程序自然就是遵循tcp/udp標準的。
?
也有人將socket說成ip+port,ip是用來標識互聯(lián)網(wǎng)中的一臺主機的位置,而port是用來標識這臺機器上的一個應(yīng)用程序,ip地址是配置到網(wǎng)卡上的,而port是應(yīng)用程序開啟的,ip與port的綁定就標識了互聯(lián)網(wǎng)中獨一無二的一個應(yīng)用程序。而程序的pid是同一臺機器上不同進程或者線程的標識。
5、套接字發(fā)展史及分類
套接字起源于 20 世紀 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設(shè)計用在同 一臺主機上多個應(yīng)用程序之間的通訊。這也被稱進程間通訊或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。?
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統(tǒng)間接完成通信
基于網(wǎng)絡(luò)類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個平臺,要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒有實現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時候我么只使用AF_INET)
6、套接字工作流程
? 一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲后提起電話,這時你和你的朋友就建立起了連接,就可以講話了。等交流結(jié)束,掛斷電話結(jié)束此次交談。生活中的場景就解釋了這工作原理,也許TCP/IP協(xié)議族就是誕生于生活中,這也不一定。
先從服務(wù)器端說起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務(wù)器(connect),如果連接成功,這時客戶端與服務(wù)器端的連接就建立了。客戶端發(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束。
socket()模塊函數(shù)用法
import socket socket.socket(socket_family,socket_type,protocal=0) socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認值為 0。獲取tcp/ip套接字 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)獲取udp/ip套接字 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)由于 socket 模塊中有太多的屬性。我們在這里破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket 模塊里的所有屬性都帶到我們的命名空間里了,這樣能 大幅減短我們的代碼。 例如tcpSock = socket(AF_INET, SOCK_STREAM)服務(wù)端套接字函數(shù)
s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監(jiān)聽
s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
客戶端套接字函數(shù)
s.connect() 主動初始化TCP服務(wù)器連接
s.connect_ex() connect()函數(shù)的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數(shù)
s.recv() 接收TCP數(shù)據(jù)
s.send() 發(fā)送TCP數(shù)據(jù)(send在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時,數(shù)據(jù)丟失,不會發(fā)完)
s.sendall() 發(fā)送完整的TCP數(shù)據(jù)(本質(zhì)就是循環(huán)調(diào)用send,sendall在待發(fā)送數(shù)據(jù)量大于己端緩存區(qū)剩余空間時,數(shù)據(jù)不丟失,循環(huán)調(diào)用send直到發(fā)完)
s.recvfrom() 接收UDP數(shù)據(jù)
s.sendto() 發(fā)送UDP數(shù)據(jù)
s.getpeername() 連接到當(dāng)前套接字的遠端的地址
s.getsockname() 當(dāng)前套接字的地址
s.getsockopt() 返回指定套接字的參數(shù)
s.setsockopt() 設(shè)置指定套接字的參數(shù)
s.close() 關(guān)閉套接字
面向鎖的套接字方法
s.setblocking() 設(shè)置套接字的阻塞與非阻塞模式
s.settimeout() 設(shè)置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間
面向文件的套接字的函數(shù)
s.fileno() 套接字的文件描述符
s.makefile() 創(chuàng)建一個與該套接字相關(guān)的文件
7、基于TCP的套接字
TCP服務(wù)端
1 ss = socket() #創(chuàng)建服務(wù)器套接字 2 ss.bind() #把地址綁定到套接字 3 ss.listen() #監(jiān)聽鏈接 4 inf_loop: #服務(wù)器無限循環(huán) 5 cs = ss.accept() #接受客戶端鏈接 6 comm_loop: #通訊循環(huán) 7 cs.recv()/cs.send() #對話(接收與發(fā)送) 8 cs.close() #關(guān)閉客戶端套接字 9 ss.close() #關(guān)閉服務(wù)器套接字(可選)TCP客戶端
1 cs = socket() # 創(chuàng)建客戶套接字 2 cs.connect() # 嘗試連接服務(wù)器 3 comm_loop: # 通訊循環(huán) 4 cs.send()/cs.recv() # 對話(發(fā)送/接收) 5 cs.close()?
socket通信流程與打電話流程類似,我們就以打電話為例來實現(xiàn)一個low版的套接字通信:
#_*_coding:utf-8_*_import socket ip_port=('127.0.0.1',9000) #電話卡 BUFSIZE=1024 #收發(fā)消息的尺寸 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 s.bind(ip_port) #手機插卡 s.listen(5) #手機待機conn,addr=s.accept() #手機接電話 # print(conn) # print(addr) print('接到來自%s的電話' %addr[0])msg=conn.recv(BUFSIZE) #聽消息,聽話 print(msg,type(msg))conn.send(msg.upper()) #發(fā)消息,說話conn.close() #掛電話s.close() #手機關(guān)機 #_*_coding:utf-8_*_import socket ip_port=('127.0.0.1',9000) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect_ex(ip_port) #撥電話s.send('linhaifeng nb'.encode('utf-8')) #發(fā)消息,說話(只能發(fā)送字節(jié)類型)feedback=s.recv(BUFSIZE) #收消息,聽話 print(feedback.decode('utf-8'))s.close() #掛電話?
上述流程的問題是,服務(wù)端只能接受一次鏈接,然后就徹底關(guān)閉掉了,實際情況應(yīng)該是,服務(wù)端不斷接受鏈接,然后循環(huán)通信,通信完畢后只關(guān)閉鏈接,服務(wù)器能夠繼續(xù)接收下一次鏈接,下面是修改版:
#_*_coding:utf-8_*_import socket ip_port=('127.0.0.1',8081)#電話卡 BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 s.bind(ip_port) #手機插卡 s.listen(5) #手機待機while True: #新增接收鏈接循環(huán),可以不停的接電話conn,addr=s.accept() #手機接電話# print(conn)# print(addr)print('接到來自%s的電話' %addr[0])while True: #新增通信循環(huán),可以不斷的通信,收發(fā)消息msg=conn.recv(BUFSIZE) #聽消息,聽話# if len(msg) == 0:break #如果不加,那么正在鏈接的客戶端突然斷開,recv便不再阻塞,死循環(huán)發(fā)生print(msg,type(msg))conn.send(msg.upper()) #發(fā)消息,說話conn.close() #掛電話s.close() #手機關(guān)機 #_*_coding:utf-8_*_import socket ip_port=('127.0.0.1',8081) BUFSIZE=1024 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect_ex(ip_port) #撥電話while True: #新增通信循環(huán),客戶端可以不斷發(fā)收消息msg=input('>>: ').strip()if len(msg) == 0:continues.send(msg.encode('utf-8')) #發(fā)消息,說話(只能發(fā)送字節(jié)類型)feedback=s.recv(BUFSIZE) #收消息,聽話print(feedback.decode('utf-8'))s.close() #掛電話問題:
有的同學(xué)在重啟服務(wù)端時可能會遇到
這個是由于你的服務(wù)端仍然存在四次揮手的time_wait狀態(tài)在占用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務(wù)器高并發(fā)情況下會有大量的time_wait狀態(tài)的優(yōu)化方法)
解決方法:
#加入一條socket配置,重用ip和端口phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) 發(fā)現(xiàn)系統(tǒng)存在大量TIME_WAIT狀態(tài)的連接,通過調(diào)整linux內(nèi)核參數(shù)解決, vi /etc/sysctl.conf編輯文件,加入以下內(nèi)容: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30然后執(zhí)行 /sbin/sysctl -p 讓參數(shù)生效。net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當(dāng)出現(xiàn)SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關(guān)閉;net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用于新的TCP連接,默認為0,表示關(guān)閉;net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關(guān)閉。net.ipv4.tcp_fin_timeout 修改系統(tǒng)默認的 TIMEOUT 時間異常處理、socke基于TCP協(xié)議編程
轉(zhuǎn)載于:https://www.cnblogs.com/1204guo/p/7147383.html
總結(jié)
以上是生活随笔為你收集整理的异常处理、socke基于TCP协议编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win7安装Python所需资源
- 下一篇: 学习:重写hashCode()方法的必要