简单了解python反射机制的一些知识


Posted in Python onJuly 13, 2019

反射

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

解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。相对于编译型语言存在的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。比如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 = 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 = []
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 False
a = 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.py
def 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 相关文章推荐
Python中几种操作字符串的方法的介绍
Apr 09 Python
Python的字典和列表的使用中一些需要注意的地方
Apr 24 Python
Python实现将多个空格换为一个空格.md的方法
Dec 20 Python
python字符串中匹配数字的正则表达式
Jul 03 Python
Python猴子补丁知识点总结
Jan 05 Python
python计算二维矩形IOU实例
Jan 18 Python
基于Python把网站域名解析成ip地址
May 25 Python
利用OpenCV中对图像数据进行64F和8U转换的方式
Jun 03 Python
Java如何基于wsimport调用wcf接口
Jun 17 Python
浅析PyCharm 的初始设置(知道)
Oct 12 Python
python 基于opencv 绘制图像轮廓
Dec 11 Python
Python中生成随机数据安全性、多功能性、用途和速度方面进行比较
Apr 14 Python
Python3内置模块之base64编解码方法详解
Jul 13 #Python
Python3enumrate和range对比及示例详解
Jul 13 #Python
基于Python的ModbusTCP客户端实现详解
Jul 13 #Python
Python Numpy库datetime类型的处理详解
Jul 13 #Python
Python3内置模块random随机方法小结
Jul 13 #Python
简单了解python的一些位运算技巧
Jul 13 #Python
简单了解python PEP的一些知识
Jul 13 #Python
You might like
让这部DC动画新作刷新你的认知
2020/03/03 欧美动漫
php中根据变量的类型 选择echo或dump
2012/07/05 PHP
Laravel框架分页实现方法分析
2018/06/12 PHP
alixixi runcode.asp的代码不错的应用
2007/08/08 Javascript
分享8款优秀的 jQuery 加载动画和进度条插件
2012/10/24 Javascript
JQuery DataTable删除行后的页面更新利用Ajax解决
2013/05/17 Javascript
js如何实现设计模式中的模板方法
2013/07/23 Javascript
javascript的几种继承方法介绍
2016/03/22 Javascript
JS前端开发判断是否是手机端并跳转操作(小结)
2017/02/05 Javascript
Bootstrap组合上、下拉框简单实现代码
2017/03/06 Javascript
小程序实现列表删除功能
2018/10/30 Javascript
理理Vue细节(推荐)
2019/04/16 Javascript
vue2 中二级路由高亮问题及配置方法
2019/06/10 Javascript
vue router总结 $router和$route及router与 router与route区别
2019/07/05 Javascript
js设计模式之代理模式及订阅发布模式实例详解
2019/08/15 Javascript
解决使用layui对select append元素无效或者未及时更新的问题
2019/09/18 Javascript
轻松理解Python 中的 descriptor
2017/09/15 Python
浅谈Python实现Apriori算法介绍
2017/12/20 Python
python实现生成字符串大小写字母和数字的各种组合
2019/01/01 Python
新建文件时Pycharm中自动设置头部模板信息的方法
2020/04/17 Python
python 自定义异常和主动抛出异常(raise)的操作
2020/12/11 Python
关于解决iframe标签嵌套问题的解决方法
2020/03/04 HTML / CSS
美国领先的个性化礼品商城:Personalization Mall
2019/07/27 全球购物
Lookfantastic澳大利亚官网:英国知名美妆购物网站
2021/01/07 全球购物
小学教师的个人自我鉴定
2013/10/24 职场文书
高中军训广播稿
2014/01/14 职场文书
九年级历史教学反思
2014/01/27 职场文书
违纪检讨书2000字
2014/02/08 职场文书
2015年高校图书馆工作总结
2015/04/30 职场文书
交通事故调解协议书
2015/05/20 职场文书
2015年学校信息技术工作总结
2015/05/25 职场文书
文明礼貌主题班会
2015/08/14 职场文书
ElementUI实现el-form表单重置功能按钮
2021/07/21 Javascript
Python装饰器的练习题
2021/11/23 Python
JavaScript中的宏任务和微任务详情
2021/11/27 Javascript
Android学习之BottomSheetDialog组件的使用
2022/06/21 Java/Android