python反射简介

所谓反射是指通过字符串的方式获取对象,然后执行对象的属性或方法。在python中一切皆对象,因此我们可以对一切事物进行发射。

关于反射python为我们提供了四个方法:

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

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那样新增逻辑或者修改对应的逻辑,易于维护。到此反射就简单介绍到这里。

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄