• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Python反射机制实例讲解

    通常,我们操作对象的属性或者方法时,是通过点“.”操作符进行的。例如下面的代码:

    class Person:
        type = "mammal"
    
        def __init__(self, name):
            self.name = name
    
        def say_hi(self):
            print('Hello, my name is', self.name)
    
        @staticmethod
        def feed():
            print("Three times per day.")
    
        @classmethod
        def sleep(cls):
            print("8 hours!")
    
    
    p = Person('Chunming')
    p.say_hi()
    print(p.name)
    

    上面代码的输出是

    Hello, my name is Nikhil
    Nikhil

    反射是另外一种操作对象属性和方法的手段,例如:

    func = getattr(p, 'say_hi') 
    func()
    print(getattr(p, "name"))
    

    上面这段代码的输出是:

    Hello, my name is Nikhil
    Nikhil

    可见与通过点操作符的结果一致。

    1. 反射的四个函数

    getattr是获取对象属性或方法的函数,Python的官方文档是这样描述其用法的:

    getattr(object, name, value)

    返回对象命名属性的值。name必须是字符串。如果该字符串是对象的属性之一,则返回该属性的值。例如, getattr(x, ‘foobar')等同于 x.foobar。如果指定的属性不存在,且提供了 default值,则返回它,否则触发 AttributeError。

    根据文档理解上述代码,getattr(p, ‘say_hi') 获取了p对象的say_hi属性值并赋值给func变量,因为say_hi属性在Person类中是一个方法,要想调用这个方法, 需要执行func(),getattr(p, “name”) 则是获取p对象的name属性。

    除了获取对象属性和方法的getattr函数,python还内置了判断、设置、删除对象属性和方法的函数,来看看Python官方文档对这三个函数的说明:

    setattr(object, name, value)

    此函数与 getattr() 两相对应。其参数为一个对象、一个字符串和一个任意值。字符串指定一个现有属性或者新增属性。函数会将值赋给该属性,只要对象允许这种操作。例如,setattr(x, ‘foobar', 123) 等价于 x.foobar = 123。

    hasattr(object, name)

    该实参是一个对象和一个字符串。如果字符串是对象的属性之一的名称,则返回 True,否则返回 False。(此功能是通过调用 getattr(object, name) 看是否有 AttributeError 异常来实现的。)

    delattr(object, name)

    setattr() 相关的函数。实参是一个对象和一个字符串。该字符串必须是对象的某个属性。如果对象允许,该函数将删除指定的属性。例如 delattr(x, ‘foobar') 等价于 del x.foobar 。

    Python中通过getattr、setattr、hasattr和delattr四个函数操作属性的机制就是反射。是通过字符串的形式操作对象属性和方法的机制。下面对p属性应用setattr、hasattr和delattr这三个函数看看效果:

    判断p对象是否有say_bye属性和say_hi属性:

    print(hasattr(p, 'say_bye'))  # 输出False
    print(hasattr(p, 'say_hi'))  # 输出True
    

    给p对象增加say_bye的方法和age属性:

    setattr(p, 'say_bye', say_bye)
    setattr(p, 'age', 18)
    

    现在可以访问这两个属性了,通过反射访问:

    bye = getattr(p, 'say_bye')
    bye()
    print(getattr(p, 'age'))
    

    或者通过点操作符访问:

    p.say_bye()
    print(p.age)
    

    删除age属性:

    delattr(p, 'age')
    print(hasattr(p, 'age'))  # 输出False
    

    2. 类的反射操作

    除了对象的反射操作,还有类的反射操作,当前模块的反射操作,还有其他模块的反射操作,其他包的反射操作。

    类的反射操作,指的是对类属性、类方法或者静态方法执行反射操作。

    获取类属性:

    t = getattr(Person, 'type')
    print(t)  # 输出mammal
    f = getattr(Person, 'feed')
    f()  # 输出Three times per day.
    s = getattr(Person, 'sleep')
    s() # 8 hours!
    

    判断类属性是否存在:

    print(hasattr(Person, 'type'))  # 输出True
    print(hasattr(Person, 'name'))  # 输出False
    print(hasattr(Person, 'say_hi')) # 输出True
    print(hasattr(Person, 'sleep'))  # 输出True
    print(hasattr(Person, 'feed'))  # 输出True
    

    此外,还可以对类添加和删除属性和方法。

    print(delattr(Person, 'feed'))
    print(hasattr(Person, 'feed'))
    setattr(Person, 'feed', lambda x: print("Three times per day."))
    print(hasattr(Person, 'feed'))
    

    3. 当前模块的反射操作

    当前模块也就是当前代码所在的py文件,反射也可以对当前模块中的变量和函数进行操作。例如某个模块包含一个al函数,用来判断迭代对象中每个元素是否都是True,内容如下:

    import sys
    
    def al(iterable):
        for element in iterable:
            if not element:
                return False
        return True
    
    
    this_module = sys.modules[__name__]
    
    if hasattr(this_module, 'al'):
        all_true = getattr(this_module, 'al')
        result = all_true([1, 2, 3, 4, True, 0])
        print(result)
    

    通过sys.modules[name]方法获取当前模块的名称。getattr第一个参数是模块名称,第二个参数是想要从模块中获取的属性。

    4. 其他模块反射操作

    对import进来的其他模块中的函数、属性、变量进行反射操作。例如,我们导入Python的heapq模块,这块模块提供了堆队列算法的实现,也称为优先队列算法。下面的代码是一个实现堆排序的函数。

    import heapq
    
    h = [(5, 'write code'), (7, 'release product'), (1, 'write spec'), (3, 'create tests')]
    
    if hasattr(heapq, 'heapify'):
       heapi = getattr(heapq, 'heapify')  # 获取heapify属性
       heapi(h)  # 建堆
       if hasattr(heapq, 'heappop'):
           heapp = getattr(heapq, 'heappop')  # 获取heappop属性
           print([heapp(h) for _ in range(len(h))])  # 弹出并从返回堆中最小的项
    

    这里,我们并没有通过heapq.heapify和heapq.heappop方式调用heapq模块中的函数。而是通过反射达到的同样的效果。

    5. 反射应用场景之一

    了解了反射中四个函数的基本用法。那么反射到底有什么用呢?它的应用场景是什么呢?答案是,当不确定所需要的属性和函数是否存在时,可以使用反射。另外一个重要作用是,可以提高代码的扩展性和可维护性。

    假如我们把所有的加密算法都放到一个叫做encryption的模块中维护 ,并且允许使用这个模块的用户添加更多的加密算法到这个模块中。encryption的模块内容如下:

    import hashlib
    import os
    import sys
    
    
    def md5(content=None):
        """生成字符串的SHA256值"""
        if content is None:
            return ''
        md5_gen = hashlib.md5()
        md5_gen.update(content.encode('utf-8'))
        md5code = md5_gen.hexdigest()
        return md5code
    
    
    def sha256(content=None):
        """生成字符串的SHA256值"""
        if content is None:
            return ''
        sha256_gen = hashlib.sha256()
        sha256_gen.update(content.encode('utf-8'))
        sha256code = sha256_gen.hexdigest()
        return sha256code
    
    
    def sha256_file(filename):
        """生成文件的SHA256值"""
        if not os.path.isfile(filename):
            return ""
        sha256gen = hashlib.sha256()
        size = os.path.getsize(filename)  # 获取文件大小,单位是Byte
        with open(filename, 'rb') as fd:  # 以二进制方式读取文件
            while size >= 1024 * 1024:  # 当文件大于1MB时分块读取文件内容
                sha256gen.update(fd.read(1024 * 1024))
                size -= 1024 * 1024
            sha256gen.update(fd.read())
        sha256code = sha256gen.hexdigest()
        return sha256code
    
    
    def md5_file(filename):
        """生成文件的MD5值"""
        if not os.path.isfile(filename):
            return ""
        md5gen = hashlib.md5()
        size = os.path.getsize(filename)  # 获取文件大小,单位是Byte
        with open(filename, 'rb') as fd:
            while size >= 1024 * 1024:  # 当文件大于1MB时分块读取文件内容
                md5gen.update(fd.read(1024 * 1024))
                size -= 1024 * 1024
            md5gen.update(fd.read())
        md5code = md5gen.hexdigest()
        return md5code
    
    
    def encrypt_something(something, algorithm):
        """
        通用加密算法
        :param something: 待加密的内容,字符串或者文件
        :param algorithm: 加密算法
        :return:  加密后的内容
        """
        result = ""
        if algorithm == "md5":
            result = md5(something)
        elif algorithm == "sh256":
            result = sha256(something)
        elif algorithm == "sh256_file":
            result = sha256_file(something)
        elif algorithm == "md5_file":
            result = md5_file(something)
        return result
    

    其中,encrypt_something函数提供了通用加密算法,需要调用者传入待加密的内容和加密算法,这样当调用者使用encryption.py模块时,只需导入encrypt_something函数即可。就像这样:

    import encryption
    print(encryption.encrypt_something("learn_python_by_coding", "sh256"))
    print(encryption.encrypt_something("learn_python_by_coding", "md5"))
    

    通过分析encrypt_something函数发现,当我们在encryption.py模块添加更多的加密算法后,就要修改encrypt_something函数,在其中增加新的if分支,随着加密算法的增加,encrypt_something函数的分支会越来越多。

    学了反射之后,encrypt_something代码部分就可以这样写:

    def encrypt_something(something, algorithm):
        """
        通用加密算法
        :param something: 待加密的内容,字符串或者文件
        :param algorithm: 加密算法
        :return:  加密后的内容
        """
        this_module = sys.modules[__name__]
        if hasattr(this_module, algorithm):
            algorithm = getattr(this_module, algorithm)
            result = algorithm(something)
        else:
            raise ValueError("Not support {} algorithm".format(algorithm))
        return result
    

    相比前面的采用if分支语句方式,反射更加简洁明了,可维护性更强,要想增加新的加密方法,只需要在encryption.py模块添加更多的加密算法即可,encrypt_something代码不需要任何变更。

    6. 反射应用场景之二

    再看一个基于Pytest测试框架的测试脚本中应用反射的例子,比如conftest文件内容如下:

    # content of conftest.py
    import pytest
    import smtplib
    
    
    @pytest.fixture(scope="module")
    def smtp_connection(request):
        server = getattr(request.module, "smtpserver", "smtp.gmail.com")
        smtp_connection = smtplib.SMTP(server, 587, timeout=5)
        yield smtp_connection
        print("finalizing {} ({})".format(smtp_connection, server))
        smtp_connection.close()
    

    request.module 是所有测试脚本,就是那些以_test结尾或者test_开头的py文件。

    server = getattr(request.module, "smtpserver", "smtp.gmail.com") 
    

    含义就是从测试脚本文件中找smtpserver属性,如果找不到,默认使用smtp.gmail.com作为smtpserver的值。如果测试脚本文件有这个属性,则使用测试脚本中的值,例如下面这个测试脚本,smtpserver则会使用mail.python.org这个值:

    # content of test_anothersmtp.py
    
    smtpserver = "mail.python.org"  # will be read by smtp fixture
    
    
    def test_showhelo(smtp_connection):
        assert 0, smtp_connection.helo()
    

    7. 总结

    在很多开源框架中普遍采用,是提高可维护性和扩展性的利器。如果工作中也涉及到框架开发,反射一定会助一臂之力,,希望大家以后多多支持脚本之家!

    您可能感兴趣的文章:
    • python对验证码降噪的实现示例代码
    • 爬虫Python验证码识别入门
    • Python机器学习入门(一)序章
    • 6个Python办公黑科技,助你提升工作效率
    • Python机器学习入门(三)之Python数据准备
    • 用python写个颜值评分器筛选最美主播
    • Python代码实现粒子群算法图文详解
    • 我用Python做个AI出牌器斗地主把把赢
    • python通过PyQt5实现登录界面的示例代码
    • Python图片验证码降噪和8邻域降噪
    上一篇:Python代码实现粒子群算法图文详解
    下一篇:用python写个颜值评分器筛选最美主播
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    Python反射机制实例讲解 Python,反射,机制,实例,讲解,