简单了解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 BeautifulSoup使用方法详解
Nov 21 Python
常用python数据类型转换函数总结
Mar 11 Python
Python中使用摄像头实现简单的延时摄影技术
Mar 27 Python
Sublime开发python程序的示例代码
Jan 24 Python
Python使用pandas对数据进行差分运算的方法
Dec 22 Python
pandas DataFrame 删除重复的行的实现方法
Jan 29 Python
Python OS模块实例详解
Apr 15 Python
python elasticsearch环境搭建详解
Sep 02 Python
Python实现剪刀石头布小游戏(与电脑对战)
Dec 31 Python
tensorflow的ckpt及pb模型持久化方式及转化详解
Feb 12 Python
python爬虫开发之Beautiful Soup模块从安装到详细使用方法与实例
Mar 09 Python
python神经网络编程实现手写数字识别
May 27 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
一个简洁的多级别论坛
2006/10/09 PHP
php中文本操作的类
2007/03/17 PHP
PHP中设置时区,记录日志文件的实现代码
2013/01/07 PHP
PHP文件上传主要代码讲解
2013/09/30 PHP
php导出word文档与excel电子表格的简单示例代码
2014/03/08 PHP
php基于openssl的rsa加密解密示例
2016/07/11 PHP
利用PHP将图片转换成base64编码的实现方法
2016/09/13 PHP
php curl上传、下载、https登陆实现代码
2017/07/23 PHP
WordPress 照片lightbox效果的运用几点
2009/06/22 Javascript
查询绑定数据岛的表格中的文本并修改显示方式的js代码
2009/12/15 Javascript
使用jquery动态加载js文件的方法
2014/12/24 Javascript
jstree创建无限分级树的方法【基于ajax动态创建子节点】
2016/10/25 Javascript
原生JS版和jquery版实现checkbox的全选/全不选/点选/行内点选(Mr.Think)
2016/10/29 Javascript
Nodejs中解决cluster模块的多进程如何共享数据问题
2016/11/10 NodeJs
Angular.js组件之input mask对input输入进行格式化详解
2017/07/10 Javascript
详解Angular CLI + Electron 开发环境搭建
2017/07/20 Javascript
使用Karma做vue组件单元测试的实现
2020/01/16 Javascript
返回上一个url并刷新界面的js代码
2020/09/12 Javascript
Python实现list反转实例汇总
2014/11/11 Python
Python编程之序列操作实例详解
2017/07/22 Python
python并发编程之线程实例解析
2017/12/27 Python
对TensorFlow的assign赋值用法详解
2018/07/30 Python
Python assert关键字原理及实例解析
2019/12/13 Python
Python爬取12306车次信息代码详解
2020/08/12 Python
python调用win32接口进行截图的示例
2020/11/11 Python
html5 Canvas实现图片旋转的示例
2018/01/15 HTML / CSS
荷兰之家英文站:Holland at Home
2016/10/26 全球购物
俄罗斯金苹果网上化妆品和香水商店:Goldapple
2019/12/01 全球购物
公务员的自我鉴定
2013/10/26 职场文书
有关爱国演讲稿
2014/05/07 职场文书
学习朴航瑛老师爱岗敬业先进事迹思想汇报
2014/09/17 职场文书
校本研修个人总结
2015/02/28 职场文书
道歉情书大全
2015/05/12 职场文书
预备党员转正党小组意见
2015/06/01 职场文书
致青春观后感
2015/06/09 职场文书
Spring Cache和EhCache实现缓存管理方式
2021/06/15 Java/Android