面向对象学习目录
七、再看property
一个静态属性property本质就是实现了get,set,delete三种方法
用法一
1 class Foo: 2 @property 3 def AAA(self): 4 print('get的时候运行我啊') 5 6 @AAA.setter 7 def AAA(self,value): 8 print('set的时候运行我啊') 9 10 @AAA.deleter 11 def AAA(self): 12 print('delete的时候运行我啊') 13 14 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 15 f1=Foo() 16 f1.AAA 17 f1.AAA='aaa' 18 del f1.AAA
用法二
1 class Foo: 2 def get_AAA(self): 3 print('get的时候运行我啊') 4 5 def set_AAA(self,value): 6 print('set的时候运行我啊') 7 8 def delete_AAA(self): 9 print('delete的时候运行我啊') 10 11 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 12 13 f1=Foo() 14 f1.AAA 15 f1.AAA='aaa' 16 del f1.AAA
怎么用?
案例一
1 class Goods: 2 def __init__(self): 3 # 原价 4 self.original_price = 100 5 # 折扣 6 self.discount = 0.8 7 8 @property 9 def price(self): 10 # 实际价格 = 原价 * 折扣 11 new_price = self.original_price * self.discount 12 return new_price 13 14 @price.setter 15 def price(self, value): 16 self.original_price = value 17 18 @price.deleter 19 def price(self): 20 del self.original_price 21 22 23 obj = Goods() 24 obj.price # 获取商品价格 25 obj.price = 200 # 修改商品原价 26 print(obj.price) 27 del obj.price # 删除商品原价
案例二
1 #实现类型检测功能 2 3 #第一关: 4 class People: 5 def __init__(self,name): 6 self.name=name 7 8 @property 9 def name(self): 10 return self.name 11 12 # p1=People('alex') 13 #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常 14 15 16 #第二关:修订版 17 18 class People: 19 def __init__(self,name): 20 self.name=name #实例化就触发property 21 22 @property 23 def name(self): 24 # return self.name #无限递归 25 print('get------>') 26 return self.DouNiWan 27 28 @name.setter 29 def name(self,value): 30 print('set------>') 31 self.DouNiWan=value 32 33 @name.deleter 34 def name(self): 35 print('delete------>') 36 del self.DouNiWan 37 38 p1=People('alex') #self.name实际是存放到self.DouNiWan里 39 print(p1.name) 40 print(p1.name) 41 print(p1.name) 42 print(p1.__dict__) 43 44 p1.name='egon' 45 print(p1.__dict__) 46 47 del p1.name 48 print(p1.__dict__) 49 50 51 #第三关:加上类型检查 52 class People: 53 def __init__(self,name): 54 self.name=name #实例化就触发property 55 56 @property 57 def name(self): 58 # return self.name #无限递归 59 print('get------>') 60 return self.DouNiWan 61 62 @name.setter 63 def name(self,value): 64 print('set------>') 65 if not isinstance(value,str): 66 raise TypeError('必须是字符串类型') 67 self.DouNiWan=value 68 69 @name.deleter 70 def name(self): 71 print('delete------>') 72 del self.DouNiWan 73 74 p1=People('alex') #self.name实际是存放到self.DouNiWan里 75 p1.name=1
八、__setitem__,__getitem__,__delitem__
1 class Foo: 2 def __init__(self,name): 3 self.name=name 4 5 def __getitem__(self, item): 6 print(self.__dict__[item]) 7 8 def __setitem__(self, key, value): 9 self.__dict__[key]=value 10 def __delitem__(self, key): 11 print('del obj[key]时,我执行') 12 self.__dict__.pop(key) 13 def __delattr__(self, item): 14 print('del obj.key时,我执行') 15 self.__dict__.pop(item) 16 17 f1=Foo('sb') 18 f1['age']=18 19 f1['age1']=19 20 del f1.age1 21 del f1['age'] 22 f1['name']='alex' 23 print(f1.__dict__)
九、__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
1 format_dict={ 2 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 3 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 4 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名 5 } 6 7 class School: 8 def __init__(self,name,addr,type): 9 self.name=name 10 self.addr=addr 11 self.type=type 12 13 def __repr__(self): 14 return 'School(%s,%s)' %(self.name,self.addr) 15 def __str__(self): 16 return '(%s,%s)' %(self.name,self.addr) 17 18 def __format__(self, format_spec): 19 # if format_spec 20 if not format_spec or format_spec not in format_dict: 21 format_spec='nat' 22 fmt=format_dict[format_spec] 23 return fmt.format(obj=self) 24 25 s1=School('oldboy1','北京','私立') 26 print('from repr: ',repr(s1)) 27 print('from str: ',str(s1)) 28 print(s1) 29 30 ''' 31 str函数或者print函数--->obj.__str__() 32 repr或者交互式解释器--->obj.__repr__() 33 如果__str__没有被定义,那么就会使用__repr__来代替输出 34 注意:这俩方法的返回值必须是字符串,否则抛出异常 35 ''' 36 print(format(s1,'nat')) 37 print(format(s1,'tna')) 38 print(format(s1,'tan')) 39 print(format(s1,'asfdasdffd'))
自定义format练习
1 date_dic={ 2 'ymd':'{0.year}:{0.month}:{0.day}', 3 'dmy':'{0.day}/{0.month}/{0.year}', 4 'mdy':'{0.month}-{0.day}-{0.year}', 5 } 6 7 class Date: 8 def __init__(self,year,month,day): 9 self.year=year 10 self.month=month 11 self.day=day 12 13 def __format__(self, format_spec): 14 if not format_spec or format_spec not in date_dic: 15 format_spec='ymd' 16 fmt=date_dic[format_spec] 17 return fmt.format(self) 18 19 d1=Date(2016,12,29) 20 print(format(d1)) 21 print('{:mdy}'.format(d1))
issubclass和isinstance
1 class A: 2 pass 3 4 class B(A): 5 pass 6 7 print(issubclass(B,A)) #B是A的子类,返回True 8 9 a1=A() 10 print(isinstance(a1,A)) #a1是A的实例
十、slots
slots使用
1 ''' 2 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 3 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 4 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 5 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 6 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 7 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 8 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 9 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 10 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 11 12 ''' 13 class Foo: 14 __slots__='x' 15 16 17 f1=Foo() 18 f1.x=1 19 f1.y=2 #报错 20 print(f1.__slots__) #f1不再有__dict__ 21 22 class Bar: 23 __slots__=['x','y'] 24 25 n=Bar() 26 n.x,n.y=1,2 27 n.z=3 #报错
刨根问底
1 class Foo: 2 __slots__=['name','age'] 3 4 f1=Foo() 5 f1.name='alex' 6 f1.age=18 7 print(f1.__slots__) 8 9 f2=Foo() 10 f2.name='egon' 11 f2.age=19 12 print(f2.__slots__) 13 14 print(Foo.__dict__) #f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
十一、__next__和__iter__实现迭代器协议
简单示范
1 class Foo: 2 def __init__(self,x): 3 self.x=x 4 5 def __iter__(self): 6 return self 7 8 def __next__(self): 9 n=self.x 10 self.x+=1 11 return self.x 12 13 f=Foo(3) 14 for i in f: 15 print(i)
1 class Foo: 2 def __init__(self,start,stop): 3 self.num=start 4 self.stop=stop 5 def __iter__(self): 6 return self 7 def __next__(self): 8 if self.num >= self.stop: 9 raise StopIteration 10 n=self.num 11 self.num+=1 12 return n 13 14 f=Foo(1,5) 15 16 from collections import Iterable,Iterator 17 print(isinstance(f,Iterator)) 18 19 for i in Foo(1,5): # obj=iter(Foo(1,5)) ---> 相当于Foo(1,5).__iter__() 20 print(i) # obj.__next__(),直到遇到StopIteration就自动终止(但for循环不会抛出异常报错)
练习:简单模拟range,加上步长
1 class Range: 2 def __init__(self,n,stop,step): 3 self.n=n 4 self.stop=stop 5 self.step=step 6 7 def __next__(self): 8 if self.n >= self.stop: 9 raise StopIteration 10 x=self.n 11 self.n+=self.step 12 return x 13 14 def __iter__(self): 15 return self 16 17 for i in Range(1,7,3): # obj=iter(Range(1,7,3)) ---> 相当于Range(1,7,3).__iter__() 18 print(i) # obj.__next__(),直到遇到StopIteration就自动终止(但for循环不会抛出异常报错)
斐波那契数列
1 class Fib: 2 def __init__(self): 3 self._a=0 4 self._b=1 5 6 def __iter__(self): 7 return self 8 9 def __next__(self): 10 self._a,self._b=self._b,self._a + self._b 11 return self._a 12 13 f1=Fib() 14 15 print(f1.__next__()) 16 print(next(f1)) 17 print(next(f1)) 18 19 for i in f1: 20 if i > 100: 21 break 22 print('%s ' %i,end='')
十二、__doc__
它类的描述信息
1 class Foo: 2 '我是描述信息' 3 pass 4 5 print(Foo.__doc__)
该属性无法被继承
1 class Foo: 2 '我是描述信息' 3 pass 4 5 class Bar(Foo): 6 pass 7 8 print(Bar.__doc__) #该属性无法继承给子类
十三、__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
lib/aa.py
1 class C: 2 def __init__(self): 3 self.name = ‘SB'
index.py
1 from lib.aa import C 2 3 obj = C() 4 print obj.__module__ # 输出 lib.aa,即:输出模块 5 print obj.__class__ # 输出 lib.aa.C,即:输出类
十四、__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__。
简单示范
1 class Foo: 2 def __del__(self): 3 print('执行我啦') 4 5 f1=Foo() 6 del f1 7 print('------->') 8 9 #输出结果 10 执行我啦 11 ------->
挖坑埋了你
1 class Foo: 2 def __del__(self): 3 print('执行我啦') 4 5 f1=Foo() # del f1 6 print('------->') 7 8 #输出结果 9 -------> 10 执行我啦
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:
1 f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 2 del f #只回收用户空间的f,操作系统的文件还处于打开状态 3 4 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 5 f=open('a.txt') 6 读写... 7 f.close() 8 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
十五、__enter__和__exit__
我们知道在操作文件对象的时候可以这么写
1 with open('a.txt') as f: 2 '代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
上下文管理协议
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 def __enter__(self): 5 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 6 # return self 7 def __exit__(self, exc_type, exc_val, exc_tb): 8 print('with中代码块执行完毕时执行我啊') 9 10 11 with Open('a.txt') as f: 12 print('=====>执行代码块') 13 # print(f,f.name)
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) 11 print(exc_val) 12 print(exc_tb) 13 14 15 with Open('a.txt') as f: 16 print('=====>执行代码块') 17 raise AttributeError('***着火啦,救火啊***') 18 print('0'*100) #------------------------------->不会执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) 11 print(exc_val) 12 print(exc_tb) 13 return True 14 15 16 with Open('a.txt') as f: 17 print('=====>执行代码块') 18 raise AttributeError('***着火啦,救火啊***') 19 print('0'*100) #------------------------------->会执行
练习:模拟Open
1 class Open: 2 def __init__(self,filepath,mode='r',encoding='utf-8'): 3 self.filepath=filepath 4 self.mode=mode 5 self.encoding=encoding 6 7 def __enter__(self): 8 # print('enter') 9 self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) 10 return self.f 11 12 def __exit__(self, exc_type, exc_val, exc_tb): 13 # print('exit') 14 self.f.close() 15 return True 16 def __getattr__(self, item): 17 return getattr(self.f,item) 18 19 with Open('a.txt','w') as f: 20 print(f) 21 f.write('aaaaaa') 22 f.wasdf #抛出异常,交给__exit__处理
用途或者说好处:
- 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
- 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
十六、__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
1 class Foo: 2 def __init__(self): 3 pass 4 5 def __call__(self, *args, **kwargs): 6 print('__call__') 7 8 9 obj = Foo() # 执行 __init__ 10 obj() # 执行 __call__