- 1.面向对象编程
- 1.1类:
- 1.2如何使用类
- 1.3 如何使用对象
- 1.4 类的继承与重用性
- 1.5多态与多态性
- 1.6封装(私有方法)
- 1.7绑定方法和非绑定方法(成员)
- 1.8反射
- 1.9内置方法
- 1.9.1 isinstance(obj,cls) 判断对象是否是某个类或其子类的实例
- 1.9.3 —item—系列 (模拟成字典去操作)
- 1.9.4 —str— 打印对象的时候触发,
- 1.9.5 —del—
- 1.9.6—new—构造方法,在init方法之前触发
- 1.9.7—call—方法 对象加括号会执行该方法
- 1.9.8—dict—方法,获取对象中的实例变量,以字典形式返回
- 1.9.9 —enter— 和—exit—,让对象支持with语法(上下文管理)
- 1.9.10 —add—方法,对象相加时触发
- 1.9.11 —iter—
- 可迭代对象
- 1.9.12 classmethod ,staticmethod,property
- 1.9.13 callable ,是否可在后面加括号执行(True,False)内部有—call—方法
- 1.9.14 super 按照self当前类的mro继承关系向上找成员
- 1.9.15 issubclass,判断是否是某个类的子孙类
- 1.10 元类介绍
- 1.11 单例模式
- 1.12 异常处理
- 1.13 socket
1.面向对象编程
面向过程编程,过程是解决问题的步骤,一步一步规划好,先干嘛后干嘛
优点:编程简单,复杂的问题流程化
缺点:扩展性差
def denglu:
输入密码账户
name = input(">>>>>用户")
passwd = input(">>>>>密码")
return {"name:name,"pwd":pwd}
def check(user_info):
检查正确与否
is_valis = TRUE
if len(user_info['name']) == 0:
print('用户名并不能为空')
is_valis = False
if len(user_info['pwd']) < 6 :
print('密码不能少于6位')
valis = False
return {'is_valis':valis,'user_info':user_info}
def regeister(check_info):
if check_info['is_valid']:
with open('xxx.text','w',encoding='utf-8') as f:
def main():
user info = denglu()
check info = check(user info)
regeister (check info)
if __name__ == '__main__'
main
------------------------------------------------
__开头和__结尾就是python内置的,在特定的时候触发
面向对象编程: 核心就是对象(python一切皆对象)
什么是对象:
特征与技能的结合
优点:可扩展性好
缺点:编程复杂
应用场景:客户需求经常变化,互联网应用,游戏,企业内部应用
1.1类:
一系列对象相似的特征与技能的结合
类的定义与实例化对象
先定义类根据类实例化对象
class func():
def __init__(self,name,age) # 初始化方法,实例化对象的时候会触发
name = name
age = age
# 对象 = 类名() 会自动执行类中的__init__方法
n1 = func() # 实例化一个对象
初识面向对象
定义类 在类中定义方法,在方法中实现具体的功能
实例化类并得到一个对象,通过对象去调用执行方法
类名首字母大写
py3之后默认类都继承object
在类中编写的函数叫方法
每一个方法的第一个参数是self
对象和self
在每个类中都可以定制特殊的—init—初始化方法,在实例化类创建对象的时候自动执行
self 接收的是调用方法的当前对象,本质就是一个参数
对象,基于类实例化出来的一块内存,默认里面没有数据,经过—init—方法,可以在内存中初始化一些数据
根据类创建对象,也就是内存的一块区域
执行--init--方法,模块会将创建的那块区域的内存地址当参数传递给self
常见成员:
实例变量,属于对象,只能通过对象调用,__init__方法下的变量就是实例变量
类变量,在类中定义的变量
绑定方法 属于类 绑定给对象和绑定到类 可以用对象或类调用
1.2如何使用类
class func:
school = "lufei" # 数据属性 类变量
def learn(self): # 函数属性
print("learn")
def eat(self): #函数属性
print("eat")
查看类的名称空间 print(func.__dict__)
类定义完成后对类的属性的增删改查方法 就是操作func.__dict__这个字典
增加一个数据属性 func.county = 'china'
删除 del func.county
改 func.school = 'luffycity'
查 print(func.__dict__[school])
实例化出对象 obj = func() # 实例化出来的类有自己独立的存储空间
1.3 如何使用对象
—init—方法用来为对象定制对象自己独有的特征(在实例化对象的时候触发)
init方法实例化的步骤
1.先产生一个空对象
2.参数传值
3.根据init方法初始化
class func:
school = 'liu'
def __init__(self,name,age) # self接收的是obj
self.name = name
self,age = age
def learn(self):
def eat(self):
obj = func('utr',19)
对象的增删改查
增加 obj.class_name = 'python'
删除 del obj.age
改 obj.name = "luffycity"
查 obj.name
1.3.1属性查找与绑定方法
类中的数据属性也就是变量是所有对象共有的,指向的是一个内存地址
类中的函数属性 :是绑定给对象的,绑定到不到的对象是不同的,内存地址是不一样的
类调用自己的函数属性,(self)需要手动传值
类中的函数属性是绑定的对象使用的,哪个对象来调用,就将对象当作第一个参数传入
对象使用属性的时候,现在自己的空间寻找,没有去类的空间寻找,有继承就去父类寻找
1.3.2 一切皆对象
站在不同的角度,定义出来的类是不同的
现实当中的类并不等于代码当中的类,如现实中的公司类,在程序中需要拆分成部门类业务等
1.3.3面向对象可扩展性强
1.3.4 对象的嵌套
class student(object):
def __init__(self):
self.name =name
self.age =age
def message =(self):
data = '学生{},{}'.fornat(self.name,self.age)
print(data)
stud1 = student('123',18)
stud2 = student('456',20)
stud3 = student('789',22)
class classes(object):
def __init__(self,title):
self.title = title
self.student_list = []
def add_student(self,stud_object):
self.student_list.append(stud_object)
def add_students(self,stu_object_list)
for stu in stu_object_list:
self.add_student(stu)
def show_members(self):
for item in self.student_list:
print(item)
c1 = classes('三年二班')
c1.add_studnet(s1)
c1.add_students([s2,s3])
print(c1.title)
print(c1.student_list)
1.4 类的继承与重用性
继承是什么是什么的关系
将类中公共的方法提取到基类中
查看继承了哪些父类 派生类.—bases— 元组形式
class func1:
pass
class func2:
pass
class func3(func1): #func3继承func1的属性 func1是基类 func3叫派生类
pass
1.4.1继承的属性查找
首先在自己的空间中寻找,没有则去对应的类中寻找,类中也没有去继承的基类中寻找(关注self是谁,不管找到哪遇到self就从先从自己空间中找起)
1.4.2派生
当派生类中有自己的属性,则首先使用自己的
1.4.3继承的实现原理
定义的类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表 (查看线性列表,派生类.mro())也就是继承顺序的列表
python2 区分
新式类:继承object的类,以及他的子类都是新式类
经典类: 没有继承object,以及他的子类都是叫经典类
python3 只有新式类,默认继承object类
深度优先:经典类
广度优先:新式类
1.4.4派生类重用基类的属性
super(自己的类,self).—init—(属性名) ## 顺着mro列表往后找,依赖继承关系 super(自己的类,self)得到一个对象然后调用基类中的方法
class func:
def __init__(self,name,age,igh)
self.name = name
self.age = age
self.high = high
def ease(self,food)
print("{}的food".formt(self.name))
class gurn(func):
def att():
super(gurn,self).ease('苹果')
class A:
def f1(self):
print('from A')
super().f1()
class B:
def f1(self):
print('from B')
class C(A,B):
pass
print(C.mro()) #
# [<class '__main__.C'>,<class '__main__.A'>,<class '__main__.B'>]
c =C()
c.f1()
1. C中没有f1属性,根据继承去A中找
2. A中有f1属性先输出from A 然后执行super().f1()执行的是B的f1
3. 因为super是根据mro往后找也就是B的f1,因为是基于c产生的mro列表,现在A中f1已经找完了mro列表中的下一个是B,所以是输出from B, (根据c产生的mro当前找到哪了继续往后找)
1.4.5抽象类与归一化
抽取相似的地方得到一个父类,子类中的属性必须实现父类中的规范
抽象出来的不定义具体的功能,只写规范
抽象类只能被继承,不能被实例化
子类中必须有抽象类的方法
使用abc第三方模块实现
import abc
class animal(metaclass= abc.ABCMeta):
@abc.abstracmethod # 子类中必须有run方法
def run(self)
pass
@abc.abstracmethod # 子类中必须有eat方法
def eat(self):
pass
class Pig(animal):
def run(self):
class Dog:
def zou(self):
1.4.6 mro列表 (继承关系) 与C3算法
class c(object):
pass
class B(object):
pass
class A(B,C):
print(A.mro()) 列表形式
print(A.__mro__) 元组形式
mro(A) = [A] + [B,C] #mroA= [ABC]
mro(A)=[A] + merge( mro(B),mro(c),[B,C] )
#A的继承关系 首先将[A]放进来 merge接收A左边的继承关系,然后是右边的继承关系,然后是所继承的父类[B,C]
mro(A)=[A] + merge( [B,object],[C,object],[B,C] )
# 第一个参数
mro(A)=[A]+[B,C,object]
mro(A) = [A,B,C,object]
# merge 计算规则
将第一个参数的第一个值(此时为B),与其他参数的除第一个值以外的比较,判断是否在里面
如果不在将其提取出来,去掉原值,(就是将参数里面的所有的B剔除掉)
如果在里面跳过,进行下一个参数的第一个值开始比较,最后把剩下的提取出来
图中的继承关系使用C3算法计算过程
[^]: 从左到右,深度优先,大小钻石,留住顶端 ,可以更快的找到类的继承关系,钻石是指方形的继承
1.5多态与多态性
python默认支持多态(这种方式称之为鸭子类型)
(在继承的背景使用下有时候也叫多态性)
多态是指同一类物体有多种形态(如文件有文本文件和可执行文件)
多态性是值不考虑实例类型(对象类型)的情况下使用实例(对象)比如+ 支持文本相加,数字相加,+本身不用考虑加的是什么类型
增加了程序的灵活性 :使用者都是同一种形式去调用 func(n1)
增加了程序的可扩展性:通过继承创建一个新的类,使用者不用更改自己的代码还是用func(n1)调用
鸭子类型:看起来像就是
def func(arg):
v1 = arg.copy()
print(v1)
func('123')
func([11,22,33,44])
# 将字符串或者列表传给arg时候,arg可以接收任意类型的数据,只要参数有copy方法就可以
# 这种形式就可以理解为多态
# 序列类型,列表 元组 字符串
l = list([1,2,3])
t = tuple((a,b))
s = str ('he')
l.__len__()
t.__len__()
s.__len__()
1.6封装(私有方法)
将同一类的方法封装到一个类中
将数据封装到对象中,如—init—
封装相当隐藏 (在属性名字前面加__)对象无法直接调用 又叫成员修饰符
class A:
__X = 1 #在类的名称空间中为_A__X 查看名称空间 A.__dict__
def __init__(self,name):
self.__name = name # 会变形
def __foo(self):
pass
A.__C = 2 # __C不会变形可以直接调用
print(A.__C)
"""
特点
类的外部无法直接调用 (A.__X) 可以通过调用(A._A__X)
类内部可以直接调用 (A.__X)
子类无法覆盖父类__开头的属性(覆盖是指两个类中相同的属性名)
这种隐藏注意的问题
这种变形只在定义阶段发生一次,类定义阶段之后赋值则不会变形
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有方法也就是加__,子类不会继承父类的私有方法
"""
封装到底是要干什么
1.6.1封装数据属性干什么:明确的区分内外控制外部对隐藏属性的操作行为
class People:
def __init__(self,name,age)
self.__name = name
self.__age = age
def tell_info(self):
print('name{} age{}'.format(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
print('name必须是字符串')
if not isinstance(age,int):
print('age必须是数字')
self.__name = name
self.__age = age
p = people('liu',18) # 外部无法直接调用p.__name
p.tell_info() # 通过统一的接口调用
p.set_info('123',45)
1.6.2 封装函数属性干什么:隔离复杂度
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('password')
def __input(self):
pirnt('输入取款金额')
def __print_bill('self'):
print('打印账单')
def __take_money(self):
print("取款")
def withdraw(self):
self.__card
self.__auth
self.__input
self.__print_bill
self.__take_money
a = ATM()
a.withdraw()
1.6.3封装与扩展性
class Room:
def __init__(self,name,owner,height,weight,length)
self.name = anme
self.owner = owner
self.__weight = weight
self.__length = length
def tell_area(self):
retuen self.__weight * self.length
a = Room('o','kiler',10,10,10)
a.tell_area()
1.6.4 property 装饰器 (函数,特性)
在方法前面加上@property 对象在调用方法的时候不用加()了
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property
def Bmi(self):
return self.weight / (self.height ** 2)
a = People('kiler',70,1.74)
a.Bmi
class people:
def __init__(self,name):
self.__name = name
@property #访问 是不能给name赋值的,因为name是一个方法
def name(self):
return (self.__name)
@name.setter #修改
def name(self,value) #现在是可以赋值的,前提是name被property装饰
if not instance(val,str)
print("名字必须是字符串")
return
self.__name = value
@name.deleter #删除
def name(self)
print('不允许删除')
p = People('liu')
print(p.name)
p.name = 'li' # 执行setter
del p.name # 执行deleter
1.7绑定方法和非绑定方法(成员)
在类定义的方法分为两类
绑定方法 (绑定给谁就由谁来调用,将调用者当第一个参数自动传给self)
- 绑定到对象的方法:在类定义的时候没有被任何装饰器修饰的
- 绑定到类的方法:在方法前面加@classmethod,则就是绑定给类的方法,类在调用的时候,将类当作第一个参数传给cls
非绑定方法 又叫静态方法
就是不与类或者对象绑定(类可以用对象也可以用,不会自动传值,需要手动传参)就是类中普通的函数
在方法前面加@staticmethod
1.7.1绑定方法和非绑定方法的使用
绑定到对象的方法
class people:
def__init__(slef,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self): 绑定到对象的方法
print("{},{},{}".format(self.name,self.age,self.sex))
p = people('kiler',18,20)
p.tell_info()
绑定给对象,应该由对象来调用,自动将对象本身当作第一个参数传入p.tell_info(p)
import settings # settings包里面有name age sex
class people:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(slef): #绑定到对象的方法
print("{},{},{}".format(slef.name, slef.age, slef.sex))
@classmethod # 绑定给类的方法
def from_setting(cls):
obj = cls(
settings.name,
settings.age,
settings.sex
)
print(obj.name)
return obj #是一个对象
# 绑定给类,由类调用,自动将类本身当作第一个参数传入p=people.from_setting(people)
p = people.from_setting()
p.tell_info()
import time
import hashlib
import settings # settings包里面有name age sex
class people:
def __init__(self, name, age, sex):
self.id = self.id()
self.name = name
self.age = age
self.sex = sex
def tell_info(self): #绑定到对象的方法
print("{},{},{}".format(slef.name, slef.age, slef.sex))
@classmethod #绑定给类的方法
def from_setting(cls):
obj = cls(
settings.name,
settings.age,
settings.sex
)
print(obj.name)
return obj #是一个对象
@staticmethod #静态方法 非绑定方法
def id():
m = hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
# 绑定给类,由类调用,自动将类本身当作第一个参数传入
p = people.from_setting()
p.tell_info()
p1 = people('12',12,12)
print(p1.id())
1.7.2 属性@property
属性由绑定方法+特殊装饰器组合
在方法前面加上@property 对象在调用方法的时候不用加()了
class People:
def __init__(self,name,weight,height)
self.name = name
self.weight = weight
self.height = height
@property
def Bmi(self):
return self.weight/(self.height** 2)
a = people('kiler',75,1.9)
a.Bmi()
a.bmi
class people:
def __init__(self,name):
self.__name = name
@property #访问 是不能给name赋值的,因为name是一个方法
def name(self):
return (self.__name)
@name.setter #修改
def name(self,value) #现在是可以赋值的,前提是name被setter装饰
if not instance(val,str)
print("名字必须是字符串")
return
self.__name = value
@name.deleter #删除
def name(self)
print('不允许删除')
obj = people('345')
obj.name = "123" # 找到 @name.setter 并将123传递给value
del obj.name()
class c(object):
def getx(self):
pass
def setx(self):
pass
def delx(self):
pass
x = property(getx,setx,delx,'i am x')
obj = c()
obj.x # 会执行property中的getx 方法
obj.x = 123 # 会执行property中的setx 方法
del obj.x # 会执行property中的delx 方法
1.8反射
通过字符串映射到对象属性(’以字符串的形式去对象中进行成员的操作’)
class people:
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print("{}<{}".formt(self.name,self.age))
obj = people('kile',18)
# 对象 属性名
hasattr(obj,'name')#判断obj对象下有没有name属性返回True或False obj.__dict__['name']
getattr (obj,'talk',none) # 拿到obj下的talk方法 加括号就可以执行 如果obj没有name属性则返回none
setattr(obj,'sex','male') #obj.sex = male
delattr(obj,'age') # del boj.age
1.8.1 import_module + 反射
可以以字符串的形式导入模块 #只能导入模块
#导入模块
from importlib import import_module
m = import_module('random')
m = import_moudle('request.exceptions')
v1 = m.randint(1,100)
使用import_module 和 getattr配合实现根据字符串的形式导入模块并获取成员
from omportlib import import_module
path = "openpyxl.utils.exceptions.InvalidFileException"
module_path,class_name = path.rsplit("."maxsplit=1)
module_object = import_module(module_path)
cls = getattr(module_object,class_name)
print(cls)
1.9内置方法
1.9.1 isinstance(obj,cls) 判断对象是否是某个类或其子类的实例
class Foo(object):
pass
obj = Foo()
isinstance(obj,Foo) # 判断obj是否是Foo类的实例 对于有继承关系实例出来的对象也是True
1.9.3 —item—系列 (模拟成字典去操作)
class Foo:
def __init__(self,name):
self.name = name
def __getitem__(self,item): #查看的时候触发
print('get')
return self.__dict__.get(item)
def __setitem__(self,key,value): #设置值的时候触发
print('set')
self.__dict__[key]=value
def __delitem__(self,key): #删除的时候触发
print("del")
del self.__dict__[key]
obj = Foo()
obj['name'] # obj.__dict__['name'] 触发getitem
obj['sex'] =16 #触发setitem sex为key,16为value
del obj['name']
1.9.4 —str— 打印对象的时候触发,
class people:
def __init__(self,anme,age):
self.name = name
self.age = age
def __str__(self): # 在打印的时候触发
print('str')
return '{},{}'.format(self.name,aelf.age) # 必须返回字符串
obj = people('si',18)
print(obj) # 触发__str__
1.9.5 —del—
回收资源,在对象被删除的时候会自动先触发该方法
class open:
def __init__(self,filename):
print('open file')
self.name = filename
def __del__(self):
print('回收资源')
f = open('setting.py')
1.9.6—new—构造方法,在init方法之前触发
class foo:
def __init__(self,name):
print('第二步,初始化对象,在空对象中创建数据')
def __new__(cls,*args,**Kwargs):
print('第一步,先创建空对象并返回')
return object.__new__(cls)
obj = foo('123')
1.9.7—call—方法 对象加括号会执行该方法
class foo:
def __call__(self,*args,**kwargs):
print('对象加括号会执行call方法')
obj = foo('123')
obj() #执行call方法
1.9.8—dict—方法,获取对象中的实例变量,以字典形式返回
class foo:
def __init__(self,anme,age):
self.name = name # 实例变量
self.age = age
obj = foo('kil',18)
print(obj.__dict__)
1.9.9 —enter— 和—exit—,让对象支持with语法(上下文管理)
class Foo:
def __enter__(self):
self.连接 = 连接操作
return self.连接
def __exit__(self,exc_type,exc_val,exc_tb):
self.连接关闭
obj = Foo()
#with 对象 as XX 在内部会执行--enter--方法,并将方法中的返回值给XX
#当with缩进中的代码执行完毕后,自动执行--exit--方法
with obj as f:
操作
...
...
-------------------------------------------------
#面试题
class Comtext:
def __enter__(self):
return self #将当前对象返回给 ctx
def __exit__(self,exc_type,exc_val,exc_tb):
pass
def do_something(self):
print('内部执行')
with Context() as ctx:
print("内部执行")
ctx.do_something() #当前对象可以执行do_something()方法
1.9.10 —add—方法,对象相加时触发
class foo:
def __add__(self,other) #outher接收的是v2这个对象
return 99
v1 = foo()
v2 = foo()
#对象+值,内部会执行 对象.--add--方法,并将+后面的值当作参数传递进去
v3 = v1+v2
print(v3) 99
1.9.11 —iter—
迭代器
定义:
1.类中定义了 --iter-- 和 --next--两个方法
2.--iter--方法需要返回对象本身,即self
3.--next--方法,返回下一个数据,如果没有数据了需要抛出一个stopiteration
创建迭代器类型
class IT:
def --init--(self):
self.counter = 0
def --iter--(self):
return self
def --next--(self):
self.counter +=1
if self.counter == 3:
raise StopIteration()
return self.counter
根据类实例化创建一个迭代器对象
obj1 = IT()
#v1 = obj1.--next--()
#v2 = obj1.--next--()
#v3 = obj1.--next--() #抛出异常
v1 = next(obj1) # obj1.__next__()
v2 = next(obj1)
v3 = next(obj1)
obj2 = IT()
for item in obj2: #先执行迭代器对象的--iter--方法并获取返回值,一直反复执行next(对象),直到抛出异常
print(item)
迭代器对象支持通过next取值,如果取值结束则自动抛出Stopiterarion
for 循环内部在循环时,先执行--iter--,获取一个迭代器对象,然后一直执行next取值,有异常终止
可迭代对象
#一个类中有--iter--方法且返回一个迭代器对象,根据这个类创建的对象叫可迭代对象
class foo:
def--iter--(slef):
return 迭代器对象(生成器对象)
obj = foo()
#可迭代对象是可以使用for循环,在循环内部是先执行--iter--,获取迭代器对象,在内部指向这个迭代器对象的next功能,逐步取值
for item in obj:
print(item)
======================================================================
# 基于可迭代对象和迭代器实现自定义range
class Iter:
def __iter__(self,name):
self.num = num
self.counter = -1
def __iter__(self):
return self
def __next__(self):
self.counter +=1
if self.counter == self.num:
raise StopIteration()
return self.counter
class Xrange:
def __init__(self,max_num):
self.max_num = max_num
def __iter__(self):
return Iter(self.max_num)
obj = Xrange(100)
for item in obj:
print(item)
#================================================
#基于可迭代对象和生成器,自定义range
class Xrange:
def __init__(self,max_num):
self.max_num - max_num
def __iter__(self):
counter = 0
while counter < self.max_num:
yiled counter
counter +=1
obj = Xrange(100)
for item in obj:
print(item)
from collections.abc import Iterator,Iterable
v1 = list([1,2,3]) #v1是可迭代对象因为列表中生命了__iter__方法并且返回一个迭代器对象
print(instance(v1,Iterator)) 判断是否是迭代器
print(instance(v1,Iterable)) 判断是否有__iter__且返回迭代器对象
1.9.12 classmethod ,staticmethod,property
1.9.13 callable ,是否可在后面加括号执行(True,False)内部有—call—方法
1.9.14 super 按照self当前类的mro继承关系向上找成员
class Base(object):
def message(self,num):
print('base.message',num)
class Foo(Base):
def message(self,num):
print("Foo.meaasge",num)
super().message(num+10) #执行base里面的message
obj = Foo()
obj.message(1)
1.9.15 issubclass,判断是否是某个类的子孙类
class Top(object):
pass
class Base(Top):
pass
class Foo(Base):
Pass
print(issubclass(Foo,Base)) True
print(issubclass(Foo,Top)) True
1.10 元类介绍
产生类的类就是元类 ,type()
默认用class 定义的类,元类是type
exec('字符串形式的的命令',‘全局作用域’,‘局部作用域’)
# 为字符串形式的命令定制一个全局作用域(不指定默认用globals())和局部作用域名(不指定默认locals())
# globals() 查看全局作用域中的值
g = {'x' = 1,'m' = 2,}
l = {}
exec('''
global x,m
x = 10
m = 100
z = 3
''',g, l
) # 全局作用域是g局部作用域名是l
#exec相当于一个函数执行过程中产生的局部变量放到l中,exec执行过程中产生的全局变量放到g中
# g = {'x'=10,'m'=100} l={z:3}
python一切皆是对象,对象怎么用
可以被引用 X = obj
可以当做函数的参数传入
可以当作函数的返回值
可以当作容器类的元素
class Foo: #类也是对象,对象是实例化出来的 Foo = type()
pass
1.10.1 定义类的两种方式
1.class定义的
2.type
定义类的要素:
类名 继承 类的名称空间
用type定义类
class_name = 'chinese' # 类名
class_base = (object,) # 继承
class_body = """ # 类体代码
country = 'china',
def __init__(self,name,age):
self.name = name
self.age = age
def talk (self):
print("{}<{}".format(self.name,self.age))"""
class_dic = dict{} # 类的名称空间
exec=(class_body,globals(),class_dic) #使用exec生成名称空间
chines1 = type(class_name,class_bases,class_dic) #type(类名,继承,名称空间)
自定义元类控制类的创建
自定义的元类只是修改部分方法,所以要继承元类
class meta(type):
def __init__(self, class_name, class_base, class_dic):
if not class_name.istitle(): # 首字母大写
raise TypeError('类型错误') # 抛出异常
if "__doc__" not in class_dic or not class_dic['__doc__'].strip():
raise TypeError("必须有注释")
super(meta,self).__init__(class_name, class_base, class_dic)
# __doc__是类空间的注释内容,如果类中有注释则类的名称空间就有__doc__
class Chinese(object, metaclass=meta): # metaclass 指定元类,object是默认继承的
# Chinese = meta(class_name,class_base,class_dic)
"""
注释
"""
country = 'china'
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
print("{}<{}".format(self.name, self.age))
1.10.2 自定义元类控制类的实例化行为
__call__ 方法 对象在被调用的时候触发 元类当中也有call方法
class Foo:
def __call__(self,*args,**kwargs):
print(self)
print(args)
print(Kwargs)
obj = Foo(1,2,3,a = 1) #Foo.__call__(Foo,1,2,3,a = 1)
#调用类发生的事情
1.创建空对象
obj = object.__new__(self):
2.初始化__init__
self.__init__(obj,*args,**Kwargs):
3.返回值
return obj
class meta(type):
def __init__(self, class_name, class_base, class_dic):
if not class_name.istitle(): # 首字母大写
raise TypeError('类型错误') # 抛出异常
if "__doc__" not in class_dic or not class_dic['__doc__'].strip():
raise TypeError("必须有注释")
super(meta, self).__init__(class_name, class_base, class_dic)
def __call__(self, *args, **kwargs):
# 调用类发生的事情
#1.创建空对象
obj = object.__new__(self)
#2.初始化__init__
self.__init__(obj, *args, **kwargs)
print(obj.__dict__)
#3.返回值
return obj
# __doc__是类空间的注释内容,如果类中有注释则类的名称空间就有__doc__
# __call__是对象被调用的时候触发的方法,控制对象的实例化
class Chinese(object, metaclass=meta): # metaclass 指定元类,object是默认继承的
"""
注释
"""
country = 'china'
def __init__(self, name, age):
self.name = name
self.age = age
kwq = Chinese('kil',18)
1.11 单例模式
单例模式就是对象的数据属性值都一样的时候不开辟新的内存空间用一个
class Mysql:
__instance = None
def __init__(self):
self.host = '127.0.0.1'
self.port = 3306
@classmethod
def singleton(cls):
if not cls.__instance:
obj = cls()
cls.__instance = obj
return cls.__instance
obj1 = Mysql.singleton()
obj2 = Mysql.singleton()
print(obj1)
print(obj2)
#obj1 和 obj2的数据属性都是一样的
1.12 异常处理
异常就是程序运行时发生错误的信号,程序出现错误,并且程序没有处理这个错误信息,就会抛出异常
并且程序运行随之终止
错误的种类,语法错误和逻辑错误
try:
要检测的代码
except 错误类型 as e: #e包含的是try里面检测到的错误
print('错误')
print('1')
print('2')
---------------------------
检测的代码中有错误时,输出错误,不再往下执行
try ….except 多分支的使用方法
被检测的代码块抛出的异常有多种可能性,并且我们需要针对每一种异常类型都定制专门的处理逻辑
try:
print('1')
name
print('2')
l = [1,2]
l[1000]
print('3')
d = {}
d[name]
print('4')
except NameError as e:
print(e)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
万能异常
被检测的代码块抛出的异常有多种可能性,对多有的异常类型定制一种公用的处理逻辑
try:
print('1')
name
print('2')
l = [1,2]
l[1000]
print('3')
d = {}
d[name]
print('4')
except Exception as e:
print('统一的处理方法')
else:
print('被检测的代码没有发生异常时执行')
finally:
print('代码有无异常都执行,一般用于释放资源 ')
#在try或者except中使用了return,finally中的代码还是会执行
# 遇到return是return结果之前执行finally中的代码
自定义异常
class MyException(Exception):
def __init__(self,msg):
super(Exception,self).__init__()
self.msg=msg
def __str__(self):
return '{}'.format(self.msg)
raise Exception('自定义异常') # 触发异常
#raise会显示异常的追踪信息和类型,还会打印这个对象的操作,也就是触发对象下的str方法
断言 assert
info = {}
info['name'] = 'kil'
info['age'] = 18
"""
if "name" not in info:
raise KeyError('没有name')
if "age" not in info:
raise KeyError('没有age')
"""
if info['name'] == 'kil' and ('age') > 10:
print('欢迎')
assert ('name' in info) and ('age' in info) #功能等于两行if判断
1.13 socket
socket 是在传输层和应用层之间抽象出来的一组接口,把复杂的tcp/ip协议簇隐藏在socket接口后面,对用户来说直接调用socket的接口就可以,socket负责去组织数据以符合指定的协议
套接字工作流程
服务端先初始化socket,然后与端口绑定bind,对端口进行监听listen,调用accept阻塞,等待客户端连接,此时如果有客户端初始化socket,连接服务器connect,如果连接成功,此时就建立起连接,客户端发送数据请求,服务端接受请求并处理请求,然后把回应数据发送到客户端,最后关闭连接,一次交互结束
socket套接字方法
family(socket家族)
socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
socket type类型
socket.SOCK_STREAM #for tcp
socket.SOCK_DGRAM #for udp
socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
proto=0 请忽略,特殊用途
fileno=None 请忽略,特殊用途
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收数据
s.send() 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() Receive data from the socket. The return value is a pair (bytes, address)
s.getpeername() 连接到当前套接字的远端的地址
s.close() 关闭套接字
socket.setblocking(flag) #True or False,设置socket为非阻塞模式,以后讲io异步时会用
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
socket.getfqdn() 拿到本机的主机名
socket.gethostbyname() 通过域名解析ip地址
import socket
phone = socket.socket(socket.AF_INET,socket.socket_STREAM) #选择协议
phone.bind(('127.0.0.1',22)) #绑定端口0-65535,0-1024是给操作系统用的
phone.listen(5) #最大连接数
conn,client_addr = phone.accept() #等待连接 conn是连接对象 client_addr是客户端ip和端口
data = conn.recv(1024) #最大接收1024byte 接受数据
conn.sendall(data.upper()) #发送数据
conn.close() #关闭连接
phone.close()
#======================================================
客户端
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM) #选择协议
phone.connect(('127.0.0.1',22)) #发起连接
phone.send(' hello',encode('utf-8'))
data = phone.recv(1024)
phone.close
通信循环
import socket
phone = socket.socket(socket.AF_INET,soc ket.socket_STREAM) #选择协议
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重复使用端口
phone.bind(('127.0.0.1',22)) #绑定端口0-65535,0-1024是给操作系统用的
phone.listen(5) #最大连接数
conn,client_addr = phone.accept() #等待连接
while True: #通信循环
data = conn.recv(1024) #最大接收1024byte 接受数据
conn.send(data.upper()) #发送数据
conn.close() #关闭连接
phone.close()
#当客户端单方面中断连接后,(客户端会发送一个空)服务端会一直卡在接收过程,形成死循环
import socket
phone = socket.socket(socket.AF_INET,soc ket.socket_STREAM) #选择协议
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重复使用端口
phone.bind(('127.0.0.1',22)) #绑定端口0-65535,0-1024是给操作系统用的
phone.listen(5) #最大连接数
conn,client_addr = phone.accept() #等待连接
while True: #通信循环
data = conn.recv(1024) #最大接收1024byte 接受数据
if not data:break # 适用linux系统,解决 客户端单方面中断连接后,服务端会一直卡在接收过程,形成死循环
"""
解决winodws下 客户端单方面中断连接后,服务端会一直卡在接收过程,形成死循环
try:
data = conn.recv(1024)
conn.send(data.upper())
except ConnectionResetError:
break
"""
conn.send(data.upper()) #发送数据
conn.close() #关闭连接
phone.close()
-------------------------------------------------------------------
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM) #选择协议
phone.connect(('127.0.0.1',22)) #发起连接
while True: #通信循环
msg = input(">>>>").strip() #
if not mag:continue #判断输入是否为空,要是空的话contiune
phone.send(msg.encode('utf-8'))
print('send')
data = phone.recv(1024)
print("recv")
print(data.decode('utf-8'))
phone.close
多客户端与多服务端
链接循环
import socket
phone = socket.socket(socket.AF_INET,socket.socket_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 避免端口被占用
phone.bind(('127.0.0.1',22))
phone.listen(5)
while True: #连接循环
conn, client_addr = phone.accept()
while True:# 通信循环
data = conn.recv(1024)
if not data: break
conn.send(data.upper())
conn.close()
phone.close()
模拟ssh远程
"""
dir :查看某个文件夹下的子文件名和子文件夹 ls
ipconfig :查看ip信息 ifconfig
tasklist :查看运行的进程 ps aux
执行系统命令
import os
os.system("dir C:\")
或者
import subprocess
obj = subprocess.Popen("dir C:\",shell = True,stdout=suprocess.PIPE,stderr= subprocess.PIPE)
taskkill python 杀死python端口进程
"""
import subprocess
import socket
phone = socket.socket(socket.AF_INET,socket.socket_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',22))
phone.listen(5)
while True: #连接循环
conn, client_addr = phone.accept()
while True:
data = conn.recv(1024) # 接收客户端的命令
if not data: break
# 执行命令并得到结果,正确结果放到stdout=suprocess.PIPE,错误放到stderr= subprocess.PIPE
obj = subprocess.Popen(data,shell = True,stdout=suprocess.PIPE,stderr= subprocess.PIPE)
stdout = obj.stdout.read()
stderr=obj.stderr.read()
conn.send(stdout+stderr)
conn.close()
phone.close()
--------------------------------------------------------------------------
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM)
phone.connect(('127.0.0.1',22))
while True:
msg = input(">>>>").strip()
if not mag:continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024) #接收的数据大于1024的时候,不会丢失,在下次执行命令的时候显示出来
#粘包现象
print(data.decode('utf-8'))
phone.close
粘包原理
send和recv都不是直接接收对方的数据,是操作自己的操作系统内存
接收的长度小于数据包的长度,导致阻塞在管道中或者因为Nagle算法将多次间隔较小且数据量小的数据合并成一个大的数据块,然后进行封包进行发送一次
recv 1.等待对方将数据发送到操作系统的缓冲区 2.recv从网卡缓冲区获取数据
send 1.send将数据发送到自己网卡到写缓冲去 2.操作系统调用网卡将缓冲区数据发送给对方网卡到缓冲区
--------------------------------------------------------------------------------
tcp,是面向连接的面向流的,提供高可靠性,收发两端都要一一成对的socket,发送端为了将多个发往接收端的包,更有效的发往对方,使用了优化算法(Nagle算法),将多次间隔较小且数据量小的数据合并成一个大的数据块,然后进行封包,这样接收端就难于分辨出来,必须提供科学的拆包机制,即面向流的。通信是无消息保护边界的
解决粘包:
每次发送数据的时候,将消息划分为头部(固定四个字节的长度)和数据部分
发送数据:先发送数据的长度 再发送数据 或拼接起来发送
接收数据:先读4字节的长度,再根据长度获取数据
struct 模块可以将数值转换为固定的四个字节 范围是-2147483648到2147483648
struct.pack('i',199) # i表示是int类型
struct.unpack('i',v1)
import subprocess
import socket
import struct#打包模块
phone = socket.socket(socket.AF_INET,socket.socket_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',22))
phone.listen(5)
while True: #连接循环
conn, client_addr = phone.accept()
while True:
data = conn.recv(1024) # 接收客户端的命令
if not data: break
# 执行命令并得到结果,正确结果放到stdout=suprocess.PIPE,错误放到stderr= subprocess.PIPE
obj = subprocess.Popen(data,shell = True,stdout=suprocess.PIPE,stderr= subprocess.PIPE)
stdout = obj.stdout.read()
stderr=obj.stderr.read()
total_size = (len(stdout)+str(stderr))##包的长度
conn.send(str(total_size).encode('utf-8')) #发送包的长度
#制作固定长度的报头
head = struct.pack('i',total_size)
#把报头发给客户端
conn.send(head)
#发送真实的数据
conn.send(stdout+stderr)
conn.close()
phone.close()
--------------------------------------------------------------------------
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM)
phone.connect(('127.0.0.1',22))
while True:
msg = input(">>>>").strip()
if not mag:continue
phone.send(msg.encode('utf-8'))
#接收报头
obj = phone.recv(4)
#从报头中解析出真实数据的描述信息(数据的长度)
total_size = struct.unpack('i',obj) #i是固定的,int类型 4个字节,unpack是解包,字节转换为数字
#接收真实的数据
recv_size = 0
recv_data = b"
while recv_size < total_size
res = phone.recv(1024)
recv_data += res
recv_size +=len(res)
print(recv_data.decode('utf-8'))
phone.close
最终版
import subprocess
import socket
import struct
phone = socket.socket(socket.AF_INET,socket.socket_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',22))
phone.listen(5)
while True: #连接循环
conn, client_addr = phone.accept()
while True:
data = conn.recv(1024) # 接收客户端的命令
if not data: break
# 执行命令并得到结果,正确结果放到stdout=suprocess.PIPE,错误放到stderr= subprocess.PIPE
obj = subprocess.Popen(data,shell = True,stdout=suprocess.PIPE,stderr= subprocess.PIPE)
stdout = obj.stdout.read()
stderr=obj.stderr.read()
total_size = (len(stdout)+str(stderr))##包的长度
conn.send(str(total_size).encode('utf-8')) #发送包的长度
#制作固定长度的报头
head_dic = {
"file_name":"a.txt",
'md5':'Xdfdfdfdfd',
'total_size':len(stdot)+len(stderr)
}
header_json = ison.dumps(header_dic)
header_byes = header_json.encode('utf-8')
struct.pack("i",len(header_bytes))
#发送报头长度
conn.send(header_bytes)
#把报头发给客户端
conn.send(head_bytes)
#发送真实的数据
conn.send(stdout+stderr)
conn.close()
phone.close()
--------------------------------------------------------------------------
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM)
phone.connect(('127.0.0.1',22))
while True:
msg = input(">>>>").strip()
if not mag:continue
phone.send(msg.encode('utf-8'))
#接收报头长度
obj = phone.recv(4)
header_size = struct.unpack('i',obj)[0]
#收报头
header_bytes = phone.recv(header_size)
#从报头中解析出真实的数据表述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
#接收真实的数据
recv_size = 0
recv_data = b"
while recv_size < total_size
res = phone.recv(1024)
recv_data += res
recv_size +=len(res)
print(recv_data.decode('utf-8'))
phone.close
文件传输功能
文件传输功能
import subprocess
import socket
import struct
phone = socket.socket(socket.AF_INET,socket.socket_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',22))
phone.listen(5)
while True: #连接循环
conn, client_addr = phone.accept()
while True:
data = conn.recv(1024) # 接收客户端的命令
if not data: break
cmds = data.decode('utf-8').split()
filename = cmds[1]
stdout = obj.stdout.read()
stderr=obj.stderr.read()
total_size = (len(stdout)+str(stderr))##包的长度
conn.send(str(total_size).encode('utf-8')) #发送包的长度
#制作固定长度的报头
head_dic = {
"file_name":"a.txt",
'md5':'Xdfdfdfdfd',
'file_size':os.oath.getsize(filename)
}
header_json = ison.dumps(header_dic)
header_byes = header_json.encode('utf-8')
struct.pack("i",len(header_bytes))
#发送报头长度
conn.send(header_bytes)
#把报头发给客户端
conn.send(head_bytes)
#发送真实的数据
with open(filename.'rb') as f:
for line in f:
conn.send(line)
conn.close()
phone.close()
--------------------------------------------------------------------------
import socket
phone = socket.socket(socket.AF_INET,scoket.socket_STREAM)
phone.connect(('127.0.0.1',22))
while True:
msg = input(">>>>").strip()
if not mag:continue
phone.send(msg.encode('utf-8'))
#接收报头长度
obj = phone.recv(4)
header-size = struct.unpack('i',obj)[0]
#收报头
header_bytes = phone.recv(header_size)
#从报头中解析出真实的数据表述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
file_size = header_dic['file_size']
filename = header_dic['filname']
#接收真实的数据
with open(filename,'wb') as f:
recv_size = 0
while recv_size < file_size
line = phone.recv(1024)
f.write(line)
recv_size +=len(line)
phone.close
UDP 套接字
服务端
from socket import *
server = socket(AF_INET,SOCK_DGRAM)
server.bind('127.0.0.1',8080) # udp是无连接的
while True:
res1 = server.recvfrom(1024)
res2 = server.recvfrom(1024)
server.close
------------------------------------------------
客户端
from socket import *
client = socket(AF_INET,SOCKET_DGRAM)
client.sendto(b'hello',('127.0.0.1',8080))
client.sendto(b'word',('127.0.0.1',8080))
client.close()
socketserver模块实现并发
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
# 并发的业务逻辑代码 self.request是客户端的套接字对象
while True:
client_data = self.request.recv(1024)
if client_data.decode('utf8') == 'exit':
print('断开连接')
break
print('接受数据', str(client_data, 'utf_8'))
response = input('响应数据')
self.request.sendall(bytes(response, 'utf8'))
self.request.close()
# 创建socket对象以及 bind() listen()
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), Myserver)
# accept() 等待用户连接
server.serve_forever()
非阻塞和Io多路复用
import select # IO多路复用
import socket
server=socket.socket(socket.AF_INET,socket.socl_STREAM)
server.setblocking(False) # 非阻塞
server.bind('127.0.0.1',8090)
server.listen(5)
inputs = [server,] #socket 对象列表,最多只能放1024个socket对象
while True:
'''
select.select(),把socket对象当作第一个参数传进去,0.05就是在select里面用
0.05的时间检测socket对象列表是否有人发连接或数据,如果没有人发起连接 r = [],
当有人发起连接,r = [发生变化的那个连接],且r只存放当前有变化的连接
w存放成功的连接
e检测异常情况,是否有异常
'''
#io多路复用
r,w,e = select.select(inputs,[],[],0.5)
foe sock in r:
if sock == server:
conn,addr = sock.accept() #接收新连接# conn是客户端连接也会放到inputs列表 addr是地址
print('有连接')
#将客户端发起的连接放进inputs列表
inputs.append(conn)
else:
data = sock.recv(1024)
if data:
print('收到消息',data)
else:
#关闭连接
inputs.remove(sock)
import socket
import select
import uuid
import os
client_list = [] # socket对象列表
for i in range(5):
client = socket.socket()
client.setblocking(False)
try:
# 连接百度,虽然有异常BlockingIOError,但向还是正常发送连接的请求
client.connect(('47.98.134.86', 80))
except BlockingIOError as e:
pass
client_list.append(client)
recv_list = [] # 放已连接成功,且已经把下载图片的请求发过去的socket
while True:
# w = [第一个socket对象,]
# r = [socket对象,]
r, w, e = select.select(recv_list, client_list, [], 0.1)
for sock in w:
# 连接成功,发送数据
# 下载图片的请求
sock.sendall(b"GET /nginx-logo.png HTTP/1.1\r\nHost:47.98.134.86\r\n\r\n")
recv_list.append(sock) #放如r中检测是否有返回数据
client_list.remove(sock) # w中检测的是连接成功的对象,无法检测有变化的连接
for sock in r:
# 数据发送成功后,接收的返回值(图片)并写入到本地文件中
data = sock.recv(8196)
content = data.split(b'\r\n\r\n')[-1]
random_file_name = "{}.png".format(str(uuid.uuid4()))
with open(os.path.join("images", random_file_name), mode='wb') as f:
f.write(content)
recv_list.remove(sock)
if not recv_list and not client_list:
break
"""
基于IO多路复用 + 非阻塞的特性,无论编写socket的服务器和客户端都可以提升性能。
IO多路复用,检测socket对象是否有变化,(连接是否成功,是否有数据到来)
非阻塞:socket的connect recv过程不再需要等待
IO多路复用只能用来监听IO对象是否发生变化,常见的有文件是否可以读写,电脑终端设备的输入和输出
"""
os.sep 根据系统判断是/还是\