简单了解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下载文件记录黑名单的实现代码
Oct 24 Python
django settings.py 配置文件及介绍
Jul 15 Python
python多线程+代理池爬取天天基金网、股票数据过程解析
Aug 13 Python
python 并发编程 非阻塞IO模型原理解析
Aug 20 Python
学Python 3的理由和必要性
Nov 19 Python
Python的对象传递与Copy函数使用详解
Dec 26 Python
解决Pytorch 加载训练好的模型 遇到的error问题
Jan 10 Python
python自动下载图片的方法示例
Mar 25 Python
设置jupyter中DataFrame的显示限制方式
Apr 12 Python
python实现IOU计算案例
Apr 12 Python
Django表单提交后实现获取相同name的不同value值
May 14 Python
OpenCV-Python 实现两张图片自动拼接成全景图
Jun 11 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
PHP 日期时间函数的高级应用技巧
2009/10/10 PHP
php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证
2016/05/04 PHP
Laravel Intervention/image图片处理扩展包的安装、使用与可能遇到的坑详解
2017/11/14 PHP
js获取单选按钮的数据
2006/11/27 Javascript
jquery 得到当前页面高度和宽度的两个函数
2010/02/21 Javascript
jQuery实现类似滑动门切换效果的层切换
2013/09/23 Javascript
js与css实现弹出层覆盖整个页面的方法
2014/12/13 Javascript
JavaScript检测浏览器cookie是否已经启动的方法
2015/02/27 Javascript
AngularJS Module方法详解
2015/12/08 Javascript
封装好的javascript前端分页插件pagination
2016/01/04 Javascript
nodejs简单实现操作arduino
2016/09/25 NodeJs
详解Javascript百度地图接口开发文档中的类和方法
2017/02/07 Javascript
QRCode.js:基于JQuery的生成二维码JS库的使用
2017/06/23 jQuery
axios发送post请求,提交图片类型表单数据方法
2018/03/16 Javascript
JS+HTML5实现获取手机验证码倒计时按钮
2018/08/08 Javascript
详解vue-cli3多环境打包配置
2019/03/28 Javascript
优雅的处理vue项目异常实战记录
2019/06/05 Javascript
基于JavaScript实现简单抽奖功能代码实例
2020/10/20 Javascript
Python中字典和JSON互转操作实例
2015/01/19 Python
flask中的wtforms使用方法
2018/07/21 Python
flask框架中勾子函数的使用详解
2018/08/01 Python
python用for循环求和的方法总结
2019/07/08 Python
更新升级python和pip版本后不生效的问题解决
2020/04/17 Python
俄罗斯香水和化妆品在线商店:Aroma-butik
2020/02/28 全球购物
学生自我鉴定范文
2013/10/04 职场文书
《世界多美呀》教学反思
2014/03/02 职场文书
产品质量承诺范本
2014/03/31 职场文书
日语专业毕业生自荐书
2014/06/18 职场文书
限期整改通知书
2015/04/22 职场文书
运动会通讯稿600字
2015/07/20 职场文书
生产实习心得体会范文
2016/01/22 职场文书
2016年社区创先争优活动总结
2016/04/05 职场文书
python opencv常用图形绘制方法(线段、矩形、圆形、椭圆、文本)
2021/04/12 Python
python 经纬度求两点距离、三点面积操作
2021/06/03 Python
德劲DE1102数字调谐收音机机评
2022/04/07 无线电
安装harbor作为docker镜像仓库的问题
2022/06/14 Servers