实现授权的关键点就是覆盖__getattr__方法
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
#我们来加上b模式支持
importtimeclassFileHandle:def __init__(self,filename,mode='r',encoding='utf-8'):if 'b' inmode:
self.file=open(filename,mode)else:
self.file=open(filename,mode,encoding=encoding)
self.filename=filename
self.mode=mode
self.encoding=encodingdefwrite(self,line):if 'b' inself.mode:if notisinstance(line,bytes):raise TypeError('must be bytes')
self.file.write(line)def __getattr__(self, item):returngetattr(self.file,item)def __str__(self):if 'b' inself.mode:
res="<_io.bufferedreader name="%s">" %self.filenameelse:
res="<_io.textiowrapper name="%s" mode="%s" encoding="%s">" %(self.filename,self.mode,self.encoding)returnres
f1=FileHandle('b.txt','wb')#f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))print(f1)
f1.close()
文件操作支持b模式
六 __getattribute__
__getattribute__不管属性存不存在,都会执行
classFoo:def __init__(self,x):
self.x=xdef __getattribute__(self, item):print('不管是否存在,我都会执行')
f1=Foo(10)
f1.x
f1.xxxxxx
输出结果:
不管是否存在,我都会执行
不管是否存在,我都会执行
__getattribute__
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
classFoo:def __init__(self,x):
self.x=xdef __getattr__(self, item):print('执行的是我')#return self.__dict__[item]
def __getattribute__(self, item):print('不管是否存在,我都会执行')raise AttributeError('哈哈')
f1=Foo(10)
f1.x
f1.xxxxxx#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
七 __str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__, repr() 输出对 Python比较友好,而str()的输出对用户比较友好。
自定制格式化字符串__format__
str()触发__str__,repr()触发repr,format()触发__format__
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'format_dict={'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
}classSchool:def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=typedef __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 format_spec
if not format_spec or format_spec not informat_dict:
format_spec='nat'fmt=format_dict[format_spec]return fmt.format(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'))
View Code
八 __slots__
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
classFoo:__slots__ = 'x'f1=Foo()
f1.x= 1
print(f1.__dict__) #报错,f1不再有__dict__,只能通过__slots来查看定义的属性
#---------------------分割线---------------------------
classBar:__slots__ = ['x', 'y']
n=Bar()
n.x, n.y= 1, 2n.z= 3 #报错
'''__slots__ 只定义了x,y变量,所以所有的对象的名称空间只能有这两个属性,
无法再新增新的属性'''
classFoo:__slots__=['name','age']
f1=Foo()
f1.name='alex'f1.age=18
print(f1.__slots__)
f2=Foo()
f2.name='egon'f2.age=19
print(f2.__slots__)print(Foo.__dict__)#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
验证对象无字典
九 __next__和__iter__实现迭代器协议
classRange:def __init__(self,start=0,end=None,n=1):
self.start=start
self.end=end
self.n=ndef __iter__(self):returnselfdef __next__(self):if notself.end:
self.end=self.start
self.start=0
x=self.startif x
self.start+=self.nreturnxelse:raise StopIteration('这已经是最后一个值了')
f=Range(2,10,2)for i inf:print(i)from collections import Iterable,Iterator #Iterator可以用来判断是否为生成器.
#Iterable用来 判断对象是否可迭代。
print(isinstance(f,Iterator)) #True
模拟range函数
十 __doc__
classFoo:'我是描述信息'
pass
print(Foo.__doc__)
classFoo:'我是描述信息'
pass
classBar(Foo):pass
print(Bar.__doc__) #该属性无法继承给子类
十一 __module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
在tmp.py模块下,创建一个类,然后再pra.py中导入tmp.py
classC:def __init__(self):
self.name= 'test'
tmp.py
from tmp import *c=C()print(c.__class__) #
print(c.__module__) #tmp
pra.py
十二 __del__
析构方法,当对象在内存中被释放时,自动触发执行。()
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
importtimeclassFoo:def __del__(self):print('执行我啦')
f1=Foo()del f1 #删除f1,即释放f1和他对应的内存关联。内存被回收,触发析构函数。
time.sleep(2)print('------->')
输出结果:
执行我啦------->
简单示例
importtimeclassFoo:def __del__(self):print('执行我啦')
f1=Foo()#del f1 #此时,不主动删除f1,那么需要等程序执行完毕,释放掉内存时,f1内存被回收
#才会触发析构函数
time.sleep(2)print('------->')
输出结果:------->执行我啦
进一步探讨
十三 __enter__和__exit__
我们知道,在操作文件时可以使用with来写代码块,如下:
with open('a.txt') as f: #等于f=open('a.txt')
f.read()
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法.
classOpen:def __init__(self,name):
self.name=namedef __enter__(self):print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')#return self
def __exit__(self, exc_type, exc_val, exc_tb):print('with中代码块执行完毕时执行我啊')
with Open('a.txt') as f:print('=====>执行代码块')
输出结果:
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量=====>执行代码块
with中代码块执行完毕时执行我啊
with的基础构造
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
classOpen:def __init__(self,name):
self.name=namedef __enter__(self):print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')def __exit__(self, exc_type, exc_val, exc_tb):print('with中代码块执行完毕时执行我啊')print(exc_type)print(exc_val)print(exc_tb)
with Open('a.txt') as f:print('=====>执行代码块')raise AttributeError('***着火啦,救火啊***')print('0'*100) #------------------------------->不会执行
输出结果:
Traceback (most recent call last):
File"D:/Users/wujy1/PycharmProjects/oldboy/类/练习/pra.py", line 562, in
raise AttributeError('***着火啦,救火啊***')
AttributeError:***着火啦,救火啊***出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量=====>执行代码块
with中代码块执行完毕时执行我啊
***着火啦,救火啊***
异常信息
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
classOpen:def __init__(self,name):
self.name=namedef __enter__(self):print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')def __exit__(self, exc_type, exc_val, exc_tb):print('with中代码块执行完毕时执行我啊')print(exc_type)print(exc_val)print(exc_tb)return True #返回为True,则忽略异常
with Open('a.txt') as f:print('=====>执行代码块')raise AttributeError('***着火啦,救火啊***')print('0'*100) #------------------------------->正常执行
忽略异常
classOpen:def __enter__(self):returnself.fdef __init__(self,filename,m='r',encode='utf-8'):
self.f=open(filename,mode=m,encoding=encode)defwrite(self,item):
self.f.write(item)def __getattr__(self, item):returngetattr(self.f,item)def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()return True #不抛异常
def __str__(self):return 'hahahah'with Open('a.txt','w') as f: #f=Open('a.txt','w')
l=['1','2','3']
f.writelines(l)
模拟with
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中(数据库链接),可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。
十四 __call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
classFoo:def __init__(self):pass
def __call__(self, *args, **kwargs):print('__call__')
obj= Foo() #执行 __init__
obj() #执行 __call__
View Code
十五 metaclass
exec:三个参数 exec(object,globals,locals)
参数一:字符串形式的命令
参数二:全局作用域
参数三:局部作用域
exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字
g={'x':1,'y':2,'teachers':{'egon','alex','yuanhao','wupeiqi'}
}
l={'birds':[1,2,3]}#exec("""#def func():#global x#x='egon'#func()#""",g,l)#print(g)#print(l)
exec("""global x
x='egon'""",g,l)print(g)print(l)
exec
一.类也是对象
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:
把类赋值给一个变量
把类作为函数参数进行传递
把类作为函数的返回值
在运行时动态地创建类
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
classA:passa=A()print(type(A)) #
print(type(a)) #
二.什么是元类
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为
元类的实例化的结果为我们用class定义的类,正如类的实例为对象(a对象是A类的一个实例,A类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。(type---类----对象)
三 创建类的两种方式
1.使用class关键字
classPeople:
country='China'
defrun(self):print('run')'''People类包含元素有:类名,名称空间,父类(py3中默认为object)'''
2.手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建
准备工作:
创建类主要分为三部分
1 类名
2 类的父类
3 类体(名称空间)
defrun(self):print('run')
class_name='People' #类名
class_bases=(object,) #父类
class_body='''country='china'
def run(self):
print('run')'''
步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典
class_dict={}exec(class_body,globals(),class_dict)print(class_dict)'''{'country': 'china', 'run': }'''
步骤二:调用元类type(也可以自定义)来产生类Chinense
People=type(class_name,class_bases,class_dict)print(People)print(type(People))print(isinstance(People,type))'''
True
我们看到,type 接收三个参数:
第 1 个参数是字符串 ‘People,表示类名
第 2 个参数是元组 (object, ),表示所有的父类
第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若People类有继承,即class People(Bar):.... 则等同于type('People',(Bar,),{})
我们也可以直接定义命名空间字典,省去使用exec转化的一步。代码如下:
defrun(self):print('run')
class_name='People' #类名
class_bases=(object,) #父类
class_dict={'country': 'china', 'run':run}
People=type(class_name,class_bases,class_dict)print(People)print(type(People))print(isinstance(People,type))'''
True'''
4.一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
所以类实例化的流程都一样,与三个方法有关:(大前提,任何名字后加括号,都是在调用一个功能,触发一个函数的执行,得到一个返回值)1.p=People(),会调用产生People的类内的__call__方法,People()的结果即__call__的结果
2.调用__call__方法的过程中,先调用People.__new__,得到obj,即实例化的对象,但是还没有初始化
3.调用__call__方法的过程中,如果People.__new__()返回了obj,再调用People.__init__,将obj传入,进行初始化(否则不调用People.__init__)
总结:
__new__更像是其他语言中的构造函数,必须有返回值,返回值就实例化的对象
__init__只是初始化函数,必须没有返回值,仅仅只是初始化功能,并不能new创建对象
前提注意:
1. 在我们自定义的元类内,__new__方法在产生obj时用type.__new__(cls,*args,**kwargs),用object.__new__(cls)抛出异常:TypeError: object.__new__(Mymeta) is not safe, use type.__new__()
2. 在我们自定义的类内,__new__方法在产生obj时用object.__new__(self)
元类控制创建类:
classMymeta(type):def __init__(self):print('__init__')def __new__(cls, *args, **kwargs):print('__new__')def __call__(self, *args, **kwargs):print('__call__')class Foo(metaclass=Mymeta):pass
print(Foo)'''打印结果:
__new__
None'''
'''分析Foo的产生过程,即Foo=Mymeta(),会触发产生Mymeta的类内的__call__,即元类的__call__:
Mymeta加括号,会触发父类的__call__,即type.__call__
在type.__call__里会调用Foo.__new__
而Foo.__new__内只是打印操作,没有返回值,因而Mymeta的结果为None,即Foo=None'''
classMymeta(type):def __init__(self):print('__init__')def __new__(cls, *args, **kwargs):#print('__new__')
obj=type.__new__(cls,*args,**kwargs)returnobjdef __call__(self, *args, **kwargs):print('__call__')
Foo=Mymeta()class Foo(metaclass=Mymeta):pass
print(Foo)'''基于上述表达,我们改写了__new__,返回obj,但是抛出异常:
TypeError: __init__() takes 1 positional argument but 4 were given'''
#改写
classMymeta(type):def __init__(self,name,bases,dic):print('__init__')def __new__(cls, *args, **kwargs):print('__new__')
obj=type.__new__(cls,*args,**kwargs)returnobjdef __call__(self, *args, **kwargs):print('__call__')#Foo=Mymeta()
class Foo(metaclass=Mymeta):pass
print(Foo)
classMymeta(type):def __init__(self,name,bases,dic):for key,value indic.items():if key.startswith('__'):continue
if notcallable(value):continue
if not value.__doc__:raise TypeError('%s 必须有文档注释' %key)
type.__init__(self,name,bases,dic)def __new__(cls, *args, **kwargs):#print('__new__')
obj=type.__new__(cls,*args,**kwargs)returnobjdef __call__(self, *args, **kwargs):#print('__call__')
pass
#Foo=Mymeta()
class Foo(metaclass=Mymeta):deff1(self):'from f1'
pass
deff2(self):pass
'''抛出异常
TypeError: f2 必须有文档注释'''
所有类都必须有__doc__
#元类总结
classMymeta(type):def __init__(self,name,bases,dic):print('===>Mymeta.__init__')def __new__(cls, *args, **kwargs):print('===>Mymeta.__new__')return type.__new__(cls,*args,**kwargs)def __call__(self, *args, **kwargs):print('aaa')
obj=self.__new__(self)
self.__init__(self,*args,**kwargs)returnobjclass Foo(object,metaclass=Mymeta):def __init__(self,name):
self.name=namedef __new__(cls, *args, **kwargs):return object.__new__(cls)'''需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__
而爹.__call__一般做两件事:
1.调用name.__new__方法并返回一个对象
2.进而调用name.__init__方法对儿子name进行初始化'''
'''class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
Foo=Mymeta('foo',(...),{...})
因此我们可以看到,只定义class就会有如下执行效果
===>Mymeta.__new__
===>Mymeta.__init__
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化'''
'''obj=Foo('egon')
的原理同上'''
'''总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
1.谁后面跟括号,就从谁的爹中找__call__方法执行
type->Mymeta->Foo->obj
Mymeta()触发type.__call__
Foo()触发Mymeta.__call__
obj()触发Foo.__call__
2.__call__内按先后顺序依次调用儿子的__new__和__init__方法'''
元类总结
补充知识点:
__eq__ 和__hash__
class People(object):
def __init__(self,name,age,sex,height):
self.name=name
self,age=age
self.sex=sex
self.height=height
a=People('test1',23,'man',183)
b=People('test2',25,'woman',183)
c=People('test3',24,'man',183)
d=People('test4',23,'woman',183)
e=People('test2',40,'woman',183)
f=People('test1',29,'man',183)
l=[a,b,c,d,e,f]
如上代码所示,有个People类,并且有6个对象。当对象的name和sex相同时,我们认为他是同一个对象。如果我们要对列表l进去去重,将同样的对象去除时,可以运用__eq__和__hash__来简单的实现该功能。
改写如下:
classPeople(object):def __init__(self,name,age,sex,height):
self.name=name
self.age=age
self.sex=sex
self.height=heightdef __hash__(self):returnhash((self.name,self.sex))def __eq__(self, other):if self.sex==other.sex and self.name==other.name:return True
new_l=set(l)for i innew_l:printi.name,i.age
结果:
test324test225test123test423
从上面的示例,我们可以指定__eq__方法是用来判断来个值是否相等,__hash__ 设置了hash对象哪些值的内容。这两者设置完成后,可以直接使用集合来进行去重。如果仅仅是判断大小是否相等,可以只设置__eq__