此文爲學習廖雪峯的Python學習教程的筆記,接下來是更加高級的部分,將會進入到一些實際用途很大的章節,由於本人之前有一定的java編程經驗,所以不自覺地會和java進行對比,如果沒有學過請自行忽略,不影響閱讀。如果有不恰當的地方,也歡迎進行糾正,以免誤導他人。願能有所收穫,以下是正文部分:
面向對象編程
- 區分面向對象與面向過程:面向過程是將計算機程序視爲一系列命令的集合,即一組函數順序執行。面向對象是將計算機程序視爲一組對象的集合,對象包含數據和操作數據的方法。實際
操作時只用將類實例化爲對象並調用函數進行操作即可,而且各對象之間可以互相影響,增強了代碼的複用性和可維護性。
- 類是抽象的模板,經過實例化纔會成爲一個個具體的對象。每個對象擁有的方法相同,但是數據可能不同。**由於Python是動態語言,因此可以在外部對對象添加屬性,這個與java很不一
樣。**
- 創建類的格式:
- 通過
class
關鍵字定義類,之後緊跟類名,類名通常是大寫開頭的單詞,緊接着是(object),表示該類是從哪個類繼承下來的。如果沒有明確的父類,就默認爲object
__init__
方法可以將類需要綁定的屬性寫入。它的第一個參數永遠是self,表示創建的實例本身,因此,在__init__
方法內部,就可以把各種屬性綁定到self,因爲self就指向創建的
- 通過
實例本身。調用時不用寫入self,但是要將必要參數寫入。
3. 和普通的函數相比,在類中定義的函數只有一點不同,就是第一個參數永遠是實例變量self,並且,調用時,不用傳遞該參數。除此之外,類的方法和普通函數沒有什麼區別,所以,你仍
然可以用默認參數、可變參數和關鍵字參數。
4. 要定義一個方法,除了第一個參數是self外,其他和普通函數一樣。要調用一個方法,只需要在實例變量上直接調用,除了self不用傳遞,其他參數正常傳入
5. 封裝有利於添加新的功能
6. 以下爲示例:
class Test(object):
def __init__(self,name,age):
self.name = name
self.age = age
def print_msg(self):
print 'name: %s age: %d' % (self.name,self.age)
steve = Test('steve' , 16)
steve.print_msg()
注意:在我敲這段代碼時print_msg函數的參數忘加self導致錯誤,這是python類中的函數與一般函數不一樣的地方,切記!、
訪問限制
java中私有變量可以通過private修飾符,公有變量可以通過public來聲明,但是python中如果需要聲明類的私有變量,就需要在變量前·加__
修飾,當然和之前模塊的私有變量一樣,這個也是一個君子協定,仍然有方法調用私有變量,方法就是xxx,我們還是儘量做君子,不要使用這種方式爲好,畢竟這樣既不規範也不安全。
示例:
def __init__(self,name,age):
self.__name = name
self.__age = age
def print_msg(self):
print 'name: %s age: %d' % (self.__name,self.__age)
當需要訪問和更改私有變量時,寫訪問器和更改器即可。例:
def get_name(self):
return self.__name
def set_age(self, age):
self.__age = age
繼承與多態
提到面向對象編程就肯定繞不開這兩個特性,這是OOP三板斧中的兩個(另一個就是之前提到的封裝)。java中通過extend
來表示繼承關係,但是在python中繼承很簡單:
class Teacher(People):
...
括號裏就是父類,括號外就是子類。前面也提到過,沒有可繼承的父類時,就用object
填入。
多態表明的是子類同樣也是父類,此例中指老師同時也是人,當一個方法需要People作爲參數時,Teacher也可以作爲參數傳入,用java來描述就是父類的引用變量可以指向子類對象。
獲取對象信息
type()函數
>>> type(123)
<type 'int'>
>>> type('str')
<type 'str'>
- type()函數返回的是
type
類型,而所有的基本類型都屬於type類型。 - 如果一個變量指向函數或者類,也可以用type()判斷:
>>> type(abs)
<type 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
Python把每種type類型都定義好了常量,放在types
模塊裏,使用之前,需要先導入:
>>> import types
>>> type('abc')==types.StringType
True
>>> type(u'abc')==types.UnicodeType
True
>>> type([])==types.ListType
True
>>> type(str)==types.TypeType
True
- 有一種類型就叫TypeType,所有類型本身的類型就是TypeType,比如:
>>> type(int)==type(str)==types.TypeType
True
isinstance()函數
- isinstance()判斷的是一個對象是否是該類型本身,或者位於該類型的父繼承鏈上。
- 能用type()判斷的基本類型也可以用isinstance()判斷:
>>> isinstance('a', str)
True
>>> isinstance(u'a', unicode)
True
>>> isinstance('a', unicode)
False
- 還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是str或者unicode:
>>> isinstance('a', (str, unicode))
True
>>> isinstance(u'a', (str, unicode))
True
由於str和unicode都是從basestring繼承下來的,所以,還可以把上面的代碼簡化爲:
>>> isinstance(u'a', basestring)
True
dir()函數
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數,它返回一個包含字符串的list,比如,獲得一個str對象的所有屬性和方法:
>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
- 操作對象的屬性:
getattr()
、setattr()
以及hasattr()
,分別可用於獲取屬性,設置屬性和查詢屬性是否存在。具體實例參考獲取對象信息
面向對象高級編程
使用__slots__
由於python是一門動態語言,允許在調用的過程中添加屬性,但如果給Student添加不被允許的方法或屬性就會導致一些不必要的麻煩,因此這時需要__slots__
屬性來限制類的屬性添加,例如:
>>> class Student(object):
... __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
...
此時如果動態添加除name和age外的屬性,程序就會報錯,但是這條規則對子類無效,因爲子類的__slots__
包含的內容除了父類的還有自己的。
@property
一個getter方法變成屬性,只需要加上@property
就可以了,此時,@property
本身又創建了另一個裝飾器@score.setter
,負責把一個setter方法變成屬性賦值,例如:
class Test(object):
@property
def name(self):
return self._name
@name.setter
def name(self,val):
self._name = val
@property
def age(self):
return self._age
多重繼承
如果一個類具有多個父類的屬性,可以通過多繼承來實現。多重繼承的實現只需要直接在括號中添加其他父類即可:
class Student(People,PlayGame)
- Mixin
由於繼承關係主線一般都是單鏈,例如學生繼承自人,但是玩遊戲也是需要額外添加的功能。因此爲了更好地確立關係,需要引入Mixin
,這是一種用於明確表明額外功能而非父類(此例中就是學生的父類爲人,但是可以加入玩遊戲的功能),此時需要進行一點改進:
class Student(People,PlayGameMixin)
定製類
__str__()
當直接print對象時,就會調用__str__
方法,對其進行重定義即可獲得自己定製的字符串。__repr__()
返回程序開發者看到的字符串,也就是說,__repr__()
是爲調試服務的。當在交互界面直接輸入對象的引用時就會調用此方法。__iter__()
如果一個類想被用於for … in循環,類似list或tuple那樣,就必須實現一個__iter__()
方法,該方法返回一個迭代對象,然後,Python的for循環就會不斷調用該迭代對象的next()方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個計數器a,b
def __iter__(self):
return self # 實例本身就是迭代對象,故返回自己
def next(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100000: # 退出循環的條件
raise StopIteration();
return self.a # 返回下一個值
調用實例作用於for循環:
>>> for n in Fib():
... print n
...
1
1
2
3
5
...
46368
__getitem__()
用於使引用變量可以像list那樣取每一個值。例如:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
利用類似下標訪問:
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
– 切片對象slice
加上slice對象判斷,可以對引用變量使用切片:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice):
start = n.start
stop = n.stop
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
__getattr__()
當本類中沒有相關屬性時,會調用此方法查找,因此它可以用於存放額外變量。__call__()
本方法可以用於將實例直接調用,而不只是通過調用方法來操作數據。callable()函數
通過此函數可判斷一個對象是否可調用,如:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('string')
False
錯誤處理及調試
錯誤處理
try...except...finally
可用於異常處理,當執行出錯時執行except後面的代碼,except後可添加else,當沒有異常時執行。finally後的代碼是無論有無異常均會執行,這與java的try…catch…finally相似。raise
可以將異常拋出,與java中throws相似。不過它可以更改拋出的異常類型,這點比較特殊。
調試
- print 這個就是在你需要知道某個變量值的地方打印一下就行,簡單粗暴,不利於後期清除。
- assert 斷言,這個相當於設了斷點,錯誤時拋出AssertionError,內容就是你的第二個字符串參數。如:
assert n != 0, 'n is zero!'
- logging 它允許你指定記錄信息的級別,有debug,info,warning,error等幾個級別,當我們指定level=INFO時,logging.debug就不起作用了。同理,指定level=WARNING後,debug和info就不起作用了。這樣一來,你可以放心地輸出不同級別的信息,也不用刪除,最後統一控制輸出哪個級別的信息。
pdb 通過
python -m pdb file_name.py
啓動(file_name是你的python文件名,此處只是在正常執行語句中間加了-m pdb)常用操作有:l
列出代碼p 變量名
查看變量值n
執行下一行代碼q
結束調試,退出程序
pdb.set_trace() 設置斷點:
import pdb
s = '0'
n = int(s)
pdb.set_trace() # 運行到這裏會自動暫停
print 10 / n
運行代碼,程序會自動在pdb.set_trace()暫停並進入pdb調試環境,可以用命令p查看變量,或者用命令c繼續運行.關於pdb的一些命令
IO編程
- 使用內置的open()函數,通過read()可以一次讀取所有內容,通過write()可以寫入文件。
>>> f = open('e:/test.txt', 'r')
>>> f.read()
'Hello, world!'
>>> f.write('test')
>>> f.close()
2.用with... as...
來簡化代碼,可以免去每次打開完文件後關閉文件,自動用完後調用close():
with open('/path/to/file', 'r') as f:
print f.read()
3.codecs可用於自動轉換成相應編碼:
import codecs
with codecs.open('e:/gbk.txt', 'r', 'gbk') as f:
f.read()
4.read(size)方法,每次最多讀取size個字節的內容。類似於java中的buffer.直接調用read()會讀取全部內容。
5.調用readline()可以每次讀取一行內容,調用readlines()一次讀取所有內容並按行返回list。
6.file-like Object :像open()函數返回的這種有個read()方法的對象,在Python中統稱爲file-like Object。除了file外,還可以是內存的字節流,網絡流,自定義流等等。file-like Object不要求從特定類繼承,只要寫個read()方法就行。
序列化
我們把變量從內存中變成可存儲或傳輸的過程稱之爲序列化,在Python中叫pickling。序列化之後,就可以把序列化後的內容寫入磁盤,或者通過網絡傳輸到別的機器上。反過來,把變量內容從序列化的對象重新讀到內存裏稱之爲反序列化,即unpickling。
Python提供兩個模塊來實現序列化:
cPickle
和pickle
。這兩個模塊功能是一樣的,區別在於cPickle是C語言寫的,速度快,pickle是純Python寫的,速度慢,跟cStringIO
和StringIO
一個道理。用的時候,先嚐試導入cPickle,如果失敗,再導入pickle:
try:
import cPickle as pickle
except ImportError:
import pickle
- pickle.dumps()方法把任意對象序列化成一個str,然後,就可以把這個str寫入文件。或者用另一個方法pickle.dump()直接把對象序列化後寫入一個file-like Object:
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
"(dp0\nS'age'\np1\nI20\nsS'score'\np2\nI88\nsS'name'\np3\nS'Bob'\np4\ns."
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}
- 如果我們要在不同的編程語言之間傳遞對象,就必須把對象序列化爲標準格式,比如XML,但更好的方法是序列化爲JSON,因爲JSON表示出來就是一個字符串,可以被所有語言讀取,也可以方便地存儲到磁盤或者通過網絡傳輸。
- 把Python對象變成一個JSON:
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'
dumps()方法返回一個str,內容就是標準的JSON。
- 反序列化得到的所有字符串對象默認都是unicode而不是str。由於JSON標準規定JSON編碼是UTF-8,所以我們總是能正確地在Python的str或unicode與JSON的字符串之間轉換。
多進程與多線程
多進程
由於Python是跨平臺的,自然也應該提供一個跨平臺的多進程支持。multiprocessing
模塊就是跨平臺版本的多進程模塊。
multiprocessing
模塊提供了一個Process類來代表一個進程對象,下面的例子演示了啓動一個子進程並等待其結束:
from multiprocessing import Process
import os
# 子進程要執行的代碼
def run_proc(name):
print 'Run child process %s (%s)...' % (name, os.getpid())
if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Process(target=run_proc, args=('test',))
print 'Process will start.'
p.start()
p.join()
print 'Process end.'
執行結果如下:
Parent process 17896.
Process will start.
Run child process test (16660)...
Process end
- 創建子進程時,只需要傳入一個執行函數和函數的參數,創建一個Process實例,用start()方法啓動。
join()方法可以等待子進程結束後再繼續往下運行,通常用於進程間的同步。
Pool
如果要啓動大量的子進程,可以用進程池的方式批量創建子進程:
from multiprocessing import Pool
import os , time, random
def long_time_task(name):
print 'Run task %s (%s) ...' % (name, os.getpid())
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print 'Task %s runs %0.2f seconds. ' % (name, (end - start))
if __name__ == '__main__' :
print 'Parent process %s.' % os.getpid()
p = Pool()
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print 'Waiting for all subprocesses done ...'
p.close()
p.join()
print 'All subprocess done.'
結果如下:
Parent process 24112.
Waiting for all subprocesses done ...
RRRun task 1 (43460) ...un task 0 (1032) ...
Run task 2 (27968) ...
un task 3 (52252) ...
Task 0 runs 0.25 seconds.
Run task 4 (1032) ...
Task 1 runs 0.81 seconds.
Task 2 runs 1.65 seconds.
Task 4 runs 2.17 seconds.
Task 3 runs 2.46 seconds.
All subprocess done.
注意:pool默認大小爲電腦的核數,如果要改爲默認一次執行10個,則可寫成p=Pool(10)
進程間通信
- multiprocessing模塊包裝了底層的機制,提供了Queue、Pipes等多種方式來交換數據。
from multiprocessing import Process, Queue
import os , time, random
def write(q):
for value in ['A' , 'B' , 'C']:
print 'Put %s to queue...' % value
q.put(value)
time.sleep(random.random())
def read(q):
while True:
value = q.get(True)
print 'Get %s from queue.' % value
if __name__ == '__main__':
q = Queue()
pw = Process(target=write, args = (q,))
pr = Process(target=read, args = (q,))
pw.start()
pr.start()
pw.join()
pr.terminate()
結果如下:
from multiprocessing import Process, Queue
import os , time, random
def write(q):
for value in ['A' , 'B' , 'C']:
print 'Put %s to queue...' % value
q.put(value)
time.sleep(random.random())
def read(q):
while True:
value = q.get(True)
print 'Get %s from queue.' % value
if __name__ == '__main__':
q = Queue()
pw = Process(target=write, args = (q,))
pr = Process(target=read, args = (q,))
pw.start()
pr.start()
pw.join() #等待pw結束
pr.terminate() #pr是死循環,必須強行終止
多線程
Python的標準庫提供了兩個模塊:thread
和threading
,thread是低級模塊,threading是高級模塊,對thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。
- 啓動一個線程就是把一個函數傳入並創建Thread實例,然後調用start()開始執行:
import time, threading
def loop():
print 'thread %s is running...' % threading.current_thread().name
n = 0
while n < 5:
n = n + 1
print 'thread %s >>> %s' % (threading.current_thread().name, n)
time.sleep(1)
print 'thread %s ended' % threading.current_thread().name
print 'thread %s is running...' % threading.current_thread().name
t = threading.Thread(target = loop, name = 'LoopThread')
t.start()
t.join()
print 'thread %s ended.' % threading.current_thread().name
結果如下:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended
thread MainThread ended.
由於任何進程默認就會啓動一個線程,我們把該線程稱爲主線程,主線程又可以啓動新的線程,Python的threading模塊有個current_thread()函數,它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創建時指定,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給線程命名爲Thread-1,Thread-2……
Lock
多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
用法示例:
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
當多個線程同時執行lock.acquire()時,只有一個線程能成功地獲取鎖,然後繼續執行代碼,其他線程就繼續等待直到獲得鎖爲止。
注:此處try...finally
用於保證即使有異常也要釋放鎖,防止其他線程持續等待。
因爲Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先獲得GIL鎖,然後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行,即使100個線程跑在100核CPU上,也只能用到1個核。
python多線程無法利用多核的優勢,就是由於GIL的存在,不過也不用擔心,利用多進程可以避免這種問題。
ThreadLocal
爲了解決多線程之間數據不方便共享,創建局部變量層層傳遞又太麻煩的問題,引入了ThreadLocal:
import threading
# 創建全局ThreadLocal對象:
local_school = threading.local()
def process_student():
print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)
def process_thread(name):
# 綁定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
結果如下:
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)
全局變量local_school就是一個ThreadLocal對象,每個Thread對它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內部會處理。
網絡編程
TCP編程
客戶端:
# -*- coding: utf-8 -*-
# 導入socket
import socket
import codecs
# 創建一個socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接
s.connect(('www.baidu.com', 80))
# 發送數據
s.send('GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
# 接收數據
buffer = []
while True:
#每次最多接受1K字節
d = s.recv(1024).decode('utf-8')
if d:
buffer.append(d)
else:
break
data = ''.join(buffer)
#關閉連接
s.close()
header, html = data.split('\r\n\r\n', 1)
print header
# 把接收的數據寫入文件
with codecs.open('baidu.html', 'w' , 'utf-8') as f:
f.write(html)
創建Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定爲AF_INET6。SOCK_STREAM指定使用面向流的TCP協議,這樣,一個Socket對象就創建成功,但是還沒有建立連接。
每種小於1024的端口都有固定作用。80用於網絡服務。
connect()的參數是一個元組,包括服務器IP和端口號。
接收數據時,調用recv(max)方法,一次最多接收指定的字節數,因此,在一個while循環中反覆接收,直到recv()返回空數據,表示接收完畢,退出循環。
當我們接收完數據後,調用close()方法關閉Socket,這樣,一次完整的網絡通信就結束了。
服務端:
服務器進程首先要綁定一個端口並監聽來自其他客戶端的連接。如果某個客戶端連接過來了,服務器就與該客戶端建立Socket連接,隨後的通信就靠這個Socket連接了。
所以,服務器會打開固定端口(比如80)監聽,每來一個客戶端連接,就創建該Socket連接。由於服務器會有大量來自客戶端的連接,所以,服務器要能夠區分一個Socket連接是和哪個客戶端綁定的。一個Socket依賴4項:服務器地址、服務器端口、客戶端地址、客戶端端口來唯一確定一個Socket。
但是服務器還需要同時響應多個客戶端的請求,所以,每個連接都需要一個新的進程或者新的線程來處理,否則,服務器一次就只能服務一個客戶端了。
import socket,threading,time
# 創建一個socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 監聽端口
s.bind(('127.0.0.1' , 9999))
s.listen(5)
print 'Waiting fro connection...'
def tcplink(sock, addr):
print 'Accept new connection from %s:%s...' % addr
sock.send('Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if data == 'exit' or not data:
break
sock.send('Hello, %s!' % data)
sock.close()
print 'Connection from %s:%s closed.' % addr
while True:
# 接受一個新連接:
sock , addr = s.accept()
# 創建新線程處理TCP連接:
t = threading.Thread(target=tcplink, args = (sock, addr))
t.start()
UDP編程
UDP的使用與TCP類似,但是不需要建立連接。此外,服務器綁定UDP端口和TCP端口互不衝突,也就是說,UDP的9999端口與TCP的9999端口可以各自綁定。
服務器端:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定端口:
s.bind(('127.0.0.1', 9999))
print 'Bind UDP on 9999...'
while True:
# 接收數據:
data, addr = s.recvfrom(1024)
print 'Received from %s:%s.' % addr
s.sendto('Hello, %s!' % data, addr)
recvfrom()
方法返回數據和客戶端的地址與端口,這樣,服務器收到數據後,直接調用sendto()
就可以把數據用UDP發給客戶端。SOCK_DGRAM代表用UDP方式傳輸。
客戶端:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['Michael', 'Tracy', 'Sarah']:
# 發送數據:
s.sendto(data, ('127.0.0.1', 9999))
# 接收數據:
print s.recv(1024)
s.close()
此處通過sendto
發送,recv
接收.
以上是關於python面向對象,IO,多線程,異常處理,網絡編程的基礎內容,其他內容後面有機會再補充。