python/面向对象(三)
python/面向对象(三)
一、面向对象进阶:
isinstance(obj,Foo)检查obj是否是类Foo的对象
1 class Foo(object): 2 pass 3 4 obj = Foo() 5 6 isinstance(obj, Foo)
issubclass(Bar,Foo)检查Bar类是不是Foo类的派生类
1 class Foo(object): 2 pass 3 4 class Bar(Foo): 5 pass 6 7 issubclass(Bar, Foo)
二、__setattr__,__delattr__,__getattr__
通过__setattr__,__delattr__,__getattr__方式把对象操作属性模拟成字符串的格式:
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的属性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #这就无限递归了,你好好想想 # self.__dict__[key]=value #应该使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item) #__setattr__添加/修改属性会触发它的执行 f1=Foo(10) print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z=3 print(f1.__dict__) #__delattr__删除属性的时候会触发 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx 复制代码
三、 二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
class List(list): def append(self, p_object): if not isinstance(p_object,int): raise TypeError('must be int') super().append(p_object) @property def mid(self): #l index=len(self)//2 #index=len(l)//2 return self[index] #l[index=len(l)//2] l=List([1,2,3,4,]) #自己定义的List并进行了实例化 print(l) l.append(5) #给自己定义的List增加值 ,这里设置了只能传整型。 print(l) print(l.mid) #打印列表的长度一半 l.insert(0,-1) #这个插入传值是传给了父类list然后又继承过来的,就没有设置字符串不可以传值 print(l) l.clear() #这个操作也是操作父类list 然后又继承过来的 print(l)
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
##基于授权的操作 import time class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): self.file=open(filename,mode=mode,encoding=encoding) #创建一个文件句柄 def write(self,line): t=time.strftime('%y-%m-%t') self.file.write('%s %s'%(t,line)) #通过文件句柄把传进来的line和t写到文件中 def __getattr__(self, item): ##实现授权的关键点就是覆盖__getattr__方法 return getattr(self.file,item) f1=FileHandle('b.txt','w+') f1.write('nihao') print(f1.read()) f1.close()
四、@property
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('AAAAAAAA') @AAA.deleter def AAA(self): print('delte的时候运行我') f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
五、__setitem__,__getitem,__delitem__
通过__setitem__,__getitem,__delitem__方式把对象操作属性模拟成字符串的格式:
class Foo: ##通过__方式把对象操作属性模拟成字典的格式: x=1 def __init__(self,y): self.y=y def __getitem__(self, item): ##只能在使用调用属性不存在的时候才会触发 print('---->from gerattr:你找的属性不存在') def __setitem__(self, key, value): #设置修改 print('--->from setattr') self.__dict__[key]=value #给字典添加键和值 def __delitem__(self, key): #删除字典 item 就是要删除的key的变量 print('-->from delattr') self.__dict__.pop(key) #在字典里通过pop模式进行删除 f1=Foo(10) print(f1.y) f1.z=3 f1.__dict__['a']=3 #通过修改的方式给字典添加键和值 print(f1.__dict__) del f1.a #删除要删掉的字典key key在赋值给item print(f1.__dict__)
六、__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
fromat_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}', 'tna':'{obj.type}:{obj.name}:{obj.addr}', 'tan':'{obj.type}/{obj.addr}/{obj.name}', } class school: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __repr__(self): return 'School(%s,%s)' %(self.name,self.addr) def __str__(self): return '(%s , %s)'%(self.name,self.addr) def __format__(self, format_spec): if not fromat_spec or format_spec not in format_dict: frmat_spec='nat' fmt=fromat_dict[format_spec] return fmt.fromat(obj=self) s1=School('oldboy1','北京','私立') print('from repr: ',repr(s1)) print('from str: ',str(s1)) print(s1) ''' str函数或者print函数--->obj.__str__() repr或者交互式解释器--->obj.__repr__() 如果__str__没有被定义,那么就会使用__repr__来代替输出 注意:这俩方法的返回值必须是字符串,否则抛出异常 ''' print(format(s1,'nat')) print(format(s1,'tna')) print(format(s1,'tan')) print(format(s1,'asfdasdffd'))
七、__slots__
__slots__:实例化的对象将没有名称空间,都保存在类的名称空间,而且只能设置指定的属性,例如下面的例子,每个实例只能设置x,y,z三个属性 class Foo: __slots__=['x','y','z'] f1=Foo() f1.x=1 f1.y=2 f1.z=3 print(f1.__slots__) print(f1.x,f1.y,f1.z)
八、__next__和__iter__实现迭代器协议
python 中所有的迭代环境都会先尝试__iter__方法,再尝试__getitem__,也就是说,只有对象在不支持迭代的情况下,
才会尝试索引的方式运算。 Python中,迭代环境是通过调用内置函数iter去尝试寻找__iter__方法来实现的,
而这种方法返回一个迭代器对象,如果已经提供了,Python就会重复调用这个迭代器对象的next方法,知道发生了StopIteration异常
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
n=self.x
self.x+=1
return self.x
f=Foo(3)
for i in f:
print(i)
from collections import Iterable,Iterator class Foo: def __init__(self,start,stop): #实例化对象传进来俩个值 self.num=start #把第一个值赋值 self.stop=stop #把第二个值赋值 def __iter__(self): #迭代对象 return self #然后返回对象 def __next__(self): #执行迭代 if self.num>=self.stop: #如果开始值等于大于停止值 raise StopIteration #抛出异常 n=self.num #给初始值进行赋值 self.num+=1 #进行自加 return self.num #返回自加过后的值 f=Foo(1,5) # print(isinstance(f,Iterator)) for i in Foo(1,5): print(i)
九、实现上下文管理:with/as,__enter__,__exit__
with open('a.txt') as f:#with代码块后会自定关闭文件,无论是否发生异常,一下是with的工作方式:
1、计算表达式,所得到的对象称为环境管理器,他必须有__enter__,__exit__方法。
2、环境管理器的__enter__方法会被调用。如果as字句存在,器返回值会赋值给as字句中的变量,否则直接丢弃。
3、代码块中嵌套的代码会执行。
4、如果with代码块引发异常,__exit__(type,value,traceback)方法就会调用(带有异常细节)
5、如果with代码块没有引发异常,__exit__方法依然会被调用,其type,value,traceback参数都会以none传递
import time class Open: def __init__(self,filepath,mode='r',encoding='utf8'): self.filepath=filepath self.mode=mode self.encoding=encoding self.x=open(filepath,mode=mode,encoding=encoding) def write(self,line): t=time.strftime('%Y-%m-%d %X') self.x.write('%s %s' %(t,line)) def __getattr__(self, item): return getattr(self.x,item) def __enter__(self): print("*************") return self def __exit__(self, exc_type, exc_val, exc_tb): print('文件关闭') self.x.close() with Open('a.txt','w') as f: #出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 f.write("abc") #调用自己定义的write方法,把‘abc’ 写入到a.txt文件 print('==============') #这一行代码执行完毕后,会触发__exit__方法,关闭文件 print("继续执行代码") #代码执行如下 ************* ============== 文件关闭 继续执行代码
十、__call__:python会为实例用函数调用的表达式运行__call__方法,这样就可以让类实例的外观看起来像是函数
class callee: def __call__(self, *args, **kwargs): print('call:',args,kwargs) C=callee() C(1,2,3) #实例加()后,会调用__call__方法 C(1,2,3,X=4,Y=5) #打印结果如下 call: (1, 2, 3) {} call: (1, 2, 3) {'X': 4, 'Y': 5}
十一、__del__:析构函数
每当实例产生时,就对调用__init__构造函数,没当实例空间被收回时,就会调用__del__析构函数。吃方法一般不需定义,python 有自己的内存回收机制
import time class Open: def __init__(self,filepath,mode='r',encoding='utf8'): self.filepath=filepath self.mode=mode self.encoding=encoding self.x=open(filepath,mode=mode,encoding=encoding) def __del__(self): print('del') self.x.close() f=Open('a.txt','w') del f
对象是由类产生的, 类是由type产生的, type继续由type产生
type()手动创建类需要三个元素,
一个是字符串类型, 表示类名
一个是元组类型, 表示父类的集合
一个是字典类型, 表示绑定的属性和方法
具体实现如下
|
1
2
3
4
5
6
7
8
9
10
|
def run(self): print("running..")PeopleClass = type("People",(object,), {"country":"China",'run':run})whc = PeopleClass()print(PeopleClass)print(whc)# <class '__main__.people'=""># <__main__.People object at 0x0000000000B27CC0></class> |
十二、元类
十二.一利用元类新增功能
可以写一个元类Mateclass, 它需要继承自type类
原来的类需要关联该元类, 也就是在继承中有 metaclass=元类名字
此时执行元类就可以生成一个对象, 也就是创建的这个类
基于此, 就是元类中的__init__()方法创建的 类对象, 所以新加的功能只需在__init__()方法中就行
元类的__init__()有额外三个参数, 分别是类名, 类基类, 类属性字典
实现检查__doc__的代码如下
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class MyMetaclass(type): def __init__(self, class_name, class_bases, class_dic): for key, value in class_dic.items(): if callable(value) and not value.__doc__: raise Exception("{}方法内必须写入注释..".format(key))class Foo(metaclass=MyMetaclass): x = 1 def __init__(self, name): self.name = name def run(self): 'run function' print('running') def go(self): print('going') |
十二.二 新建对象的过程
首先, 类要生成对象, 类本身需要可调用
针对于基类, 类是基类的对象, 也就是说在基类中, 需要有一个__call__()函数, 而这个函数, 是在类的__init__之前执行的
在基类的__call__()方法中, 需要使用self.__new__(self)来创建一个空对象, 这个对象就是类
有了类之后就可以调用原有的方法__init__(), 这时在其中就是熟悉的生成对象了
最后再返回这个类就行了
完成整个过程的代码如下
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class MyMetaclass(type): def __call__(self, *args, **kwargs): obj = self.__new__(self) self.__init__(obj, *args, **kwargs) # obj.name='egon' return objclass People(metaclass=MyMetaclass): def __init__(self, name): self.name = namewhc = People('whc')print(whc.name) |

浙公网安备 33010602011771号