反射与二次加工标准类型
反射与二次加工标准类型 |
一、反射
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。
有四个可以实现自省(反射)的函数,如下表所示:
函数名 | 含义 |
hasattr(object,name) | 判断object中有没有一个name字符串对应的方法或属性 |
getattr(object,name, default=None) | 检查obj.__dict__中有没有name这个键值,有则不做任何处理,没有则报错 |
setattr(object,name,value) | 等价于object.name=value |
delattr(object,name) | 等价于del object.name
|
各个函数具体代码实现:
class BlackMedium: feture = 'Ugly' def __init__(self,name,addr): self.name = name self.addr = addr def sell_hourse(self): print('%s正在卖房子' %self.name) def rent_hourse(self): print('%s正在租房子' %self.name) print(hasattr(BlackMedium,'feture')) #结果为:True b1 = BlackMedium('alex','厦门') print(hasattr(b1,'name')) #结果为:True print(hasattr(b1,'sell_hourse')) #结果为:True print(getattr(b1,'name')) #结果为:alex g = getattr(b1,'sell_hourse') g() #结果为:alex正在卖房子 print(getattr(b1,'sell_hoursesafgsgr','没有这个属性')) #结果为:没有这个属性 setattr(b1,'phone',18264894546) print(b1.__dict__) #结果为:{'name': 'alex', 'addr': '厦门', 'phone': 18264894546} setattr(b1,'func',lambda x:x+1) print(b1.__dict__) #结果为:{'name': 'alex', 'addr': '厦门', 'phone': 18264894546, 'func': <function <lambda> at 0x000000000041C1E0>} delattr(b1,'func') print(b1.__dict__) #结果为:{'name': 'alex', 'addr': '厦门', 'phone': 18264894546}
上述代码介绍了实现自省(反射)的函数。
反射的两个好处:
第一的好处:实现可插拔机制
假设程序员a与程序员b负责同一个项目,程序员a负责实现功能,而程序员b负责执行功能,那么程序员b在执行功能时发现程序员a的实现功能还未写入,这样就会出错,该如何解决这一问题?我们可以使用上面所学的反射来解决,具体代码如下:
#程序员a写的实现功能,写在ftp_client.py文件 class FtpClient: def __init__(self,addr): print('正在连接服务器%s' %addr) self.addr = addr # def put(self): #假设这方法还没写,被程序员b调用 # print('正在上传文件')
#程序员b写的执行功能,写在ftp_bin.py文件 from ftp_client import FtpClient f1 = FtpClient('1.1.1.1') #f1.put() ftp_client文件还没有写这功能会报错,可以利用反射方式判断,如下所示 if hasattr(f1,'put'): func_get = getattr(f1,'put') func_get() else: print('不存在此方法,处理其它的逻辑')
上述代码利用反射方式判断,相当于事先定义接口,接口只有在被完成后才会真正执行,这实现了即插即用。
第二个好处:动态导入模块(基于反射当前模块成员)
假如你需要导入m1.py模块,即:
import m1 __import__('m1.tttt') #这是解释器自己内部用的,它导入的就是m1模块 importlib.import_module('m1.tttt') #与上面这句效果一样
我们来了解类内置attr函数,分别是__setattr__、__getattr__、__delattr__。
__getattr__只有在使用点调用属性且属性不存在时候才会执行,具体如何实现请看下面代码:
class Foo: x = 1 def __init__(self,y): self.y = y def __getattr__(self, item): print('执行__getattr__') #调用一个对象不存在时候才会执行 f1 = Foo(10) print(f1.y) #结果为:10 print(getattr(f1,'y')) #结果为:10 f1.svdgsg #结果为:执行__getattr__
__delattr__删除属性的时候才会执行,该属性不存在也会执行,具体实现如下代码所示:
class Foo: x = 1 def __init__(self,y): self.y = y def __delattr__(self, item): print('删除操作__delattr__') self.__dict__.pop(item) #内置的 f1 = Foo(10) del f1.y #结果为:删除操作__delattr__ del f1.x #结果为:删除操作__delattr__,报错
__setattr__在类被实例化、以及添加或修改属性的才会执行,代码如下:
class Foo: x = 1 def __init__(self,y): self.y = y def __setattr__(self,key,value): print('执行__setattr__') self.__dict__[key] = value #内置的 f1 = Foo(10) #结果为:执行__setattr__ print(f1.__dict__) #结果为:{'y': 10} f1.z = 2 #结果为:执行__setattr__ print(f1.__dict__) #结果为:{'y': 10, 'z': 2}
二、二次加工标准类型
1.包装
Python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识。
假设利用list方法只能添加到字符串类型,具体代码如下:
class List(list): def append(self, item): if type(item) is str: super().append(item) else: print('只能添加字符串类型') l1 = List('hello') l1.append('alex') print(l1) #结果为:['h', 'e', 'l', 'l', 'o', 'alex'] l1.append(1646) #结果为:只能添加字符串类型 print(l1) #结果为:['h', 'e', 'l', 'l', 'o', 'alex']
上述代码在讲述包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建、修改或删除原有产品的功能,其它的则保持原样。
2.授权
授权是包装的一个特性,授权的过程即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
假设我们想要修改文件的写操作,具体代码如下:
import datetime class Open: def __init__(self,filename,mode='r',encoding='utf-8'): #self.filename = filename self.file = open(filename,mode,encoding=encoding) self.mode = mode self.encoding = encoding def write(self,line): #将写操作加上当前时间 t = datetime.datetime.now() self.file.write('%s %s' %(t,line)) def __getattr__(self,item): return getattr(self.file,item) f1 = Open('a.txt','w+') f1.write('1111111\n') #执行类的write方法 f1.write('ssssss\n') f1.write('hhhhh\n') f1.seek(0) print(f1.read()) #先在初始函数找,然后在类找,最后找到__getattr__方法,触发__getattr__
实现授权的关键点就是类中的__getattr__函数。