此文为学习廖雪峰的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,多线程,异常处理,网络编程的基础内容,其他内容后面有机会再补充。