最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一文读懂python反射机制

    正文概述    2020-06-09   221

    一文读懂python反射机制

    反射

    反射机制就是在运行时,动态的确定对象的类型,并可以通过字符串调用对象属性、方法、导入模块,是一种基于字符串的事件驱动。

    解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。相对于编译型语言存在
    的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。
    比如Python/JavaScript / Perl /Shell等都是解释型语言。

    python是一门解释型语言,因此对于反射机制支持很好。在python中支持反射机制的函数有getattr()、setattr()、delattr()、exec()、eval()、__import__,这些函数都可以执行字符串。

    eval

    计算指定表达式的值。它只能执行单个表达式,而不能是复杂的代码逻辑。而且不能是赋值表达式。

    单个表达式

    a = "12 + 43"b = eval(a)
    print(b)

    复杂表达式

    a = "print(12 + 43); print(1111)"b = eval(a)
    print(b)# 输出:Traceback (most recent call last):
      File "xxxx.py", line 10, in <module>
        b = eval(a)
      File "<string>", line 1
        print(12 + 43); print(1111)
                      ^
    SyntaxError: invalid syntax

    赋值

    a = 1b = eval("a = 21")
    print(b)

    通常我们使用eval的时候,主要是使用它的返回值,获取表达式计算出的值

    exec

    执行复杂表达式,返回值永远都是None

    b = exec("aa = 21")
    print(b)      # None,exec返回值为Noneprint(aa)    # 21,exec执行了赋值语句,并定义了aa变量

    执行复杂语句

    a = '''ret = []
    for i in range(10):
        ret.append(i)'''exec(a)
    print(ret)   # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    导入模块

    # 导入模块exec("import config")
    print(config.KEYWORD)# 动态创建类class Base:
        def __init__(self):
            print("Base")
    
    a = "Base"exec(a+"()")

    导入模块这个功能就非常屌了,这样我们就可以动态的创建各种模块类。

    eval()函数和exec()函数的区别:
    eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
    eval()函数可以有返回值,而exec()函数返回值永远为None。

    看一下这个例子

    class Base:
        def __init__(self):
            print("Base")    def test(self):
            print("test")        return "Base::test"

    如果我们想通过字符串来调用a对象的test方法,应该怎么做呢,如果要获取返回值,那么可以使用

    b = eval("a.test()")
    print(b)

    输出:

    test
    Base::test

    如果不需要获取返回值,那么可以使用exec,exec("a.test()"),输出:test

    虽然我们可以使用eval和exec来执行以上代码,但是这种方式有一个缺陷,假如这个属性是不存在的,那么这种调用就会报错。那么做好的方式是什么呢?先判断属性是否存在,如果存在就调用,不存在就不调用,python为我们提供了一套方法:hasattr、getattr、setattr、delattr

    hasattr

    def hasattr(*args, **kwargs): # real signature unknown
        """
        Return whether the object has an attribute with the given name.
        
        This is done by calling getattr(obj, name) and catching AttributeError.
        """
        pass

    通过源码注释我们知道,它返回对象是否具有指定名称的属性。而且它是通过调用getattr并捕获AttributeError异常来判断的。就像上面的属性调用,我们就可以使用hasattr(a, "test")来判断,通过源码注释我们也可以思考一下,eval这种是不是也可以实现这种方法呢?

    def has_attr(obj, name):
        try:
            eval("obj.%s()" % name)        return True
        except AttributeError as e:        return Falsea = Base()if has_attr(a, "test"):
        eval("a.test()")# 输出:Base
    test
    test

    但是这种方式是有缺陷的,因为test输出了两次,因为我们调用了两次test(),这跟我们想要的效果不一样。如果用hasattr呢,这个函数就不会在判断的时候调用一次了。

    getattr()

    有了判断属性是否存在的函数,那么就得有获取属性的函数了

    def getattr(object, name, default=None): # known special case of getattr
        """
        getattr(object, name[, default]) -> value
        
        Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
        When a default argument is given, it is returned when the attribute doesn't
        exist; without it, an exception is raised in that case.
        """
        pass

    从源码注释我们就能知道获取object对象的名为name的属性,想到与object.name,如果提供了default参数,那么当属性不存在的时候,就会返回默认值。同样是上面的例子:

    a = Base()if hasattr(a, "test"):
        func = getattr(a, "test")
        func()# 输出:Base
    test

    从例子中我们可以看出,hasattr并没有调用test函数,而且getattr获取到的是函数对象,也没有调用它,通过我们主动执行func()才执行了a.test()函数,这样相比于exec和eval就灵活了许多。

    setattr

    判断和获取属性有了,那么设置属性也是需要的

    def setattr(x, y, v): # real signature unknown; restored from __doc__
        """
        Sets the named attribute on the given object to the specified value.
        
        setattr(x, 'y', v) is equivalent to ``x.y = v''
        """
        pass

    将一个特殊值设置给object对象的name属性,相当于x.y = v

    class Base:
        def __init__(self):
            self.name = "name"a = Base()
    setattr(a, "name", "zhangsan")
    print(a.name)                    # 改变原有属性的值setattr(a, "age", 32)
    print(getattr(a, "age"))      # 新增不存在的属性,并设置值

    虽然setattr(a, "age", 32)等于a.age=32,但是我们不要忘了,这是通过一个字符串来增加的属性。

    判断、获取、增加都有了,当然还有删除delattr,这个我们就不详述了,接下来我们要看一个比较重要的方法。

    import

    在学习exec的时候,我们有一个例子,导入配置文件exec("import config"),针对这种方式python也为我们提供了更好的方法。

    def __import__(name, globals=None, locals=None, fromlist=(), level=0): 
    # real signature unknown; restored from __doc__
        """
        __import__(name, globals=None, locals=None, fromlist=(), level=0) -> module
        
        Import a module. Because this function is meant for use by the Python
        interpreter and not for general use, it is better to use
        importlib.import_module() to programmatically import a module.
        
        The globals argument is only used to determine the context;
        they are not modified.  The locals argument is unused.  The fromlist
        should be a list of names to emulate ``from name import ...'', or an
        empty list to emulate ``import name''.
        When importing a module from a package, note that __import__('A.B', ...)
        returns package A when fromlist is empty, but its submodule B when
        fromlist is not empty.  The level argument is used to determine whether to
        perform absolute or relative imports: 0 is absolute, while a positive number
        is the number of parent directories to search relative to the current module.
        """
        pass

    在这里我们最需要关注的是formlist参数,先看一个简单的例子:

    a = __import__("config")
    print(a.KEYWORD)

    config是一个py脚本-config.py,内部有一个变量KEYWORD,我们要通过其他py模块来导入这个文件,使用__import__我们就可以把它导入为一个对象,然后使用对象的方式去调用,而不是一直用exec字符串的形式去调用。上面我们说了formlist这个参数需要关注,为什么呢?我们新增了一个模块:comm。模块内有一个脚本function.py

    # function.pydef comm_function():
        print("test_module")

    我们现在想通过动态引入的方式调用comm_function函数,那么按照上面的方式来

    a = __import__("comm.function")
    a.comm_function()

    结果输出:

    Traceback (most recent call last):
      File "xxx.py", line 10, in <module>
        print(a.comm_function())
    AttributeError: module 'comm' has no attribute 'comm_function'

    意思是comm模块没有comm_function这个属性,为什么是comm模块而不是function呢?我们可以打印一下模块的引入名称print(a.__name__),打印的结果是comm,就是说我们通过上面的方式只是引入comm,而不是function。其实通过源码注释我们就知道了,__import__(A.B),如果fromlist为空,返回的是A包,如果不为空,则返回其子包B。修改一下我们的代码:

    a = __import__("comm.function", fromlist=True)
    print(a.__name__)
    a.comm_function()# 输出:comm.function
    test_module

    引入的模块和执行函数都正确了,符合了我们的预期要求。

    总结

    通过以上的函数学习,其中有常用的,也有不常用的,但是这些函数在我们进行框架设计时是必不可少的,尤其是__import__,接下来我们还会继续看框架设计中最重要的一个概念--元编程。学完了这些概念就可以设计框架了。开玩笑的,哪有那么简单。阅读源码是一种增长知识的最快捷方式,但是前提是基础一定要打好。否则看源码是一头雾水。我们整理完这些概念后,在找几个源码库看看,学习一下里面的设计理念。


    起源地下载网 » 一文读懂python反射机制

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元