简单了解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(mostrecentcalllast): File"xxxx.py",line10,inb=eval(a) File" ",line1 print(12+43);print(1111) ^ SyntaxError:invalidsyntax
赋值:
a=1 b=eval("a=21") print(b)
通常我们使用eval的时候,主要是使用它的返回值,获取表达式计算出的值
exec
执行复杂表达式,返回值永远都是None
b=exec("aa=21") print(b)#None,exec返回值为None print(aa)#21,exec执行了赋值语句,并定义了aa变量
执行复杂语句:
a='''ret=[] foriinrange(10): ret.append(i)''' exec(a) print(ret)#[0,1,2,3,4,5,6,7,8,9]
导入模块:
#导入模块 exec("importconfig") print(config.KEYWORD) #动态创建类 classBase: def__init__(self): print("Base") a="Base" exec(a+"()")
导入模块这个功能就非常厉害了,这样我们就可以动态的创建各种模块类。
eval()函数和exec()函数的区别:
eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
eval()函数可以有返回值,而exec()函数返回值永远为None。
再看一下下面的例子:
classBase: def__init__(self): print("Base") deftest(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
defhasattr(*args,**kwargs):#realsignatureunknown """ Returnwhethertheobjecthasanattributewiththegivenname. Thisisdonebycallinggetattr(obj,name)andcatchingAttributeError. """ pass
通过源码注释我们知道,它返回对象是否具有指定名称的属性。而且它是通过调用getattr并捕获AttributeError异常来判断的。就像上面的属性调用,我们就可以使用hasattr(a,"test")来判断,通过源码注释我们也可以思考一下,eval这种是不是也可以实现这种方法呢?
defhas_attr(obj,name): try: eval("obj.%s()"%name) returnTrue exceptAttributeErrorase: returnFalse a=Base() ifhas_attr(a,"test"): eval("a.test()") #输出: Base test test
但是这种方式是有缺陷的,因为test输出了两次,因为我们调用了两次test(),这跟我们想要的效果不一样。如果用hasattr呢,这个函数就不会在判断的时候调用一次了。
getattr()
有了判断属性是否存在的函数,那么就得有获取属性的函数了
defgetattr(object,name,default=None):#knownspecialcaseofgetattr """ getattr(object,name[,default])->value Getanamedattributefromanobject;getattr(x,'y')isequivalenttox.y. Whenadefaultargumentisgiven,itisreturnedwhentheattributedoesn't exist;withoutit,anexceptionisraisedinthatcase. """ pass
从源码注释我们就能知道获取object对象的名为name的属性,想到与object.name,如果提供了default参数,那么当属性不存在的时候,就会返回默认值。同样是上面的例子:
a=Base() ifhasattr(a,"test"): func=getattr(a,"test") func() #输出: Base test
从例子中我们可以看出,hasattr并没有调用test函数,而且getattr获取到的是函数对象,也没有调用它,通过我们主动执行func()才执行了a.test()函数,这样相比于exec和eval就灵活了许多。
setattr
判断和获取属性有了,那么设置属性也是需要的
defsetattr(x,y,v):#realsignatureunknown;restoredfrom__doc__ """ Setsthenamedattributeonthegivenobjecttothespecifiedvalue. setattr(x,'y',v)isequivalentto``x.y=v'' """ pass
将一个特殊值设置给object对象的name属性,相当于x.y=v
classBase: 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("importconfig"),针对这种方式python也为我们提供了更好的方法。
def__import__(name,globals=None,locals=None,fromlist=(),level=0):#realsignatureunknown;restoredfrom__doc__ """ __import__(name,globals=None,locals=None,fromlist=(),level=0)->module Importamodule.BecausethisfunctionismeantforusebythePython interpreterandnotforgeneraluse,itisbettertouse importlib.import_module()toprogrammaticallyimportamodule. Theglobalsargumentisonlyusedtodeterminethecontext; theyarenotmodified.Thelocalsargumentisunused.Thefromlist shouldbealistofnamestoemulate``fromnameimport...'',oran emptylisttoemulate``importname''. Whenimportingamodulefromapackage,notethat__import__('A.B',...) returnspackageAwhenfromlistisempty,butitssubmoduleBwhen fromlistisnotempty.Thelevelargumentisusedtodeterminewhetherto performabsoluteorrelativeimports:0isabsolute,whileapositivenumber isthenumberofparentdirectoriestosearchrelativetothecurrentmodule. """ pass
在这里我们最需要关注的是formlist参数,先看一个简单的例子:
a=__import__("config") print(a.KEYWORD)
config是一个py脚本-config.py,内部有一个变量KEYWORD,我们要通过其他py模块来导入这个文件,使用__import__我们就可以把它导入为一个对象,然后使用对象的方式去调用,而不是一直用exec字符串的形式去调用。上面我们说了formlist这个参数需要关注,为什么呢?我们新增了一个模块:comm。模块内有一个脚本function.py
#function.py defcomm_function(): print("test_module")
我们现在想通过动态引入的方式调用comm_function函数,那么按照上面的方式来
a=__import__("comm.function") a.comm_function()
结果输出:
Traceback(mostrecentcalllast): File"xxx.py",line10,inprint(a.comm_function()) AttributeError:module'comm'hasnoattribute'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__,接下来我们还会继续看框架设计中最重要的一个概念--元编程。学完了这些概念就可以设计框架了。开玩笑的,哪有那么简单。
阅读源码是一种增长知识的最快捷方式,但是前提是基础一定要打好。否则看源码是一头雾水。我们整理完这些概念后,在找几个源码库看看,学习一下里面的设计理念。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。