自省 / 反射
什么是反射?
自省也称作反射, 这个性质展示了某对象是如何在运行期取得自身信息的.
并且在 python 里, 反射可以使得程序运行时对象拥有增删改查它本身属性或行为的一种能力
如果 Python 不支持某种形式的自省功能, dir 和 type 内建函数, 将很难正常工作. 还有那些特殊属性, 像__dict__,__name__及__doc__
反射的使用场景?
即插即用, 即可以事先定义好接口, 接口只有在被完成后才会真正执行
比如: 如果和别人共同合作开发项目, 但是需要用到对方的类的方法, 对方还没完成
- f1=FtpClient('192.168.1.1')
- if hasattr(f1,'get'):
- func_get=getattr(f1,'get')
- func_get()
- else:
- print('---->不存在此方法')
- print('处理其他的逻辑')
四个可以实现自省的函数
python 通过字符串的形式操作对象相关的属性. python 中的一切事物都是对象(都可以使用反射)
- getattr(x, 'y') #x.y
- setattr(x, 'y', v) #x.y = v 可以设置属性 setattr(b1,'show_name',lambda self:self.name+'sb')
- delattr(x, 'y') #del x.y
- hasattr(b1,'name')
魔法方法
__setattr__,__delattr__,__getattr__
必知
1)覆写以上函数非常容易出现无限递归, 因为无论是操纵全局自省函数, 还是对象. 属性都是去调用对应的魔法方法. 所以只能操纵魔法字典这个属性
- def __getattr__(self, item):
- return self.__dict__[item]
2)覆写以上函数的格式和对应的全局自省函数参数相同, 因为全局自省函数就是调用对应的它们
__getattribute__
__getattr__对比__getattribute__有什么区别?
1)当使用 对象. 属性 找不到的时候, 会调用 getattr, 返回一个值或 AttributeError 异常, 但是若属性存在, 则不调用.
但是__getattribute__无论属性存在与否都会调用
2)当__getattribute__与__getattr__同时存在, 只会执行__getattrbute__, 除非__getattribute__在执行过程中抛出异常 AttributeError
描述符协议
__get__,__set__,__delete__ # 描述符
什么是描述符?
描述符的功能就是描述其他类的类属性
描述符本质就是一个新式类, 在这个新式类中, 至少实现了__get__(),__set__(),__delete__()中的一个, 这也被称为描述符协议
__get__(): 调用一个属性时, 触发
__set__(): 为一个属性赋值时, 触发
__delete__(): 采用 del 删除属性时, 触发
描述符的原理是将描述符定义为指定类的类属性, 产生指定操作时, 便去属性字典里面找, 如果是描述符对象, 便执行
- class Int:
- def __get__(self, instance, owner): #owner 是 instance 的 class
- print('Int 调用')
- def __set__(self, instance, value):
- print('Int 设置...')
- def __delete__(self, instance):
- print('Int 删除...')
- class People:
- age=Int()
- def __init__(self,name,age):
- self.age=age #执行 Int.__get__
描述符的分类
描述符分两种, 数据描述符 (至少实现类__get__() 和__set__()), 非数据描述符(没有实现__set__()),
考虑要定义哪一种, 主要是考虑需不需要给实例定义自己相同的属性(该属性名与类描述符名相同)
并且数据描述符的优先级只有类属性可以覆盖.
4.__set__设置类. 描述符时, 新设的类属性值会覆盖描述符, 但是通过实例. 描述符却不会覆盖. 在类的__dict__可以观察到.
5. 描述符只能通过 类. 描述符名 = XX 改变, 实例是无法改变, 并且实例可以设置描述符同名的属性(所以数据描述符, 和非数据描述符就是有区别, 区别就是有__set__方法, 就应该去执行, 没有的话就直接绑定了)
描述符的用处
干预被描述的属性的操作
当操纵指定的类属性时, 会跳到相应的描述符函数并执行返回. 我们可以在描述符函数通过操作指定对象的__dict__模仿正常的属性操作. 所以说这中间多了个干预工程, 常用于制作类构造函数的类型限制.
给类加上类属性的三种方法:(注意, 函数也属于类属性)
1)直接在类中声明, 例如 a=Des()
2)定义一个函数'装饰器', 装饰于类中. 在函数里面为类添加属性
3 ) 定义一个类'装饰器', 然后装饰于另外一个类的方法里,
然后被装饰器的函数名就指向这个类'装饰器'产生的对象(obj=cls(func)), 若此时类装饰器又实现了__get__方法,
换句话说, 函数是类属性, 类属性指向描述符对象. 专门应用于函数.
扩展: 类装饰器
类装饰器, 原理和函数装饰器一样, 前者将函数当作对象传入, 后者将类当作对象传入, 通过类比可知.
当构造函数的参数过多时, 可以额外写一个函数作装饰器(参数为 属性名 = 期望类型.. 的可变参数 **kwargs, 函数里面, 为类加入描述符属性(类. 属性名 = 描述符)
例如: 自制 proprety 就是类装饰器配合描述符的好例子
- class Pro():
- def __init__(self,ab):
- self.ab=ab
- def __get__(self,instance,owner):
- return self.ab(instance)
- class Apple():
- def __init__(self,a,b):
- self.a=a
- self.b=b
- @Pro
- def ab(self):
- return self.a*self.b
- def __str__(self):
- return ("是我 a=%s,b=%s"%(self.a,self.b))
- if __name__=='__main__':
- aa=Apple(2,3)
- print(aa.ab)
- __setitem__,__getitem,__delitem__ #等于 c++ 的运算符 [] 重载
- __str__,__repr__,__format__
1)改变对象的字符串显示__str__,__repr__, 自定制格式化字符串__format__
2)在类中__str__和__repr__没什么区别. 如果__str__没有被定义, 那么就会使用__repr__来代替输出
注意: 这俩方法的返回值必须是字符串, 否则抛出异常
__slots__ #内存优化工具, 顺便可以限制属性
用法:
可以选择性定义的一个类属性, 类型是列表, 列表元素是定义的实例属性名(字符串)
作用:
1)__slots__机制借由数组存储属性来节约内存. 因为类的字典是共享的, 而每个实例的是独立的, 字典占用内存很大,
2)在类中定义__slots__属性列表后, 实例通过一个很小的固定大小的数组来构建, 在__slots__中列出的属 性名在内部被映射到这个数组的指定小标上. 而不是为每个实例定义一个字典, 节约内存.
3)使用__slots__的代价就是不能再给实例添加新的属性, 只能使用在__slots__中定义的那些属性名
__next__和__iter__ #实现迭代器协议
- __doc__ #类中最开头处的字符串
- class Foo:
- '我是描述信息'
- pass
- print(Foo.__doc__)
- __module__ #表示当前操作的对象在那个模块
- __class__ #表示当前操作的对象的类是什么
- __del__ #析构方法, 当对象在内存中被释放时, 自动触发执行.
1)创建数据库类, 用该类实例化出数据库链接对象, 对象本身是存放于用户空间内存中, 而链接则是由操作系统管理的, 存放于内核空间内存中
当程序结束时, python 只会回收自己的内存空间, 即用户态内存, 而操作系统的资源则没有被回收, 这就需要我们定制__del__, 在对象被删除前向操作系统发起关闭数据库链接的系统调用, 回收资源
- 2)f=open('a.txt') #做了两件事, 在用户空间拿到一个 f 变量, 在操作系统内核空间打开一个文件
- del f #只回收用户空间的 f, 操作系统的文件还处于打开状态
- # 所以我们应该在 del f 之前保证 f.close()执行, 即便是没有 del, 程序执行完毕也会自动 del 清理资源.
- __call__ #变为函数对象
__enter__和__exit__ #上下文管理器
注意的是:__exit__的函数参数有点多. 还有一般这上下文管理器中的异常会有点多.
上下文中间的代码发生异常时, 若__exit__()返回值为 True, 那么异常就会被吞掉, 就好像啥都没发生一样,
例如:
- 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)
- print(exc_val)
- print(exc_tb)
- #return True
- with Open('a.txt') as f:
- print('=====>执行代码块')
- raise AttributeError('*** 着火啦, 救火啊 ***')
- print('0'*100) #------------------------------->不会执行
元类
type()函数产生类
Foo=type(class_name,class_bases,class_dic)
第 1 个参数是字符串'Foo', 表示类名
第 2 个参数是元组 (object, ), 表示所有的父类
第 3 个参数是字典, 这里是一个空字典, 表示没有定义属性和方法
使用自己定义的元类(class A(metaclass=MyType))
当定义自己的元类时, 首先要继承 type(注意一下, 元类中 self 意味着实例化的 cls)
1)如果想干涉'元类 -->类', 就改写__new__.(def__new__(cls,class_name,class_bases,class_dict))
因为类创建对象的流程是__new__创建对象后将对象地址传给__init__,__init__加工上自己所需的属性,
换句话说, 前者负责创建返回, 后者负责加工.
2)如果想干涉'对象的创建', 就改写元类的__call__, 其实元类的__call__才是大佬,
由它决定要不要调用类的__new__函数和__init__函数. 默认会调
来源: http://www.bubuko.com/infodetail-2620588.html