Python之面向对象
一、面向对象
1、导入模块-导入自身
import sys def cal(x): x=x+1 return x def plus(x): x=x-1 return x if __name__ == "__main__": obj1 =sys.modules[__name__] # print("===============>>>>",hasattr(obj1,"cal")) ###True ###判断导入的模块是不是有该变量名 if not hasattr(obj1,"cal"): raise TypeError("调用的对象不存在,请检查代码!!") print(cal(1))
2、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)判断obj是否是类cls的对象
class Room: pass class Speace: pass r1 = Room() s1 = Speace() print(isinstance(r1,Room)) ##True print(isinstance(s1,Room)) ##False
issubclass(sub,super)判断sub是否是super的子类
class Speace: pass class Room(Speace): pass class Room1: pass print(issubclass(Room,Speace)) ##True print(issubclass(Room1,Speace)) ###False
3、__setitem__,__getitem__,__delitem__
中括号的调用形式调用的是__item__,
“.”的调用形式调用的是__attr__,
class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key]=value def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item) f1=Foo('sb') f1['age']=18 f1['age1']=19 del f1.age1 del f1['age'] f1['name']='alex' print(f1.__dict__)
4、__str__,__repr__
class Company: def __init__(self,name,add,age): self.name = name self.add = add self.age = age def __str__(self): return "公司名称为%s,地址%s" %(self.name,self.add) c1 = Company("BGY","佛山",1) # print(c1) ###内部定义了__str__方法:公司名称为BGY,地址佛山 # print(c1) ###内部未定义__str__方法:打印为内存地址 x = str(c1) ##str(c1)等同于c1.__str__() print(x)
当__str__与__repr__共存的时候
class Company: def __init__(self,name,add,age): self.name = name self.add = add self.age = age # def __str__(self): # print("执行的是__str__函数") # return "公司名称为%s,地址%s" %(self.name,self.add) def __repr__(self): print("执行的是__repr__函数") return "公司名称为%s,地址%s" %(self.name,self.add) c1 = Company("BGY","佛山",1) # print(c1) ## 当只存在__repr__函数的时候打印结果:公司名称为BGY,地址佛山 print(c1) ###当两者都存在的时候,优先执行__str__函数
当使用format进行自定制格式的时候:
format_dict={ "ymd" :"{0.year}{0.mon}{0.day}", "y-m-d":"{0.year}-{0.mon}-{0.day}", "y:m:d":"{0.year}:{0.mon}:{0.day}" } class Time_format: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): print(format_spec) if not format_spec or format_spec not in format_dict: format_spec = "ymd" fm = format_dict[format_spec] return fm.format(self) t1 = Time_format(2019,12,18) print(format(t1,"y:m:d"))
5、slots属性
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为内存优化工具;
class Foo: __slots__ = "x" f1 = Foo() f2 = Foo() f1.x = 2 f1.y = 2 ###不能再定义新的属性,AttributeError: 'Foo' object has no attribute 'y' f2.x = 20 print(f1.__slots__) ####f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存 print(f1.__dict__) ##答f1.__dict__的时候报错:ttributeError: 'Foo' object has no attribute '__dict__'
6、doc属性
__doc__属性时不能继承给子类
class Foo: '公司属性类' ###__doc__属性 addr = "佛山" def __init__(self,name,age): self.name = name self.age = age f1 = Foo("BGY",1) print(Foo.__dict__) print(f1.__dict__) ###{'name': 'BGY', 'age': 1}
7、__enter__,__exit__上下文管理协议
使用with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法。
class Open: def __init__(self,name): self.name = name def __enter__(self): print("出现with语句,对象的__enter__方法被触发,有返回值则赋予给as声明的变量") def __exit__(self, exc_type, exc_val, exc_tb): print("with中代码块执行完毕后执行我") print(exc_type,exc_val,exc_tb) ###None None None with Open("a.text") as f: print("======>>>开始执行")
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class Open: def __init__(self,name): self.name = name def __enter__(self): print("出现with语句,对象的__enter__方法被触发,有返回值则赋予给as声明的变量") def __exit__(self, exc_type, exc_val, exc_tb): print("with中代码块执行完毕后执行我") print("========>>1、",exc_type) print("========>>2、",exc_val) print("========>>3、",exc_tb) with Open("a.text") as f: print("======>>>开始执行") raise AttributeError("抛出一个异常,后续代码不再执行") print("***************最后执行代码块*******************") """ 出现with语句,对象的__enter__方法被触发,有返回值则赋予给as声明的变量 ======>>>开始执行 with中代码块执行完毕后执行我 ========>>1、 <class 'AttributeError'> ========>>2、 抛出一个异常,后续代码不再执行 ========>>3、 <traceback object at 0x000001C884C8C540> """
如果__exit()__的返回值为True,那么异常会被清空,就好像啥都没有发生一样,with后的语句正常执行
class Open: def __init__(self,name): self.name = name def __enter__(self): print("出现with语句,对象的__enter__方法被触发,有返回值则赋予给as声明的变量") def __exit__(self, exc_type, exc_val, exc_tb): print("with中代码块执行完毕后执行我") print("========>>1、",exc_type) print("========>>2、",exc_val) print("========>>3、",exc_tb) return True with Open("a.text") as f: print("======>>>开始执行") raise AttributeError("抛出一个异常,后续代码不再执行") print("***************最后执行代码块*******************") """ 出现with语句,对象的__enter__方法被触发,有返回值则赋予给as声明的变量 ======>>>开始执行 with中代码块执行完毕后执行我 ========>>1、 <class 'AttributeError'> ========>>2、 抛出一个异常,后续代码不再执行 ========>>3、 <traceback object at 0x00000276FF9BC500> ***************最后执行代码块******************* """
##模拟读写 class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding def __enter__(self): # print('enter') self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f,item) with Open('a.txt','w') as f: print(f) f.write('aaaaaa') f.wasdf #抛出异常,交给__exit__处理
备注:
a、使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预;
在需要管理一些资源比如文件,网络连接和锁的编程过程中,可以在__exit__中定制自动释放资源的机制。
b、with obj =====》》触发obj.__enter__(),拿到返回值;
as f =======》》f=返回值
with obj as f 等同于f = obj.__enter__()
执行代码块:
没有异常的情况下,整个代码块运行完毕后去触发__exit__方法;
有异常的情况下,从异常出现的位置直接触发__exit__
如果__exit__的返回值为True,代表吞掉了异常;
如果__exit__的返回值不为True,代表吐出了异常;
exit运行完毕后代表着这个with语句执行完毕;
8、__module__,__class__
__module__表示当前操作的对象在哪个模块;
__class__表示当前操作的对象的类是什么;
###同级目录有delay_cal文件,定义了一个Room方法 from delay_cal import Room r2 = Room("A6-07",2,3) print("=======>>>",r2.__module__) ##=======>>> delay_cal print("=======>>>",r2.__class__) ###=======>>> <class 'delay_cal.Room'>
9、__del__析构方法
析构方法,当对象在内存中被释放时,自动触发执行
注意:如果产生的对象仅仅只是python程序级别的(用户级),那么无须定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级和内核级的两种资源,比如(打开一个文件
创建一个数据库连接),则必须在清除对象的同时回收系统资源,这就用到了__del__.
示例:创建数据库类,用该类实例出数据库连接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中;当程序结束时,python只会回收自己的内存空间,即用户态内存,而
操作系统的资源则没有被回收,这就需要我们定制__del__,在对象呗删除前向操作系统发起关闭数据库链接的系统调用,回收资源。
f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 del f #只回收用户空间的f,操作系统的文件还处于打开状态 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 f=open('a.txt') 读写... f.close() 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
10、__cal__
对象后面加括号,触发执行
构造方法的执行由创建对象触发的,即:对象=类名();而对于__call__方法的执行是由对象加括号触发的,即:对象()或者类名()()
class Foo: def __init__(self): print("========》》实例化触发init方法执行") def __call__(self, *args, **kwargs): print('========》》》',"执行call方法") obj = Foo() # 实例化触发执行 __init__ obj() # 执行 __call__
11、__next__和__iter__实现迭代器协议
class Range: def __init__(self,n,stop,step): self.n=n self.stop=stop self.step=step def __next__(self): if self.n > self.stop: raise StopIteration x=self.n self.n+=self.step return x def __iter__(self): return self for i in Range(1,100,3): # print(i)
使用__next__和__iter__方法实现迭代器协议和斐波那契数列
class Fib: def __init__(self): self._a=0 self._b=1 def __iter__(self): return self def __next__(self): self._a,self._b=self._b,self._a + self._b return self._a f1=Fib() for i in f1: if i > 100: break print('%s ' %i,end='')
12、描述符
(1)、描述符的本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__(),中的一个,这也被称为描述符协议;
__get__():调用一个属性时触发;
__set__():为一个属性赋值时触发;
__delete__():采用del删除属性时触发;
(2)、描述符的作用就是用来代理一个类的属性的(必须把描述符定义为这个类的类属性,不能定义到构造函数中)
a、描述符产生的实例进行属性操作的时候并不会触发发个方法的执行
class Foo: def __get__(self, instance, owner): print("执行get方法") def __set__(self, instance, value): print("执行set方法") def __delete__(self, instance): print("执行delete方法") ##实例化 foo = Foo() foo.name = "heaton" ##赋值并没有触发__set__函数属性 print(foo.__dict__)
b、触发描述符
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age #何地?:定义成另外一个类的类属性 #何时?:且看下列演示 p1=People('alex',18) ##触发了str/int类的__set__方法 print(p1.__dict__) ##{} #描述符Int的使用 p1.age ###触发了int类的__get__方法 p1.age=18 ###触发了int类的__set__方法 del p1.age ###触发了int类的__delete__方法 # #我们来瞅瞅到底发生了什么 print(p1.__dict__) print(People.__dict__) ##数据属性里面有'age': <__main__.Int object at 0x0000015F640B75B0>键值对 # # #补充 print(type(p1)) #type(obj)其实是查看obj是由哪个类实例化来的 ###<class '__main__.People'>
(3)、描述符分类
a、数据描述符:至少实现了__get__和__set__方法
b、非数据描述符:没有实现__set__方法
(4)、备注
a、描述符本身应该定义成新式类,被代理的类也应该是新式类;
b、必须把描述符定义成这个类的属性,不能定义到够着函数中;
c、要严格遵循下面的优先级:优先级由高到低分别是:
类属性>数据描述符>实例属性>非数据描述符>找不到属性触发__getattr__()
###类属性优先级大于数据描述符
###类在调用属性的时候
class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age People.name People.name = 19 ###该方法并没有触发描述符的__set__方法 print(People.__dict__) ###属性字典类有该键值对:'name': 19 , del People.name ##没哟触发描述的__delete__方法
"""
原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() del People.name #同上
"""
####数据描述符优先级大于实例属性 class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age p1 = People("heaton",20) ##Str设置...Int设置... p1.name ##Str调用 p1.name = 20 ###Str设置... del p1.name ###Str删除..
###实例属性优先级大于非数据属性 # class Foo: # def __set__(self, instance, value): # print('set') # def __get__(self, instance, owner): # print('get') # class Room: # name=Foo() # def __init__(self,name,width,length): # self.name=name # self.width=width # self.length=length # # # #name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级 # #对实例的属性操作,触发的都是描述符的 # r1=Room('厕所',1,1) # r1.name # r1.name='厨房' class Foo: def __get__(self, instance, owner): print('get') class Room: name=Foo() def __init__(self,name,width,length): self.name=name self.width=width self.length=length #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 #对实例的属性操作,触发的都是实例自己的 r1=Room('厕所',1,1) r1.name r1.name='厨房' print(r1.__dict__) ###{'name': '厨房', 'width': 1, 'length': 1}
(5)、描述符的使用
python属于弱类型语言,即参数的赋值没有类型限制,我们可以通过描述符来实现类型限制功能
###对属性name,age进行检查,然后处理成期望的数据类型 class Check_data: def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type print(self.key,self.expect_type) def __get__(self, instance, owner): print("get方法") return instance.__dict__[self.key] def __set__(self, instance, value): print("执行的是set方法") print("=========>>>",self,instance,value) if type(value) is not self.expect_type: value = self.expect_type(value) instance.__dict__[self.key] = value else : # raise TypeError("你输入的值不符合预期的数据类型:%s" %self.expect_type) instance.__dict__[self.key] = value class People: name = Check_data("name",str) age = Check_data("age",int) # age = Check_age() def __init__(self,name,age,sal): self.name = name self.age = age self.sal = sal p1 = People(123,19,2000) # print(p1.name,type(p1.name)) print(People.__dict__) print(p1.__dict__)
备注:描述符可以实现大部分python类特性中的底层魔法,包括,@classmethod,__slots__属性;
描述符是很多高级库和框架的重要工具之一,描述符通常使用到装饰器或者元类的大型框架中的一个组件;
自定义延迟计算
###模拟property静态属性 class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): print("get") print(instance) print(owner) print("=========>>>",self.func) res= self.func(instance) setattr(instance,self.func.__name__,res) return res class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty def area(self): return self.width*self.length r1 = Room("A6-07",1,1) print(r1.area) print(Room.__dict__)
13、类的装饰器
###描述符 +装饰器 class Check_data: def __init__(self,key,expect_type): self.key = key self.expect_type = expect_type def __get__(self, instance, owner): print("get方法") return instance.__dict__[self.key] def __set__(self, instance, value): print("set方法") print(self) if type(value) is not self.expect_type: value = self.expect_type(value) instance.__dict__[self.key] = value else : # raise TypeError("你输入的值不符合预期的数据类型:%s" %self.expect_type) instance.__dict__[self.key] = value ###装饰器 def func(**kwargs): def deco(obj): print(obj) for key,value in kwargs.items(): setattr(obj,key,Check_data(key,value)) return obj return deco @func(name=str,age=int,sal=float) class People: # name = Check_data("name",str) # age = Check_data("age",int) # # age = Check_age() def __init__(self,name,age,sal): self.name = name self.age = age self.sal = sal p1 = People(123,19,2000) # print(p1.name,type(p1.name)) print(p1.__dict__)