上学期看视频记得,也没学到多少,目前打算一边通过《Python学习手册 第四版》提高核心语法(太厚了 噗),一边学习Python Web开发
然后这里的多任务编程和网络编程是暑假学的
5. 函数
5.1 函数的参数
### 可变对象按引用传递,不可变对象按引用传递
# 避免对可变参数的修改:
L = [1,2]
change(L[:]) #
# 或
def change(l):
l = l[:]
###【默认参数】【必须是不可变对象】
def power(x,n=2):
return x**n
power(2) #=>4
power(2,3) #=>8
###【可变参数】(元祖)
def sum(*nums): #可以传入0到N个参数,函数内部接收到的是tuple
s=0
for x in nums:
s+=x
return s
###【关键字参数】(字典)
def preson(name,age,**kw): #可以传入0个或多个参数
print('name:',name,'age:',age,'other',kw)
person('bin',19,city='zz',job='stu') #=> name:bin age:19 other:{'city':'zz','job':'stu'}
###【元祖字典的】【拆包】
a=(1,2,3)
def func(*args):
print(args)
func(a) # => ((1,2,3),) 没有*当作一个值,有*拆成N个值传入
func(*a) # => (1,2,3) 【拆包,拆成一个一个的值】 字典 传入 **a
5.2 函数式编程
5.2.1 匿名函数
>>> func = lambda x : x*2 #分号前是参数,分号后是表达式,不用return,自动返回后面的值
>>> func(2) #=> 4
###【简单应用,字典排序,匿名函数作实参】
info = [{'name':'bin','age':19},{'name':'jin','age':19},{'son':'sname','age':1}]
info.sort(key=lambda x:x['name'])
print(info)
5.2.2 高阶函数
就是函数名可以作为参数使用
### map(func,seq) 将函数func作用于序列seq的每个元素,并返回一个迭代器
>>> list(map(lambda x:x*2,[1,2,3])) # => [2,4,6]
### reduce(func,seq) 把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
###reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
###filter(func,seq) filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
def not_empty(s): #把列表的空字符串去掉
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
###sorted(seq,key=None,reverse=False)
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
#还可以接收一个key函数来实现自定义的排序,key作用于每一个元素,并按作用后的seq排序
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
5.3 作用域
a=1
def aa():
global a #如果没有global a的话,想要在函数中对a这个全局变量修改,仅仅a=0还不够,因为a是个与全局
a=0 #变量相同的局部变量,需要用 global a将他声明为全局变量,然后修改
aa()
print(a) # => 0
##如果列表和字典当作全局变量时,可以不用global,直接在函数内部修改。因为列表字典是可变类型可以直接修改
l=[1,2,3]
def a():
l.append(4)
a()
print(l) #=> [1,2,3,4]
###---------
a=100
def test(a):
a+=a
print(a) #=>200 没用global,函数外a不变
#-----------
a=[100]
def test(b): #b引用a指向的对象,如果对象是可变类型,下面可以修改全局变量,否则的话,重新指向复制的新的对象
b+=b #=> 函数外:a => [100,100],
b=b+b #=> [100]+[100]==>[100,100] 函数外:a => [100]。因为这里右侧新建了一个对象,b作为局部变量重新引用了一个新的对象。
#----对象名搜寻顺序----
#locals -> enclosing function -> globals -> builtins
#局部 闭包的外函数的局部变量 全局 内建
5.4 闭包
闭包就是内部函数引用了外部函数的临时变量,并且外部函数返回了内部函数的引用
def outer(x):
def wrapper(y):
print(x+y)
return wrapper
a = outer(1)#a现在是wrapper指向函数的引用
a(1)#2
a(2)#3
###【闭包无法修改外部函数的局部变量】
def outer():
n = 1
def wrapper():
n -= 1 #error,
n = 2 #相当于新定义了一个局部变量 n = 2
return wrapper
##通过nonlocal修改外部函数局部变量
def outer():
n = 1 #将n定义为列表也可以,解决这个问题
def wrapper():
nonlocal n
n -= 1
print(n)
return wrapper
a = outer()
a()#输出0
###闭包执行完成后,仍然能保持住当前的执行环境
a()#输出-1
a()#输出-2
###【注意事项】
# 当闭包内引用了一个后期会发生变化变量时
# 1.
def outer():
a = 1
def inner():
print(a)
a = 2
return inner
a = outer()
a()#输出了2,因为inner后又修改了
## 2.
def outer():
l = []
for i in range(1,4):
def inner():
return i*i
l.apend(inner)
return l
f1,f2,f3 = outer()
print(f1(),f2(),f3())#9,9,9
#原因就在于返回的函数引用了变量i,但返回的函数并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9
#[解决:]
def outer():
def f(j):
def inner():
return j*j
return inner
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行(是一个数值,而不是函数),因此i的当前值被传入f()
return fs
5.5 装饰器
就是不修改原函数的情况下,添加新的功能
###【装饰器原理】
def w1(func):
def wrapper():
print('这是新加功能')
func()
return wrapper
def f1():
print('---f1---')
f1 = w1(f1)
f1() # 将会添加一个print
### 【使用@】
@w1 #在定义的函数面前使用 @包装函数。代码执行到这会立即执行函数w1。
def f2():
print('---f2---')
f2() #将会添加一个print
###【添加多个装饰器】
def w1(func):
def wrapper():
print('w1')
func()
return wrapper
def w2(func):
def wrapper():
print('w2')
func()
return wrapper
@w2
@w1 #这样就添加了两个新功能;先w1装饰,然后w2再装饰,即w2装饰的是w1装饰的结果
def f():
print('test...')
f()
###【带有参数的函数的装饰器】
def decorator(func):
def wrapper(a,b):
print('新功能')
func(a,b) #这里和wrapper添加参数
return wrapper
@decorator
def sum(a,b):
print('sum ',a+b)
sum(1,2)
###【不定长参数的装饰器】
def decorator(func):
def wrapper(*args,**kwd): #这样就对没有参数、有n个参数,的函数都能使用了
print('新功能')
func(*args,**kwd) #这里和wrapper添加参数
return wrapper
@decorator
def sum(a,b):
print('sum ',a+b)
@decorator
def sum2(a,b,c):
print('sum ',a+b+c)
sum(1,2)
sum2(1,2,3)
###【装饰有返回值的函数】
def decorator(func):
def wrapper(*args,**kwd):
print('新功能')
s = func(*args,**kwd)
return s
return wrapper
@decorator
def sum(a,b):
return a+b
print(sum(1,2))
###【通用装饰器,即在装饰器加上不定长参数和返回值】
def log(func):
def wrapper(*args,**kwd):
print('日志已记录')
x = func(*args,**kwd)
return x
return wrapper
@log
def test():
print('test...')
@log
def sum(a,b):
print('sum',a+b)
test()
#日志已记录
#test...
sum(1,2)
#日志已记录
#sum 3
###【带有参数的装饰器】
def user_log(text):
def decorater(func):
def wrapper(*args,**kwd):
print('用户 %s 的日志已记录'.format(text))
x = func(*args,**kwd)
return x
return wrapper
return decorater
@user_log('bin')
def test():
print('test...')
test()
#相当于:test = user_log('bin')(test)
###【类当做装饰器】
pass
6. python动态类型
>>> id(obj) #获得对象的地址
###【引用】【任何东西,只要在等号右边,或者参数传入,就是引用】
>>> a = 3 #1.创建一个对象代表值3 2.创建一个变量a 3.变量a引用对象3,相当于a保存了对象3的地址。
###【变量名没有类型】
>>> a = 1
>>> a = 'aa' #a仅仅是变量名而已,没有类型,有类型的是引用的对象,对象包含了头部类型信息和值。
###【对象的垃圾回收】
>>> a = 1
>>> a = 'b'
>>> a = [1,2,3] #python中每个对象有个计数器,记录了当前指向该对象的引用的数目,一但数目被设为0,python就会回收该对象的内存空间
###【共享引用】
>>> a = 3
>>> b = a #a和b都引用了对象3
>>> a = 'x' #a引用了新建的字符串对象'x',不会修改b的值,因为b还引用着对象3
###【共享引用和在原处修改】
>>> L1 = [1,2,3]
>>> L2 = L1
>>> L1[1] = 0
>>> L2
[0,2,3] #L2也跟着改变了,因为改变了L1引用对象的一部分,还是原来那个对象
###【is 和 == 的区别:is判断的是引用的指针是否相等,==判断的是对象的值是否相等】
###【引用 VS 拷贝】==【浅拷贝 VS 深拷贝】
>>> x = [1,2,3]
>>> y = x #如果y简单引用x的话,那么x改变的话y也会变
>>> y = x[:] #这样就实现了拷贝一个新的对象,修改x的某个元素,y不会变。字典的话用dict.copy()实现拷贝
>>> import copy
>>> y = copy.deepcopy(x) #深拷贝
###【可变类型与不可变类型】
###不可变类型:数字类型、字符串类型、元祖
7. 文件与IO
7.1 文件打开读写
>>> f = open('filename','mode',encoding='gbk/utf-8',errors='ignore') #encoding用于指定打开文件的编码,errors='ignore'表示打开的文件有非法编码的字符时,忽略错误,以上针对文本文件
>>> f.read() #读出所有,返回字符串
>>> f.read() #返回空,因为上一次文件指针,指向了文件尾
>>> f.read(n) #读取n个字符
>>> f.write('string') #向文件中写入字符串。要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。
>>> f.close() #关闭文件。可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
>>> f.readline() #读取一行
>>> f.readlines() #读取文件的所有行,返回字符串的列表,每行作为一个元素
>>> f.writelines(sequence) #向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。
###【读取大文件】
while True:
content = old_file.read(1024)
if len(content)==0:
break
new_file.write(content)
old_file.close()
new_file.close()
###【定位读写】
>>> f.seek(offset,[0:从文件开头,1:从当前位置,2:从文件末尾])
>>> t.tell() #返回文件指针位置
7.2 文件操作 (os模块 和 shutil模块)
###【os模块】
import os
os.rename('xxx','yyy') #将文件名xxx改为yyy,不指定路径,则时当前工作目录
os.remove('xxx') #删除xxx,不指定路径,则时当前工作目录
os.mkdir('xxx') #创建文件夹xxx
os.rmdir('xxx') #删除文件夹xxx,只能删除空的文件夹。
os.removedirs(path) #递归删除没有文件的文件夹
os.getcwd() #返回当前工作路径
os.chdir('xxx') #将当前工作路径改为xxx
os.listdir('xxx') #返回路径xxx的所有文件名和文件夹名
os.unlink(path) #用于删除文件,如果文件是一个目录则返回一个错误。
os.path.isfile('xxx') #判断xxx是否是文件
os.path.isdir('xxx') #判断xxx是否是目录
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')
#os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便:
>>> os.path.splitext('/path/to/file.txt') #获得扩展名,返回元组
('/path/to/file', '.txt')
>>> os.path.join('/etc','pass.txt')
'/etc/pass.txt'
#这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
#列出当前目录所有py文件
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
#列出当前目录所有文件夹
###【shutil模块】
7.3 序列化
我们把变量从内存中变成可存储或传输的过程称之为序列化, 序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。把变量内容从序列化的对象重新读到内存里称之为反序列化。
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
pickle.dumps()
方法把任意对象序列化成一个bytes
,然后,就可以把这个bytes
写入文件。或者用另一个方法pickle.dump()
直接把对象序列化后写入一个file-like Object:
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes
,然后用pickle.loads()
方法反序列化出对象,也可以直接用pickle.load()
方法从一个file-like Object
中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}
8. 迭代器、生成器
8.1 迭代器
###【可迭代对象 iterable】【可以用for便利的对象】
#1. 集合数据类型:list tuple dict set str等
#2. generator,包括生成器和带 yield的 generator function
#使用isinstance()判断是否可以迭代
>>> from collections import Iterable
>>> isinstance([],Iterable) #is instance(实例) isinstance()用于判断是不是某个对象
True
###【迭代器 iterator】【可以被next()函数调用并不断返回下一个值的对象成为迭代器,可以记住上次访问的位置】
# 生成器一定是迭代器,迭代器一定是可迭代对象,但可迭代对象不一定是迭代器
>>> isinstance([],Iterator)
False
>>> isinstance((x for x in range(10)),Iterator)
True
# a = iter(obj) 把obj转化成迭代器
8.2 生成器(generator)
生成器保存的是算法,用的时候才生成值(惰性),变循环边计算,节省内存
###【生成器表达式】
>>> a = (x for x in range(10))
<generator object <genexpr> at 0x0000027C58926A40>
>>> next(a)
0
>>> next(a)
1
>>> next(a) #generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个
2 #元素,没有更多的元素时,抛出StopIteration的错误。
>>> next(a) #几乎不用next(),用for,不用担心异常
StopIteration #异常
###【yield 加 函数 生成器】
# 函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,【遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行】。
def createNum(max):
for x in range(max):
#print(x)
yield x
f = createNum(6) #createNum相当于一个生成器,要使用它,要先生成一个生成器对象
def test():
print('---1---')
yield 1
print('---2---')
yield 2
print('---3---')
yield 3
a = test()
next(a)#---1--- 调用内部的__next__()方法
next(a)#---2---
next(a)#---3--- 每次执行后,都会保存执行到的位置
b = test()
for x in b: #可以用for迭代。其实for每次调用的是生成器内部的__next__() 方法
pass
###【send()方法】
def test():
i = 0
while i<5:
temp = yield i
print(temp)
i += 1
t = test()
>>> t.__next__()
0 # 执行到yield停止,yield返回0
>>> t.__next__()
None # 接着yield执行(从等号开始执行),temp = 后面没东西了,print(temp) => None
1
>>> t.send('haha') # 将yield的返回值设置为haha,和next一样可以让生成器向下执行一次,【send(Node)和next()一样】
9. 面向对象
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤 。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。
再比如我要把这个五子棋游戏改为围棋游戏,如果你是面向过程设计,那么五子棋的规则就分布在了你的程序的每一个角落,要改动还不如重写。但是如果你当初就是面向对象的设计,那么你只用改动规则对象就可以了,五子棋和围棋的区别不就是规则吗?(当然棋盘大小好像也不一样,但是你会觉得这是一个难题吗?直接在棋盘对象中进行一番小改动就可以了。)而下棋的大致步骤从面向对象的角度来看没有任何变化。
当然,要达到改动只是局部的需要设计的人有足够的经验,使用对象不能保证你的程序就是面向对象,初学者或者很蹩脚的程序员很可能以面向对象之虚而行面向过程之实,这样设计出来的所谓面向对象的程序很难有良好的可移植性和可扩展性。
9.1 类、对象、属性、方法
###【创建】
class Cat: #类名,建议大驼峰法【类对象】
#属性
#方法
def eat(self):
print('eating...')
def pname(self): #创建一个对象,实际上对象里只有实例属性,没有父类方法,因为方法都
print(self.name) #一样,没必要再生成一份。但多了一个属性,指向了父类。调用方法时
#去父类找,执行,self指向实例对象
tom = Cat() #【实例对象】创建一个对象,实际上是向内存申请一块内存,tom是对象的引用,实际上对象中有一个__class__属性指向对应的类
tom.eat() #执行对象的方法
tom.name = '汤姆' #给对象添加属性
print(tom.name) #获取对象的属性
tom.pname() # => 汤姆
###【self】 【类中每个方法必须有个参数指向对象本身,self自动引用这个对象,self命名随意】
lanmao = Cat()
lanmao.name = '蓝猫'
lanmao.pname() # => 蓝猫 【自动的传入参数-对象本身,相当于lanmao.pname(lanmao)】
###【__init__ 方法 魔法方法 两个下划线】【初始化对象】
class Cat: #创建一个对象就会自动调用__init__这个方法
def __init__(self,new_name,new_age): #初始化时传入参数
#print('__init__') #创建一个对象,自动执行
self.name = new_name #【实例属性】
self.age = new_age
tom = Cat(new_name,new_age) #传入__init__的参数,然后内部给对象的属性赋值
###【给类添加说明文档】
class test(object):
'''
这是说明文档的形式
'''
pass
t = test()
t.__doc__
9.2 私有属性、私有方法
###【私有属性】【用方法设置对象的属性,保护属性】
class Dog:
def __init__(self,name,age):
self.__name = name #在对象中,以两个下划线开头的属性是私有属性,不可外部访问(会出错)
self.__age = age #不能这样:__xxx__,是特殊变量,特殊变量是可以直接访问。
def get_age(self):
print(self.__name)
def set_age(self,new_age): #通过方法设置属性,避免传入无效值、非法值
self.age = new_age
dog1 = Dog('xx',10)
print(dog1.name) # => error
###【私有方法】【保护方法,只有满足一定条件用另一个方法调用】
class Msg:
#私有方法,以两个下划线开头
def __send_msg(self): #实际方法
print('发送短息...')
#公有方法
def send_msg(self): #外部包装的方法
if money > 100: #如果钱够了才调用内部的隐藏方法
self.__send_msg() #对象内调用方法
else:
print('钱呢')
#属性或方法前加一个下划线,表示在模块内可以用,但不能用from module import * 调用,import可以
###【property() @property】【负责把一个方法变成属性调用】
#【既能检查参数,又可以用类似属性这样简单的方式来访问类的变量】
#property(fget,fset,fdel,doc),获得一个属性值时调用get(),设置属性值调用fset(),删除调用fdel()
class Stu(object):
def __init__(self):
self.__age = 10
def setAge(self,newAge):
self.__age = newAge
def getAge(self):
return self.__age
age = property(getAge, setAge)#顺序不能变,第一个参数用于获取,第二个用于设置
s = Stu()
print(s.age)#10 简介调用getAge()
a.age = 20#简介调用setAge()
print(s.age)#20
# @property
class Stu(object):
def __init__(self):
self.__age = 10
@property #【相当于把一个getter()方法变成属性,属性可读】
def age(self):
return __age
@age.setter #【@property本身又创建了另一个装饰器@score.setter,属性可写】
def age(self,newAge):
self.__age = newAge
@property
def score(self):
return 80
#----上面age可读写,score只可读
s = Stu()
print(s.age) #相当于调用getter
s.age = 20 #相当于调用setter
print(s.age)
#-----@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
9.3 继承、重写、多态
###【继承】
class Animal:
def eat(self):
print('eating...')
def run(self):
print('running...')
class Dog(Animal): #在类名后的括号里加上另一个类,表示继承另一个类,继承它的全部属性、方法
def bark(self):
print('wang wang ...')
class XiaoTQ(Dog):
def fly(self):
print('flying...')
g = XiaoTQ()
g.eat() # => eating...
g.bark() #继承自父类 => wang wang ...
g.fly() #继承自爷爷类 => flying...
###【重写】
class XiaoTQ(Dog):
def fly(self):
print('flying...')
def bark(self): #对继承自父类的方法重写
#print('kuang jiao...')
#Dog.bark(self) #第一种调用被重写的方法
super(Dog,self).bark() #调用被重写的父类的方法
g = XiaoTQ()
g.bark() # => wang wang ...
#
###【私有属性、方法在继承中的表现】
class A:
def __init__(self):
self.num1 = 100
self.__num2 = 200
def test1(self):
print('test1...')
def __test2(self):
print('test2...')
def test3(self):
self.__test2()
print(self.__num2)
class B(A):
def test4(self):
self.__test2()
print(self.__num2)
b = B()
b.test1() # => test1...
#【私有方法不会被继承下来,同理私有属性也不会被继承下来】
b.__test2() # => AttributeError: 'B' object has no attribute '__test2'
b.test3() # 可以调用父类的私有方法、属性
b.test4() # => AttributeError
#【如果调用的是继承的父类的公有方法,可以在这个公有方法中访问父类中的私有属性和私有方法。如果在子类中实现了一个公有方法,那么这个方法是不能调用继承的父类中的私有方法和私有属性】
###【多继承】
class Base(object): #顶层类,可以不继承(经典类)。也可以加个(object),新式类。所有类最终都继承object
def test(self):
print('base...')
class A(Base):
def test1(self):
print('test1...')
class B(Base):
def test2(self):
print('test2...')
class C(A,B): #多继承,继承多个类,相当于杂交
pass
c = C()
c.test()
c.test1()
c.test2()
###【多态】
def Dog(object):
def print_self(self):
print('I am a Dog')
def XiaoTQ(Dog):
def print_self(self):
print('I am XiaoTQ')
def introduce(temp): #多态:写完一个方法,仅仅知道调用一个方法,但不知道调用哪个类的方法
temp.print_self()
d = Dog()
x = XiaoTQ()
introduce(d) #调用Dod的print_self()方法
introduce(x) #调用XiaoTQ的print_self()方法
9.4 类属性、实例属性,类方法、实例方法
###【类属性、实例属性】
class Tool(object):
num = 0 #雷属性用于在所有对象间共享数据。类属性所有对象共用,实例属性不共享
def __init__(self, new_name):
self.name = new_name
Tool.num += 1 #加【Tool】
tool1 = Tool('tool1')
tool2 = Tool('tool2')
tool3 = Tool('tool3')
print(Tool.num) # => 3
#创建一个对象,实际上对象里只有实例属性,没有父类方法,因为方法都一样,没必要再生成一份。但多了一个属性 __class__,指向了父类。调用方法时去父类找并执行,但self指向实例对象,即作用在实例对象
###【类方法、实例方法、静态方法】
class Game(object):
num = 0
#类方法【用来改类属性】
@classmethod #加这个
def add_num(cls): #和self一样,不必须是这个名字
cls.num = 100
#静态方法【不用来操作类属性和实例属性】
@staticmethod
def print_info(): #参数可以没有self
print('xxx')
game = Game() #可以通过类的名字调用类方法,也可以通过对象的名字
game.add_num() # => 100
game.print_info() #可以通过类的名字调用静态方法,也可以通过对象的名字
###【__slots__】【作用是限制类实例能够添加哪些属性,仅对当前类其动作,对子类不起作用】
class Person(object):
__slots__ = ['name','age']
p = Person()
p.name = 'bin'
p.age = '19'
p.xxx = 'yyy' #error
###【类的内置属性】
__dict__: 以字典的形式显示类的成员的组成
__doc__: 获取类的文档信息
__name__: 获取累的名称,如果在模块中使用,获得模块的名称
__bases__: 获取某个类的所有父类,以元组形式显示
9.5 魔法方法
###【__init__方法】【初始化对象】
###【__str__方法】【当把对象以字符串输出时,自动调用这个方法】
class Cat:
def __str__(self):
return 'string' #返回描述信息
tom = Cat()
print(tom) #没有__str__方法时,输出<__main__.cat object at 0x....>
print(tom) #有__str__方法时,输出string
###【__del__ 方法】【销毁对象时调用,比如游戏临死前的补救】
class Hero:
def __del__(self): #当对象的引用为零时,这个对象就会被python解释器销毁
print('呃,死了')
hero = Hero() #英雄出生
del hero #英雄死亡 => 呃,死了
del everthing #程序退出,世界末日,销毁所有 => 呃,死了
#如果没有在程序运行期间销毁对象,则在程序退出时,python销毁所有对象,这个方法也会被调用
###【__new__方法】【实际上是__new__方法创建的对象】
class Dog(object):
def __init__(self):
print('init 方法')
def __new__(cls): #参数是类,即Dog
print('new 方法')
#return object.__new__(cls) #返回一个对象,给__init__(self)
g = Dog() # => new 方法。没有init,因为没有对象
10. 异常处理
错误:1. 语法错误,自己写错的 2. 逻辑错误,代码的逻辑错误。
异常:代码执行过程中未知的错误
###【系统异常】
# 除零异常:ZeroDivisionError
# 名称异常:NameError
# 类型异常:TypeError
# 索引异常:IndexError
# 键异常:KeyError 访问字典里不存在的键
# 值异常:ValueError int('abc')
# 属性异常:AttributeError 访问对象里不存在的属性
# 迭代器异常:StopIteration
###【异常处理基本操作】【异常处理保证程序出错后可以正常运行下面的部分】
try:
print(num)
except Exception: #接收所有错误,也可以没有Exception
print('NameError')
#----------------------------
try:
open('xxx.xxx')
except NameError: #错误类型不匹配继续匹配下一个except
print('NameError')
except FileNotFoundError:
print('FileNotFoundError')
#----------------------------
try:
print(1/0)
except (NameError,FileNotFound): #匹配多个异常,用元祖括起来
print('error')
except Exception as MyError: #把异常给MyError
print(MyError)
else: #【没有异常才会执行】
print('no error')
finally: #有没有异常都会执行【用于清理,例如文件打开无论失败成功,都关闭文件】
print('finally')
###【抛出自定义异常】
class MyError(Exception): #自定义异常继承自 Exception
def __init__(self, length):
self.length = length
try:
s = input('input:')
if len(s) < 3:
raise MyError(len(s)) #抛出异常类
except MyError as err: #err作为异常对象
print('MyError:length = %d' % err.length)
###【异常处理中抛出异常】
try:
pass
except:
if xxx:
print('yyy')
else:
raise('xxx') #抛给系统 例如:保安处理不了的事,给警察
###【异常的传递】
#出现异常时,一层一层向上传递,直到传给系统,或者except
###【使用 with 语句】
# 作用:适用于执行某一段代码A之前,进行预处理;执行代码A结束后,进行清理操作。不管出现了什么异常,最终都要执行一些清理操作。
with content_expression [as target(s)]:
with_body
# content_expression 是一个上下文表达式,执行后返回一个上下文管理器,上下文管理器是一个实现了上下文协议的对象,对象内要实现 __enter__() 和 __exit__() 方法
#【执行流程】:执行 content_expression 后,返回一个上下文管理器对象;调用上下文管理器的 __enter__()方法,如果写了 as target 语句,则把__enter__()方法的返回值赋值给target;执行语句体;执行上下文管理器的__exit__()方法(保证资源一定被清理)
# 运行中如果发生了异常,那么将会把异常的类型,值和追踪传递给__exit__()方法。如果__exit__()方法返回值为true,那么这个异常将会被抑制,否则这个异常将会被重新抛出。
11. 模块 和 包
模块就是个 .py 文件,里面有函数,类等等
###【模块的导入】【导入模块的实质是,把模块里的所有代码在原本的命名空间执行一遍】
>>> import module #调用时:module.test()
>>> import module1,module2,modele3 #导入多个模块
>>> from module import test #从模块中导入某个类、变量、函数。调用时直接:test()
>>> from module import * #从模块中导入所有类、变量、函数。直接调用
>>> from module import ThisIsALongModule as test
>>> import ThisIsALongModule as test #别名,模块名过长时使用
>>> import module1
>>> import module2 #module1 module2 都有一个函数test()
>>> test() #调用时,使用的是module2的test()。
###【模块的制作】
#当前目录文件: ./main.py ./module.py
# module.py
def test():
print('test')
# main.py
import module
module.test()
#【调用时,模块目录会生成 ./__pycache__/module.cpython-36.pyc】
# 这是模块翻译成的二进制字节码,CPU可以执行。先翻译好,以后调用就不用翻译了,节约时间。
# cpython-36.pyc 表示只有C解释器的3.6版本才能使用
###【__name__】
# module.py
def test():
print('test')
print(__name__) #在main.py中当成模块调用,输出:module
# main.py
import module
def main():
module.test()
print(__name__) #不当成模块调用,输出:__main__
if __name__ = '__main__': #不被当成模块执行时,才调用。可用于调试。
main()
###【__all__】【限制 from module import * 导入的内容】
# module.py
__all__ = ['test1', 'test2'] #列表,元素是函数或类名
def test1():
pass
def test2():
pass
def test3():
pass
# main.py
from module.py import *
test1()
test2()
test3() #error
###【包】
## ./main.py ./Bag/module.py
# ./Bag/__init__.py【py3.3后,可有可无,加了这个文件就代表Bag这个文件夹是包】
# module.py
def test1():
print('test1')
# __init__.py
__all__ = ['module'] #表示 from Bag import * 能导入什么模块
from . import module #表示可以在main.py 用import Bag; Bag.module.test1() 调用
print('__init__') #只要调用,就会执行。调用包会把__init__.py的代码都执行一遍
# main.py
from Bag import * #条件:__all__ = ['module'] 调用:module.test1()
import Bag #条件:from . import module 调用:Bag.module.test1()
import Bag.module # 条件:无 调用:Bag.module.test1()
from Bag import xxx #条件无
###【模块的制作发布安装】
#占坑
12. 多任务
12.1 多进程
进程 就是正在运行着的程序
进程的执行顺序不确定,有操作系统调度决定
12.1.2 使用 os.fork
###【fork 创建子进程】不支持Windows,是类Unix的系统调用接口
import os
import time
ret = os.fork() # 执行到这,创建一个新进程,新进程的代码也执行到下一行,即和父进程执行的位置一样
if ret == 0: # fork() 分别在子进程和父进程返回,子进程返回0
while True:
print("这是子进程") #他们拥有相同的代码,然后if判断,子进程返回0,执行这一句
time.sleep(1)
else: #父进程返回子进程的PID
while True:
print("这是父进程")
time.sleep(1)
###【getpid()获得PID getppid()过得父进程PID】
import os
ret = os.fork()
print('ret: ',ret)
if ret = 0:
print('子进程 getpid():',os.getpid(),' getppid():',os.getppid())
else:
print('父进程 getpid():',os.getpid())
#【返回】
#ret: 0
#子进程 getpid(): 2538 getppid(): 2537
#ret: 2538
#父进程 getpid(): 2537
###【父子进程执行的先后顺序】【父进程不会等待子进程】
import os
import time
ret = os.fork()
if ret == 0:
print("-----子进程-----")
time.sleep(1) #子进程休眠一秒,父进程早已执行完毕,即python3进程结束。
print("-----子进程结束-----") #python3结束,所以终端出现了提示符。
else: #但是子进程没有结束。所以在终端提示符后又输出了内容。
print("-----父进程-----")
#-----子进程-----
#-----父进程-----
#【bin21st@bin21st:~/test$】 -----子进程结束-----
# 方括号内容为终端提示符
###【全局变量在多个进程中不共享】
#两个进程虽然有一样的代码,但是是个人的,没有关系;进程之间数据不共享。
###【多次 fork() 的问题】
import os
ret1 = os.fork()
if ret1 == 0:
print("子进程")
else:
print("父进程")
ret2 = os.fork()
if ret1 == 0:
if ret2 == 0:
print("子-生成的子进程")
else:
print("子-作为父进程")
else:
if ret2 == 0:
print("父-生成的子进程")
else:
print("父-作为父进程")
12.1.2 使用 multiprocessing
###【Process 创建子进程】【Process 是一个类】
from multiprocessing import Process
def son(a):
print("子进程 ",a)
if __name__ == "__main__": # Windows上,因为安全问题,要使用这种方法,调用Process
print("主进程开始")
s = Process(target=son,args=(1,)) #target参数是要执行的函数名,args传入要执行函数的参数元祖,kwargs参数,允许传入字典形式的参数
s.start() #让这个进程开始执行
print("主进程结束")
# 最后输出为: python主进程不会等待子进程
#主进程开始
#主进程结束
#子进程
#【使用 join()】
if __name__ == "__main__":
print("主进程开始")
p = Process(target=son,args=(1,))
p.start()
p.join() #阻塞当前进程(主进程),直到调用join方法的那个进程(子进程)执行完,再继续执行当前进程(主进程)。有个可选timeout参数,表示阻塞时间,超过这个时间,子进程还没有执行完毕,主进程就不管他了,继续执行。
print("主进程结束")
#最后输出为: 因为主进程被阻塞,所以会等待子进程执行完毕再执行主进程
#主进程开始
#子进程
#主进程结束
#【这样的话】
def son1:
print("son1")
def son2:
print("son2")
time.sleep(1)
if __name__ == "__main__":
print("主进程开始")
p1 = Process(target=son1)
p2 = Process(target=son2)
p1.start() #p1开始运行
p2.start() #p2也开始运行,两个进程,在后台同时运行
p1.join()
#p2不join
print("主进程结束")
#输出: 只阻塞了son1,所以son1结束了,主进程就结束
#主进程开始
#son1
#主进程结束
#son2
#【p.is_alive()】 判断子进程是否结束
#【p.pid】获得子进程PID
#【p.run()】不指定target,就默认运行内部的run()方法
#【multiprocessing.cpu_count()】获得CPU核心数
#【multiprocessing.active_children()】返回子进程列表,multiprocessing.active_children()[0].pid,第一个子进程的PID
#【multiprocessing.current_process().pid】当前进程
#【p.terminate()】终止子进程
###【重写Process类】【封装自己的代码】
class my_Process(Process):
def __init__(self):
Process.__init__(self)
def run(self):
print("子进程")
if __name__ = "__main__":
print("主进程开始")
p = my_Process();
p.start()
p.join()
print("主进程结束")
###【进程锁】
# 当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。
###【进程池 Pool】
#进程池,就好像一个池子,里面有N个进程,要执行什么代码,就放到池子里的一个进程中
# 如果创建的子进程数量不多时,可以用Process类创建多个进程。如果要是创建数量非常多的子进程时,手动创建工作量就很大,此时就可以用multiprocessing提供的Pool。
#初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool时,如果池还未满,那么就会创建一个新的进程支持该请求,如果池中的进程数已达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。
#pool.terminate() 结束工作进程,不在处理未完成的任务。
from multiprocessing import Pool
import os
import time
def son(i):
print("子进程 %d PID %d" % (i,os.getpid()))
time.sleep(1)
def main():
print("主进程开始")
t1 = time.time()
pool = Pool(5) #池中最多同时运行5个进程,不给参数默认数目是CPU核心数
for i in range(10):
pool.apply_async(son,(i,))
pool.close() #关闭进程池,不能再接受新任务了
pool.join() #主进程阻塞,等待子进程的退出,【join方法要在close或terminate之后使用】
t2 = time.time()
print("主进程结束,总耗时 %.2f 秒" % (t2-t1))
if __name__ == "__main__":
main()
#主进程开始
#子进程 0 PID 14740
#子进程 1 PID 8548
#子进程 2 PID 10436
#...
#主进程结束,总耗时 2.49 秒
###【map()】
from multiprocessing import Pool
import time
def son(i):
print('i: %d' % i)
time.sleep(1)
def main():
pool = Pool(3)
pool.map(son,[i for i in range(10)]) #和apply_async()类似,但可以像map一样吧iterable的每个元素每次作为一个参数传给一个进程
pool.close()
pool.join()
print("主进程结束")
if __name__ == '__main__':
main()
###【进程间通信-队列 Queue】
#就是操作系统开辟了一个空间,可以让各个子进程把信息放到Queue中,也可以把自己需要的信息取走
#队列先进先出,即最先进入的消息,最先出来
#q.empty()判断队列是否为空 q.full()判断队列是否满了 q.qsize()返回消息数目
#q.put(obj,block=True,timeout=None),block是否阻塞,timeout等待时间,超时引发Queue.Full异常
from multiprocessing import Process,Queue
import os
import time
def write(q):
for i in "YYB":
q.put(i) #往队列里面添加消息,任何数据类型
print("PID %d 写入 %s" % (os.getpid(),i))
time.sleep(1)
def read(q):
while True:
if not q.empty(): #不为空才读取消息,防止异常
print("PID %d 读入: %s" % (os.getpid(),q.get()))
time.sleep(1)
else:
break #为空则退出循环
if __name__ == "__main__":
q = Queue() #可以指定一个最大消息数量的参数,不指定则没有上限制。这是方法,返回队列对象。
p1 = Process(target=write,args=(q,))
p2 = Process(target=read,args=(q,))
p1.start()
p1.join() #阻塞主进程,p1执行完,才执行p2
p2.start()
#PID 9220 写入 Y
#PID 9220 写入 Y
#PID 9220 写入 B
#PID 3044 读入: Y
#PID 3044 读入: Y
#PID 3044 读入: B
###【进程池的进程通信】
#只需将 from multiprocessing import Queue 改成 from multiprocessing import Manager
#调用时变成 q = Manager().Queue() #注意Manager() 括号
12.1.3 subprocess
12.2 多线程
###【threading模块】
##【内置函数】
# threading.active_count()
#
#
#
#
###【使用Thread完成多线程】
from threading import Thread
import time
def test(u):
print("线程 %d" % i)
time.sleep(1)
for i in range(5):
t = Thread(target=test,args=(i,))
t.start()
###【重写Thread,自定义类】
class myThread(Thread):
def __init__(self,i):
Thread.__init__(self)
self.i = i
def run(self): #和多进程一样
time.sleep(1)
print("线程 %s %d" % (self.name,self.i)) #name 内置属性,线程名字
for i in range(5):
t = myThread(i)
t.start()
###【线程的执行顺序】
# 系统调度各个线程的顺序是随机的
###【共享全局变量问题】【资源竞争】
# 多个线程都可以共享全局变量
num = 0
def work1():
global num
print("线程1 start num: ",num)
for i in range(1000000):
num += 1
print("线程1 end global: ",num)
def work2():
global num
print("线程2 start num: ",num)
for i in range(1000000):
num += 1
print("线程2 end global: ",num)
t1 = Thread(target=work1)
t1.start()
t2 = Thread(target=work2)
t2.start()
t1.join()
t2.join()
print("主线程 end num: ",num) # 期望输出200W
#线程1 start num: 0
#线程2 start num: 51349
#线程1 end global: 1210366
#线程2 end global: 1216082
#主线程 end num: 1216082
#### 结果却是一百多万,因为存在资源竞争情况。CPU执行时,线程的调用顺序不确定,num = num+1 是两个过程,num先加一、再赋值给num,有可能加完1后(还没赋值),又执行了B线程,B执行完后,A再赋值,相当于B没加。
###【互斥锁】
from threading import Lock,Thread
num = 0
lock = Lock() #创建一把锁
def work1():
global num
print("线程1 start num: ",num)
lock.acquire() #开启锁,当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待锁被释放
for i in range(1000000):
num += 1
lock.release() #释放锁
print("线程1 end global: ",num)
def work2():
global num
print("线程2 start num: ",num)
lock.acquire()
for i in range(1000000):
num += 1
lock.release()
print("线程2 end global: ",num)
t1 = Thread(target=work1)
t1.start()
t2 = Thread(target=work2)
t2.start()
t1.join()
t2.join()
print("主线程 end num: ",num)
#最后输出正确
###【非共享变量】
import threading
def test():
num = 1
name = threading.current_thread().name #获得当前线程名字
if name=="Thread-1":
num -= 1
else:
print("num:",num)
t1 = threading.Thread(target=test)
t2 = threading.Thread(target=test)
t1.start()
t1.join()
t2.start()
#num = 1 ,可以看出两个线程并不共享函数内的变量
###【死锁问题】
# 就是多线程间功效多个资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会发生死锁。
#lock.acquire(blocking=True,timeout=-1)
#blocking=True 如果锁被锁上,则等待锁被释放;为false,则不等待,直接略过
#timeout=-1 等待锁被释放的时间,超过这个时间就不等待
def test1():
if lock1.acquire(): #获得锁成功,返回TRUE
print("test1 获得了 lock1")
time.sleep(1)
if lock2.acquire(): #lock2被test2占用,一直等待
print("test1 获得了 lock2")
lock2.release()
lock1.release()
def test2():
if lock2.acquire():#获得锁成功,返回TRUE
print("test2 获得了 lock2")
time.sleep(1)
if lock1.acquire():#lock1被test1占用,一直等待
print("test2 获得了 lock1")
lock1.release()
lock2.release()
lock1 = Lock()
lock2 = Lock()
t1 = Thread(target=test1)
t2 = Thread(target=test2)
t1.start(); t2.start(); t1.join(); t2.join();
# 这样双方就会无休止的等待,所以添加超时时间可以解决
###【线程同步】
# 同步就是按照预定的先后次序运行。“同”不是指的同时,而是指协同、互相配合
###【生产者消费者模型】
##【queue模块】【专门给线程使用的模块】
#【类】
# queue.Queue(maxsize=0) 构造一个FIFO(先进先出)队列
# queue.LifoQueue(maxsize=0) 构造一个Lifo(后进先出)队列
# 【方法】
# Queue.qsize() 返回队列的大小
# Queue.empty() 判断队列是否为空,返回布尔
# Queue.full() 判断队列是否满了
# Queue.put(item,block=True,timeout=None) item放入队列的内容
# Queue.get(block=True,timeout=None) 取得数据
# Queue.join() 阻塞调用线程,直到队列中所有任务被处理掉
from threading import Thread
from queue import Queue
class Producer(Thread):
def run(self):
count = 0
while True:
if queue.qsize()<100: # 产品不足时要及时生成
for i in range(20):
count += 1
msg = '生成产品 %d' % count
queue.put(msg)
print(msg)
time.sleep(1)
class Consumer(Thread):
def run(self):
while True:
if queue.qsize()>20: # 有足够的产品才消费
for i in range(20):
msg = self.name + "消费了" + queue.get()
print(msg)
time.sleep(2)
queue = Queue() # 依靠queue通信
for i in range(100):
queue.put("初始产品 %d" % i)
p = Producer()
p.start()
c = Consumer()
c.start()
###【ThreadLocal 解决局部变量传参问题】
# 多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,使用全局变量的话就要加锁了。
# 但局部变量的话,函数调用时传参会很麻烦
def main():
num = 1
son2(num)
son3(num)
def son2(num):
sub_son2(num)
def son3(num):
sub_son3(num)
# 使用ThreadLocal
import threading
local_school = threading.local()
def process_student():
std = local_school.student # 获取当前线程关联的student:
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
local_school.student = name # 绑定ThreadLocal的student:
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()
#每个线程对它都可以读写student属性,但互不影响。可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。ThreadLocal类似字典。
###【GIL】
#Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
12.3 协程
13. 网络编程
13.1 socket
import socket
socket.socket(socket_family, socket_type) #创建一个套接字
# socket_family 可以是 AF_INET(ipv4) AF_INET6(ipv6)
# socket_type 可以是 socket_STREAM(TCP) socket_DGRAM(UDP)
13.2 UDP 编程
from socket import *
udpSocket = socket(AF_INET,SOCK_DGRAM) # UDP套接字,随机获得一个端口
udpSocket.sendto(byte,adress) # 发送数据,adress必须是元祖类型 如:('127.0.0.1',1234);
udpSocket.bind( ('127.0.0.1',9999) ) #绑定 IP 和端口,IP可为空字符串(自动获得可用IP)
udpSocket.recvfrom(buffersize) #接收数据(阻塞的),buffersize(缓冲区大小)是接收的字节大小,一般1024。返回(data,address) 元祖
udpSocket.close() #关闭套接字
###【UDP 服务器】
ss = socket() # 创建服务器套接字
ss.bind() # 绑定服务器套接字
inf_loop: # 服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 关闭(接收/发送)
ss.close() # 关闭服务器套接字
###【UDP 客户端】
cs = socket() # 创建客户端套接字
comm_loop: # 通信循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户端套接字
###【结合多线程同时收发信息】
from socket import *
from threading import Thread
udp = socket(AF_INET,SOCK_DGRAM)
udp.bind(('localhost',9999))
def recvData():
while True:
data = udp.recvfrom(1024)
print("From [%s]: %s" % (data[1],data[0]))
def sendData():
while True:
data = input("> ")
udp.sendto(data.encode(),("localhost",8888))
print("Send: ",data)
def main():
tr = Thread(target=recvData)
ts = Thread(target=sendData)
tr.start()
ts.start()
tr.join()
ts.join()
if __name__ == "__main__":
main()
13.3 TCP 编程
from socket import *
tcp = socket(AF_INET,SOCK_STREAM)
tcp.connect(("http://www.sina.com.cn",80)) #建立 TCP 连接
tcp.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')#发送数据
###【TCP服务端】
from socket import *
tcp = socket(AF_INET,SOCK_STREAM)
tcp.bind(('127.0.0.1',9999))
tcp.listen(2)#监听连接,表示允许最多同时有n个客户端连接
while True:
print("waiting for connection...")
client,addr = tcp.accept()#开启一个简单地单线程服务器,等待客户端的连接,accept是阻塞的,一旦服务器接受了一个连接就会返回一个独立的套接字(client),用来与即将到来的消息进行交换,然后空出主线(原始服务器套接字),以便接收新的客户端连接请求。
print("...connected form: ",addr)
while True:
data = client.recv(1024)
client.send(data+" ==OK==".encode())
print(data.decode())
client.close()
tcp.close()
###【tcp客户端】
from socket import *
tcp = socket(AF_INET,SOCK_STREAM)
tcp.connect(("127.0.0.1",9999)) #连接TCP服务器
while True:
data = input("> ")
tcp.send(data.encode()) #发送零个字节,则关闭连接;不向UDP那样每次需要addr,因为TCP是面向连接的
data = tcp.recv(1024)
print(data)
tcp.close()
13.4 Web服务器案例
13.4.1固定返回值的Web服务器代码
import socket
from multiprocessing import Process
def handle(client):
'''处理客户端请求'''
request_data = client.recv(1024)
print(request_data)
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My_server\r\n"
response_body = "Hello World!"
response = response_start_line + response_headers + "\r\n" + response_body
print("response data: ",response)
client.send(response.encode())
client.close()
def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("",80))
server.listen(5)
while True:
client,addr = server.accept()
p = Process(target=handle, args=(client,)) #多进程方式接受用户连接
p.start()
client.close() #因为子进程已经获得了一个client,所以关闭
if __name__ == "__main__":
main()
13.4.2 返回静态文件
import socket
from multiprocessing import Process
#静态文件根目录
HTTP_ROOT_DIR = 'E:/Program/Python/网络'
def handle(client):
# 处理客户端请求
request_data = client.recv(1024).decode()
request_start_line = request_data.splitlines()[0]
file_name = request_start_line.split(' ')[1]
if "/" == file_name: #防止少写一个等号,将常量放在左边
file_name = '/index.html'
try:
file = open(HTTP_ROOT_DIR + file_name)
print(HTTP_ROOT_DIR + file_name)
except IOError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My_server\r\nContent-type: text/html,charset=utf-8\r\n"
response_body = "<h3>404 Not Found</h3>"
response = response_start_line + response_headers + "\r\n" + response_body
else:
file_data = file.read()
file.close()
#响应数据
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My_server\r\nContent-type: text/html,charset=utf-8\r\n"
response = response_start_line + response_headers + "\r\n" + file_data
client.send(response.encode())
client.close()
def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("",80))
server.listen(10)
while True:
client,addr = server.accept()
p = Process(target=handle, args=(client,))
p.start()
client.close() #因为子进程已经获得了一个client,所以关闭
if __name__ == "__main__":
main()
$ - 内置模块
1. time
import time
>>> time.time() #获得时间戳
1533131596.133227
>>> time.sleep(5) #睡眠5秒
2. random
import random
>>> random.random() #返回 0-1 的随机数
0.5987034894846363
>>> random.choice(序列) #随机返回序列中的某个值
>>> random.shuffle(列表) #随机打乱列表
>>> random.randint(start,end) #随机返回指定返回的随机整数
3. string
import string
>>> string.ascii_letters #返回所有大小写字母
>>> string.ascii_lowercase #小写字母
>>> string.ascii_uppercase #大写字母
>>> string.digits #数字
>>> string.hexdigits #十六进制字符
'0123456789abcdefABCDEF'
>>> string.punctuation #键盘上所有符号
>>> string.printable #所有可打印字符,上面加起来
4. hashlib
import hashlib
md5 = hashlib.md5() #创建一个MD5对象
md5.update(b'admin') #通过update方法填充【字节】
# md5.update(b'addtion') #如果数据太长,可以分开填充。等同于:md5.update('adminaddtion')
md5.hexdigest() #返回十六进制形式的hash摘要
5. urllib
见爬虫
6. re
import re
###【匹配标志】
# 多个标志用 or 或者 | 连接 如: re.I | re.S
# re.I 忽略大小写
# re.M 多行匹配,影响 ^ 和 $
# re.S 使得 . 能够匹配换行符
###【re.match(pattern,string,flags=0)】
# 从字符串开头开始匹配,匹配成功返回匹配对象,失败返回None
###【re.search(pattern,string,flags=0)】
# 扫描整个字符串,不像match 那样只从开头匹配
###【group(num=0) groups()】
# 匹配对象有这两个方法。group()要么返回整个匹配的对象,要么根据要求返回特定子组。
# groups()则仅返回一个包含唯一或者全部子组的元祖,如果没有子组要求的话,那么group()仍然返回整个匹配,groups()返回空元祖。
re.match('(a)(b)c','abcd').groups() #('a','b')
re.match('(a)(b)c','abcd').group() #'abc'
re.match('(a)(b)c','abcd').group(1) #'a'
re.match('(a)(b)c','abcd').group(2) #'b'
re.match('a','bbb').group() #AttributeError,因为返回了None
###【re.findall(pattern,string,flags=0)】
# 以列表形式返回所有匹配的内容
re.findall('\d','a1b2c3') #['1', '2', '3']
re.findall('b(\d)b','b1bb2bb3b') #['1', '2', '3']
###【re.finditer(pattern,string,flags=0)】
# 与findall 类似,返回的是迭代器。迭代器中每一个元素是一个匹配对象
it = re.finditer(r"\d+","12a32bc43jf3")
for match in it:
print (match.group(),end=' ')
#12 32 43 3
###【re.sub(pattern, repl, string, count=0,flags=0)】
# repl 要替换成的字符串,【也可以是一个函数】
re.sub('\d','x','a1b2c3') #'axbxcx'
#【分组替换,注意反斜线会转义,用两个或加r】
re.sub('(\d{3}).+(\d{4})',r'\1xxxx\2','13736302955') #137xxxx2955
# 【repl为函数】
def double(matched):
value = matched.group()*2
return value
re.sub('a',double,'abca') #aabcaa
###【re.subn()】
同 sub() ,这个方法会返回替换的次数
###【re.split(pattern,string,maxsplit=0,flags=0)】
# 分隔字符串
7. csv
# CSV(Comma-Separated Values)即逗号分隔值,可以用Excel打开查看。由于是纯文本,任何编辑器也都可打开。
# CSV文件值没有类型,不能设置样式
# 不能设置多个工作表
# 不能嵌入图像
import csv
###【读取csv文件】
# csv.reader() 将返回一个读取器对象。
with open('test.csv','r') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(row) #将会打印出列表
#【写入CSV文件】
# csv默认写入文件时,会在每一行数据下添加一个新空行,所以要用open(file,newline='')来取消新空行
with open('test.csv','w',newline='') as f:
writer = csv.writer(csvfile)
writer.writerow([1,2,3])
writer.writerow(['a','b','c'])
writer.writerows([[1],[2]]) #也可以一次写入多个列表,一个列表嵌套几个列表(就几行)