1. __getattr__、set__attr__、__delattr__
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
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
import time class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): self.file=open(filename,mode,encoding=encoding) def write(self,line): t=time.strftime('%Y-%m-%d %T') self.file.write('%s %s' %(t,line)) def __getattr__(self, item): return getattr(self.file,item) f1=FileHandle('b.txt','w+') f1.write('你好啊') f1.seek(0) print(f1.read()) f1.close()
2. __getattribute__
class Foo: def __init__(self,x): self.x=x def __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
3. __setitem__,__getitem,__delitem__
通过.的方式触发attr系列方法(__getattr__、set__attr__、__delattr__);通过字典的方式触发的是item系列的方法
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): print('getitem') print(self.__dict__[item]) def __setitem__(self, key, value): print('setitem') 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__、__format__
__str__ __repr__ 改变对象的字符串显示
__format__ 自定义格式化字符串
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。
format_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 format_spec if not format_spec or format_spec not in format_dict: format_spec = 'nat' fmt = format_dict[format_spec] return fmt.format(obj=self) s1 = School('oldboy', '北京', '私立') 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'))
date_dic={ 'ymd':'{0.year}:{0.month}:{0.day}', 'dmy':'{0.day}/{0.month}/{0.year}', 'mdy':'{0.month}-{0.day}-{0.year}', } class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day def __format__(self, format_spec): if not format_spec or format_spec not in date_dic: format_spec='ymd' fmt=date_dic[format_spec] return fmt.format(self) d1=Date(2016,12,29) print(format(d1)) print('{:mdy}'.format(d1))
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() f1.x=1 f1.y=2#报错 print(f1.__slots__) #f1不再有__dict__ class Bar: __slots__=['x','y'] n=Bar() n.x,n.y=1,2 n.z=3#报错
6. __doc__
描述信息
class Foo: """我是描述信息""" pass class Bar(Foo): pass print(Foo.__doc__) print(Bar.__doc__) #该属性无法继承给子类 我是描述信息 None
7. __module__、__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
8. __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
class Foo: def __del__(self): print('执行我啦') f1=Foo() # del f1 print('------->') #输出结果 -------> 执行我啦
9. __call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__
10. __next__、__iter__
实现迭代器协议
# class Foo(object): # def __init__(self,x): # self.x = x # f = Foo() # for i in f: # 报错,不可迭代 # print(i) class Foo(object): def __init__(self, x): self.x = x def __iter__(self): return self def __next__(self): self.x += 1 return self.x f = Foo(10) print(next(f)) print(next(f)) print(next(f))
11. __get__、__set__、__delete__
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
class Foo: def __get__(self, instance, owner): print('触发get') def __set__(self, instance, value): print('触发set') def __delete__(self, instance): print('触发delete') #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法 f1=Foo() f1.name='egon' f1.name del f1.name #疑问:何时,何地,会触发这三个方法的执行
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的使用 p1.name p1.name = 'egon' del p1.name # 描述符Int的使用 p1.age p1.age = 18 del p1.age # 我们来瞅瞅到底发生了什么 print(p1.__dict__) print(People.__dict__) # 补充 print(type(p1) == People) # type(obj)其实是查看obj是由哪个类实例化来的 print(type(p1).__dict__ == People.__dict__)
实现了__get__()、__set__()的是数据描述符,没有实现__set__()的是非数据描述符
注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
# 描述符 class Str(object): def __init__(self, key): self.key = key def __get__(self, instance, owner): # instance接收p1,owner接收p1的类 # print('get方法') # print('instance是%s' % instance) # print('owner是%s' % owner) return instance.__dict__[self.key] def __set__(self, instance, value): # instance接收p1,value接收修改的值 # print('set方法') # print('instance是%s' % instance) # print('value是%s' % value) if not isinstance(value, str): # 在描述符内对输入的值进行限制 raise TypeError('%s must be str' % self.key) instance.__dict__[self.key] = value def __delete__(self, instance): # print('delete方法') instance.__dict__.pop(self.key) class Int(object): def __init__(self, key): self.key = key def __get__(self, instance, owner): return instance.__dict__[self.key] def __set__(self, instance, value): if not isinstance(value, int): raise TypeError('%s must be int' % self.key) instance.__dict__[self.key] = value def __delete__(self, instance): instance.__dict__.pop(self.key) class Person(object): name = Str('name') # name被Str描述符代理 age = Int('age') # age被Int描述符代理 def __init__(self, name, age): self.name = name self.age = age p1 = Person('alex', 18) print(p1.__dict__) p2 = Person(123, 15) # 报错 TypeError: name must be str
12. __enter__、__exit__
我们知道在操作文件对象的时候可以这么写
1 with open('a.txt') as f:
2 '代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
class Open: def __init__(self, name): self.name = name def __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('=====>执行代码块') # print(f,f.name) print('=====>执行完毕') 运行结果: 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 =====>执行完毕
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
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__处理
13. __getslice__、__setslice__、__delslice__
该三个方法用于分片操作,如:列表
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自动触发执行 __getslice__ obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__ del obj[0:2] # 自动触发执行 __delslice__