简单了解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中列表、字典、元组、集合数据结构整理
Nov 20 Python
Python正则抓取网易新闻的方法示例
Apr 21 Python
python实现决策树C4.5算法详解(在ID3基础上改进)
May 31 Python
python实现图片批量压缩程序
Jul 23 Python
从请求到响应过程中django都做了哪些处理
Aug 01 Python
对Python协程之异步同步的区别详解
Feb 19 Python
Python partial函数原理及用法解析
Dec 11 Python
使用Python实现将多表分批次从数据库导出到Excel
May 15 Python
pandas处理csv文件的方法步骤
Oct 16 Python
基于Python制作一副扑克牌过程详解
Oct 19 Python
详解Python流程控制语句
Oct 28 Python
Django一小时写出账号密码管理系统
Apr 29 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将session信息存储到数据库的类实例
2015/03/04 PHP
PHP中异常处理的一些方法整理
2015/07/03 PHP
摘自织梦CMS的HTTP文件下载类
2015/08/08 PHP
php获取POST数据的三种方法实例详解
2016/12/20 PHP
firefox 和 ie 事件处理的细节,研究,再研究 书写同时兼容ie和ff的事件处理代码
2007/04/12 Javascript
css图片自适应大小
2007/11/28 Javascript
ext监听事件方法[初级篇]
2008/04/27 Javascript
javascript EXCEL 操作类代码
2009/07/30 Javascript
JS解析XML的实现代码
2009/11/12 Javascript
JavaScript中使用replace结合正则实现replaceAll的效果
2010/06/04 Javascript
Javascript 鼠标移动上去小三角形滑块缓慢跟随效果
2013/04/26 Javascript
jQuery Ajax 全局调用封装实例代码详解
2016/06/02 Javascript
js自调用匿名函数的三种写法(推荐)
2016/08/19 Javascript
将form表单通过ajax实现无刷新提交的简单实例
2016/10/12 Javascript
解决在vue+webpack开发中出现两个或多个菜单公用一个组件问题
2017/11/28 Javascript
浅析Angular19 自定义表单控件
2018/01/31 Javascript
es6新特性之 class 基本用法解析
2018/05/05 Javascript
Vue 路由 过渡动效 数据获取方法
2018/07/31 Javascript
vue中v-text / v-html使用实例代码详解
2019/04/02 Javascript
Python脚本实现代码行数统计代码分享
2015/03/10 Python
Python实现多线程抓取妹子图
2015/08/08 Python
python+matplotlib实现动态绘制图片实例代码(交互式绘图)
2018/01/20 Python
Python之列表的插入&amp;替换修改方法
2018/06/28 Python
Python使用logging模块实现打印log到指定文件的方法
2018/09/05 Python
详解解决Python memory error的问题(四种解决方案)
2019/08/08 Python
Django框架 查询Extra功能实现解析
2019/09/04 Python
windows10 pycharm下安装pyltp库和加载模型实现语义角色标注的示例代码
2020/05/07 Python
使用Filters滤镜弥补CSS3的跨浏览器问题以及兼容低版本IE
2013/01/23 HTML / CSS
推荐10个CSS3 制作的创意下拉菜单效果
2014/02/11 HTML / CSS
俄罗斯香水和化妆品购物网站:Л’Этуаль
2018/05/10 全球购物
Harrods英国:世界领先的奢侈品百货商店
2020/09/23 全球购物
《得道多助,失道寡助》教学反思
2014/04/19 职场文书
连锁超市项目计划书
2014/09/15 职场文书
2014年仓库管理员工作总结
2014/11/18 职场文书
公司仓管员岗位职责
2015/04/01 职场文书
致运动员的广播稿
2015/08/19 职场文书