bizercsdn 2020-05-10
所谓反射是指通过字符串的方式获取对象,然后执行对象的属性或方法。在python中一切皆对象,因此我们可以对一切事物进行发射。
关于反射python为我们提供了四个方法:
hasattr(object, name):name必须是字符串,如果字符串name是object对象当中的某一属性或某一方法将返回True,否则返回False。
getattr(object, name ,default=None):name必须是字符串,如果字符串name是object对象当中的某一属性或某一方法将返回对应的属性或方法,否则如果给定默认值将返回默认值,如果没指定默认值将引发AttributeError的异常。
setattr(object, name, value):可以新增或修改对象的属性,name必须是字符串,value可以是任意数据类型。如果name是object的属性将修改对应属性的值为value,如果对象不是object的属性将新增一个属性。
delattr(object, name):删除对象属性,name必须是字符串,name必须是object的属性且object对这个属性有删除的权限,否则将引发AttributeError。
下面以对对象进行反射,对类进行反射,对当前模块进行反射,对其它模块进行反射为示例来了解反射的应用。
class A: num = 10 def fun(self): print(‘Hello World‘) # 判断A中是否有fun属性或方法 if hasattr(A,‘fun‘): # 获取fun方法的内存地址 f = getattr(A,‘fun‘) # 执行A中fun的方法 f(‘‘) # 打印结果如下 Hello World # 给A中新增属性buf setattr(A,‘buf‘,[1,2,3]) print(A.buf) # 打印内容如下 [1, 2, 3] # 删除类中属性num delattr(A,‘num‘) print(A.num) # 打印结果如下 AttributeError: type object ‘A‘ has no attribute ‘num‘
class A: num = 10 def fun(self): print(‘Hello World‘) obj = A() # 判断对象obj中是否有fun方法 if hasattr(obj,‘fun‘): # 获取对象中fun方法的内存地址 f = getattr(obj,‘fun‘) # 执行obj中fun方法 f() # 打印结果如下 Hello World # 给对象obj新增属性buf setattr(obj,‘buf‘,[1,2,3]) print(obj.buf) # 打印结果如下 [1, 2, 3] # 删除obj中属性buf delattr(obj,‘buf‘) # 删除类A的属性num delattr(obj,‘num‘) # 引发如下异常 delattr(obj,‘num‘) AttributeError: num 原因:obj是类A的实例化对象,它有自己的内存空间,与类A空间互不干涉,obj仅可以访问类A的属性和方法,是不能对类A中原有的属性和方法进行修改的。
import sys class A: num = 10 def fun(self): print(‘Hello World‘) def f(): print(‘对当前模块进行反射‘) # 获取当前模块 current_module = sys.modules[__name__] # 获取当前模块下的类A if hasattr(current_module,‘A‘): obj_A = getattr(current_module,‘A‘) obj_A.fun(‘‘) # 获取当前模块下的函数f if hasattr(current_module,‘f‘): obj_f = getattr(current_module,‘f‘) obj_f() # 打印内容如下 Hello World 对当前模块进行反射
# 1.py对test.py进行反射 ============================ # test.py中代码如下 class A: num = 10 def fun(self): print(‘Hello World‘) def f(): print(‘对当前模块进行反射‘) ============================ # 1.py中代码如下 # 第一种方式 import test # 获取test.py模块下的类A if hasattr(test,‘A‘): obj_A = getattr(test,‘A‘) obj_A.fun(‘‘) # 获取test.py模块下的函数f if hasattr(test,‘f‘): obj_f = getattr(test,‘f‘) obj_f() # 打印内容如下 Hello World 对当前模块进行反射 # 第二种方式 imp_module = __import__(‘test‘) # 获取test.py模块下的类A if hasattr(imp_module,‘A‘): obj_A = getattr(imp_module,‘A‘) obj_A.fun(‘‘) # 获取test.py模块下的函数f if hasattr(imp_module,‘f‘): obj_f = getattr(imp_module,‘f‘) obj_f() # 打印内容如下 Hello World 对当前模块进行反射
关于getattr(object, name ,default=None)中的参数default需要注意,直接按位置参数传参即可,不要是要关键字参数进行传参,否则会报错。
class A: num = 10 def fun(self): print(‘Hello World‘) # getattr正确的默认参数 obj = getattr(A,‘max‘,‘没找到‘) print(obj) # 打印内容如下 没找到 # getattr错误的默认参数 obj = getattr(A,‘max‘,default=‘没找到‘) # 打印内容如下 TypeError: getattr() takes no keyword arguments
看了上面的代码片段,也许你会感觉反射也不过如此,但是在有些场景反射真的很有用,下面以FTP server和FTP client为例,我之前写的小练习。
有兴趣的朋友可以看一下:https://www.cnblogs.com/caesar-id/p/12105321.html里面服务端对客户端发来的命令进行解析时就是用的反射,如果不用反射,就需要使用大量的if来解析客户端要执行哪条命令。
下面在不使用反射的情况下解析客户端发来的命令(伪代码):
class DataAnalysis(FileOperation): """ 数据分析处理类,主要负责解析client发送过来的指令。 """ def syntax_analysis(self,recv_data, socket_obj, commom): """ 负责解析客户端传来的数据。 :param recv_data:客户端执行的命令 :param socket_obj:socket对象 :param commom:数据对象 :return: """ # 用户要执行的命令可能是help 也可能是get 文件 客户端路径 还可能是cd 目录 # 所以首先要明确用户到底要执行什么用的命令,通过切割后获取第一个元素 # 我们通过第一个元素来判断用户到底要做什么,下面是判断用户到底用做什么 clientData = recv_data.split(" ") if clientData[0] == ‘help‘: pass elif clientData[0] == ‘get‘: pass elif clientData[0] == ‘cd‘: pass elif clientData[0] == ‘put‘: pass ...
上面的情况很明显的就是程序有多少功能,就需要有多少个if来对其进行判断,当某个方法发生改变或新增功能时,需要到if中添加相应的逻辑判断或者修改对应的逻辑,无论是从代码的阅读还是后期的维护都不是很方便。
下面使用反射的情况下解析客户端发来的命令:
class DataAnalysis(FileOperation): """ 数据分析处理类,主要负责解析client发送过来的指令。 """ def syntax_analysis(self,recv_data, socket_obj, commom): """ 负责解析客户端传来的数据。 :param recv_data:客户端执行的命令 :param socket_obj:socket对象 :param commom:数据对象 :return: """ # 用户要执行的命令可能是help 也可能是get 文件 客户端路径 还可能是cd 目录 # 所以首先要明确用户到底要执行什么用的命令,通过切割后获取第一个元素 # 我们通过第一个元素来判断用户到底要做什么,下面是判断用户到底用做什么 clientData = recv_data.split(" ") if hasattr(self, clientData[0]): # 判断用户执行的命令是否存在 get_fun = getattr(self, clientData[0]) # 获取命令的执行方法 get_fun(clientData, socket_obj, commom) # 执行对应的命令 else: pass
使用反射对客户端命令解析,无论程序有多少方法,都只需上面那几行代码即可。相比通过if对命令解析反射结构更加清晰,当某个方法发生改变,或添加新的功能,只需要把所有精力都用在功能实现上即可,不需要像if那样新增逻辑或者修改对应的逻辑,易于维护。到此反射就简单介绍到这里。